Skip to content

Commit 0364ade

Browse files
d.efimenkodefimenko-ops
authored andcommitted
add support write only arguments for resources robot_account and user
Signed-off-by: defimenko-ops <defimenko000@gmail.com>
1 parent 67cfed5 commit 0364ade

File tree

6 files changed

+306
-8
lines changed

6 files changed

+306
-8
lines changed

provider/resource_robot_account.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ import (
88

99
"github.com/goharbor/terraform-provider-harbor/client"
1010
"github.com/goharbor/terraform-provider-harbor/models"
11+
"github.com/hashicorp/go-cty/cty"
1112
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1214
)
1315

1416
func resourceRobotAccount() *schema.Resource {
1517
return &schema.Resource{
18+
ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
19+
validation.PreferWriteOnlyAttribute(cty.GetAttrPath("secret"), cty.GetAttrPath("secret_wo")),
20+
},
1621
Schema: map[string]*schema.Schema{
1722
"robot_id": {
1823
Type: schema.TypeString,
@@ -52,6 +57,31 @@ func resourceRobotAccount() *schema.Resource {
5257
Optional: true,
5358
Computed: true,
5459
Sensitive: true,
60+
ConflictsWith: []string{
61+
"secret_wo",
62+
"secret_wo_version",
63+
},
64+
},
65+
"secret_wo": {
66+
Type: schema.TypeString,
67+
Optional: true,
68+
WriteOnly: true,
69+
RequiredWith: []string{
70+
"secret_wo_version",
71+
},
72+
ConflictsWith: []string{
73+
"secret",
74+
},
75+
},
76+
"secret_wo_version": {
77+
Type: schema.TypeInt,
78+
Optional: true,
79+
RequiredWith: []string{
80+
"secret_wo",
81+
},
82+
ConflictsWith: []string{
83+
"secret",
84+
},
5585
},
5686
"permissions": {
5787
Type: schema.TypeSet,
@@ -114,6 +144,10 @@ func resourceRobotAccountCreate(d *schema.ResourceData, m interface{}) error {
114144
apiClient := m.(*client.Client)
115145

116146
body := client.RobotBody(d)
147+
secretWriteOnly, err := getWriteOnlyString(d, "secret_wo")
148+
if err != nil {
149+
return err
150+
}
117151

118152
resp, headers, _, err := apiClient.SendRequest("POST", models.PathRobots, body, 201)
119153
if err != nil {
@@ -131,10 +165,15 @@ func resourceRobotAccountCreate(d *schema.ResourceData, m interface{}) error {
131165
return err
132166
}
133167

134-
if d.Get("secret").(string) != "" {
168+
secret := d.Get("secret").(string)
169+
if secretWriteOnly != "" {
170+
secret = secretWriteOnly
171+
}
172+
173+
if secret != "" {
135174
robotID := strconv.Itoa(jsonData.ID)
136175
secret := models.RobotSecret{
137-
Secret: d.Get("secret").(string),
176+
Secret: secret,
138177
}
139178
_, _, _, err := apiClient.SendRequest("PATCH", models.PathRobots+"/"+robotID, secret, 200)
140179
if err != nil {
@@ -241,6 +280,12 @@ func resourceRobotAccountRead(d *schema.ResourceData, m interface{}) error {
241280

242281
func resourceRobotAccountUpdate(d *schema.ResourceData, m interface{}) error {
243282
apiClient := m.(*client.Client)
283+
oldSecret, _ := d.GetChange("secret")
284+
oldSecretWOVersion, _ := d.GetChange("secret_wo_version")
285+
secretWriteOnly, err := getWriteOnlyString(d, "secret_wo")
286+
if err != nil {
287+
return err
288+
}
244289

245290
body := client.RobotBody(d)
246291

@@ -254,17 +299,34 @@ func resourceRobotAccountUpdate(d *schema.ResourceData, m interface{}) error {
254299
body.Name = robot.Name
255300
}
256301

257-
_, _, _, err := apiClient.SendRequest("PUT", d.Id(), body, 200)
302+
_, _, _, err = apiClient.SendRequest("PUT", d.Id(), body, 200)
258303
if err != nil {
259304
return err
260305
}
261306

262-
if d.HasChange("secret") {
307+
if d.HasChange("secret") || d.HasChange("secret_wo_version") {
308+
secretValue := d.Get("secret").(string)
309+
if d.HasChange("secret_wo_version") {
310+
if secretWriteOnly == "" && !d.HasChange("secret") {
311+
_ = d.Set("secret_wo_version", oldSecretWOVersion)
312+
return fmt.Errorf("secret_wo must be configured when secret_wo_version changes")
313+
}
314+
if secretWriteOnly != "" {
315+
secretValue = secretWriteOnly
316+
}
317+
}
318+
263319
secret := models.RobotSecret{
264-
Secret: d.Get("secret").(string),
320+
Secret: secretValue,
265321
}
266322
_, _, _, err := apiClient.SendRequest("PATCH", d.Id(), secret, 200)
267323
if err != nil {
324+
if d.HasChange("secret") {
325+
_ = d.Set("secret", oldSecret)
326+
}
327+
if d.HasChange("secret_wo_version") {
328+
_ = d.Set("secret_wo_version", oldSecretWOVersion)
329+
}
268330
return err
269331
}
270332
}

provider/resource_robot_account_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,28 @@ func TestAccRobotProject(t *testing.T) {
5656
})
5757
}
5858

59+
func TestAccRobotProjectWriteOnlySecret(t *testing.T) {
60+
randStr := randomString(4)
61+
62+
resource.Test(t, resource.TestCase{
63+
PreCheck: func() { testAccPreCheck(t) },
64+
Providers: testAccProviders,
65+
CheckDestroy: testAccCheckRobotDestroy,
66+
Steps: []resource.TestStep{
67+
{
68+
Config: testAccCheckRobotProjectWriteOnlySecret("acctest_robot_" + strings.ToLower(randStr)),
69+
Check: resource.ComposeTestCheckFunc(
70+
testAccCheckResourceExists("harbor_project.main"),
71+
72+
testAccCheckResourceExists(harborRobotAccount),
73+
resource.TestCheckResourceAttr(
74+
harborRobotAccount, "name", "test_robot_project_wo"),
75+
),
76+
},
77+
},
78+
})
79+
}
80+
5981
func testAccCheckRobotDestroy(s *terraform.State) error {
6082
apiClient := testAccProvider.Meta().(*client.Client)
6183

@@ -140,3 +162,31 @@ func testAccCheckRobotProject(projectName string) string {
140162
}
141163
`, projectName)
142164
}
165+
166+
func testAccCheckRobotProjectWriteOnlySecret(projectName string) string {
167+
return fmt.Sprintf(`
168+
resource "harbor_robot_account" "main" {
169+
name = "test_robot_project_wo"
170+
description = "project level robot account with write-only secret"
171+
level = "project"
172+
secret_wo = "robotSecret12345"
173+
secret_wo_version = 1
174+
permissions {
175+
access {
176+
action = "pull"
177+
resource = "repository"
178+
}
179+
access {
180+
action = "push"
181+
resource = "repository"
182+
}
183+
kind = "project"
184+
namespace = harbor_project.main.name
185+
}
186+
}
187+
188+
resource "harbor_project" "main" {
189+
name = "%v"
190+
}
191+
`, projectName)
192+
}

provider/resource_user.go

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import (
66

77
"github.com/goharbor/terraform-provider-harbor/client"
88
"github.com/goharbor/terraform-provider-harbor/models"
9+
"github.com/hashicorp/go-cty/cty"
910
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1012
)
1113

1214
func resourceUser() *schema.Resource {
1315
return &schema.Resource{
16+
ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
17+
validation.PreferWriteOnlyAttribute(cty.GetAttrPath("password"), cty.GetAttrPath("password_wo")),
18+
},
1419
Schema: map[string]*schema.Schema{
1520
"username": {
1621
Type: schema.TypeString,
@@ -19,8 +24,37 @@ func resourceUser() *schema.Resource {
1924
},
2025
"password": {
2126
Type: schema.TypeString,
22-
Required: true,
27+
Optional: true,
2328
Sensitive: true,
29+
ExactlyOneOf: []string{
30+
"password",
31+
"password_wo",
32+
},
33+
ConflictsWith: []string{
34+
"password_wo_version",
35+
},
36+
},
37+
"password_wo": {
38+
Type: schema.TypeString,
39+
Optional: true,
40+
WriteOnly: true,
41+
ExactlyOneOf: []string{
42+
"password",
43+
"password_wo",
44+
},
45+
RequiredWith: []string{
46+
"password_wo_version",
47+
},
48+
},
49+
"password_wo_version": {
50+
Type: schema.TypeInt,
51+
Optional: true,
52+
RequiredWith: []string{
53+
"password_wo",
54+
},
55+
ConflictsWith: []string{
56+
"password",
57+
},
2458
},
2559
"full_name": {
2660
Type: schema.TypeString,
@@ -54,6 +88,18 @@ func resourceUserCreate(d *schema.ResourceData, m interface{}) error {
5488
apiClient := m.(*client.Client)
5589

5690
body := client.UserBody(d)
91+
passwordWriteOnly, err := getWriteOnlyString(d, "password_wo")
92+
if err != nil {
93+
return err
94+
}
95+
if passwordWriteOnly != "" {
96+
body.Password = passwordWriteOnly
97+
body.Newpassword = passwordWriteOnly
98+
}
99+
100+
if body.Password == "" {
101+
return fmt.Errorf("one of password or password_wo must be configured")
102+
}
57103

58104
_, header, _, err := apiClient.SendRequest("POST", models.PathUsers, &body, 201)
59105
if err != nil {
@@ -92,9 +138,20 @@ func resourceUserRead(d *schema.ResourceData, m interface{}) error {
92138

93139
func resourceUserUpdate(d *schema.ResourceData, m interface{}) error {
94140
apiClient := m.(*client.Client)
141+
oldPassword, _ := d.GetChange("password")
142+
oldPasswordWOVersion, _ := d.GetChange("password_wo_version")
95143

96144
body := client.UserBody(d)
97-
_, _, _, err := apiClient.SendRequest("PUT", d.Id(), body, 200)
145+
passwordWriteOnly, err := getWriteOnlyString(d, "password_wo")
146+
if err != nil {
147+
return err
148+
}
149+
if passwordWriteOnly != "" {
150+
body.Password = passwordWriteOnly
151+
body.Newpassword = passwordWriteOnly
152+
}
153+
154+
_, _, _, err = apiClient.SendRequest("PUT", d.Id(), body, 200)
98155
if err != nil {
99156
return err
100157
}
@@ -104,9 +161,19 @@ func resourceUserUpdate(d *schema.ResourceData, m interface{}) error {
104161
return err
105162
}
106163

107-
if d.HasChange("password") == true {
164+
if d.HasChange("password") || d.HasChange("password_wo_version") {
165+
if d.HasChange("password_wo_version") && passwordWriteOnly == "" && !d.HasChange("password") {
166+
_ = d.Set("password_wo_version", oldPasswordWOVersion)
167+
return fmt.Errorf("password_wo must be configured when password_wo_version changes")
168+
}
108169
_, _, _, err = apiClient.SendRequest("PUT", d.Id()+"/password", body, 200)
109170
if err != nil {
171+
if d.HasChange("password") {
172+
_ = d.Set("password", oldPassword)
173+
}
174+
if d.HasChange("password_wo_version") {
175+
_ = d.Set("password_wo_version", oldPasswordWOVersion)
176+
}
110177
return err
111178
}
112179
}

provider/resource_user_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ func TestAccUserUpdate(t *testing.T) {
8888
})
8989
}
9090

91+
func TestAccUserWriteOnlyPassword(t *testing.T) {
92+
resource.Test(t, resource.TestCase{
93+
PreCheck: func() { testAccPreCheck(t) },
94+
Providers: testAccProviders,
95+
CheckDestroy: testAccCheckUserDestroy,
96+
Steps: []resource.TestStep{
97+
{
98+
Config: testAccCheckUserWriteOnly(),
99+
Check: resource.ComposeTestCheckFunc(
100+
testAccCheckResourceExists(resourceHarborUserMain),
101+
resource.TestCheckResourceAttr(
102+
resourceHarborUserMain, "username", "john_wo"),
103+
resource.TestCheckResourceAttr(
104+
resourceHarborUserMain, "full_name", "John WriteOnly"),
105+
resource.TestCheckResourceAttr(
106+
resourceHarborUserMain, "email", "john.writeonly@contoso.com"),
107+
),
108+
},
109+
},
110+
})
111+
}
112+
91113
func testAccCheckUserBasic() string {
92114
return fmt.Sprintf(`
93115
resource "harbor_user" "main" {
@@ -109,3 +131,15 @@ func testAccCheckUserUpdate() string {
109131
}
110132
`)
111133
}
134+
135+
func testAccCheckUserWriteOnly() string {
136+
return fmt.Sprintf(`
137+
resource "harbor_user" "main" {
138+
username = "john_wo"
139+
password_wo = "Password12345"
140+
password_wo_version = 1
141+
full_name = "John WriteOnly"
142+
email = "john.writeonly@contoso.com"
143+
}
144+
`)
145+
}

provider/write_only.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/go-cty/cty"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
func getWriteOnlyString(d *schema.ResourceData, key string) (string, error) {
12+
rawValue, diags := d.GetRawConfigAt(cty.GetAttrPath(key))
13+
14+
return parseWriteOnlyString(rawValue, diags, key)
15+
}
16+
17+
func parseWriteOnlyString(rawValue cty.Value, diags diag.Diagnostics, key string) (string, error) {
18+
if diags.HasError() {
19+
return "", fmt.Errorf("error retrieving write-only argument %q: %v", key, diags)
20+
}
21+
22+
if rawValue.IsNull() {
23+
return "", nil
24+
}
25+
26+
if !rawValue.Type().Equals(cty.String) {
27+
return "", fmt.Errorf("error retrieving write-only argument %q: value must be a string", key)
28+
}
29+
30+
return rawValue.AsString(), nil
31+
}

0 commit comments

Comments
 (0)