Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/resources/robot_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,68 @@ resource "harbor_robot_account" "project" {
}
```

### Project with Write-only Secret

```terraform
resource "harbor_project" "main" {
name = "main"
}

resource "harbor_robot_account" "project" {
name = "example-project"
description = "project level robot account"
level = "project"
secret_wo = "StrongSecret123!"
secret_wo_version = 1
permissions {
access {
action = "pull"
resource = "repository"
}
access {
action = "push"
resource = "repository"
}
kind = "project"
namespace = harbor_project.main.name
}
}
```

### Project with Write-only Secret from Ephemeral Random Secret

```terraform
ephemeral "random_password" "robot_secret" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "harbor_project" "main" {
name = "main"
}

resource "harbor_robot_account" "project" {
name = "example-project"
description = "project level robot account"
level = "project"
secret_wo = tostring(ephemeral.random_password.robot_secret.result)
secret_wo_version = 1
permissions {
access {
action = "pull"
resource = "repository"
}
access {
action = "push"
resource = "repository"
}
kind = "project"
namespace = harbor_project.main.name
}
}
```

The above example creates a project level robot account with permissions to
- pull repository on project "main"
- push repository on project "main"
Expand All @@ -111,6 +173,8 @@ The above example creates a project level robot account with permissions to
- `disable` (Boolean) Disables the robot account when set to `true`.
- `duration` (Number) By default, the robot account will not expire. Set it to the amount of days until the account should expire.
- `secret` (String, Sensitive) The secret of the robot account used for authentication. Defaults to random generated string from Harbor.
- `secret_wo` (String, Write-only) Write-only alternative for `secret`. Must be used together with `secret_wo_version`.
- `secret_wo_version` (Number) Rotation trigger for write-only secret updates. Must be used together with `secret_wo`.

### Read-Only

Expand Down
40 changes: 36 additions & 4 deletions docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,40 @@ description: |-

```terraform
resource "harbor_user" "main" {
username = "john"
password = "Password12345!"
username = "john"
password = "Password12345!"
full_name = "John Smith"
email = "john@smith.com"
email = "john@smith.com"
}
```

### Write-only Password

```terraform
resource "harbor_user" "main" {
username = "john"
password_wo = "Password12345!"
password_wo_version = 1
full_name = "John Smith"
email = "john@smith.com"
}
```

### Write-only Password from Ephemeral Random Secret

```terraform
ephemeral "random_password" "user_password" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "harbor_user" "main" {
username = "john"
password_wo = tostring(ephemeral.random_password.user_password.result)
password_wo_version = 1
full_name = "John Smith"
email = "john@smith.com"
}
```

Expand All @@ -27,13 +57,15 @@ resource "harbor_user" "main" {

- `email` (String) The email address of the internal user.
- `full_name` (String) The Full Name of the internal user.
- `password` (String, Sensitive) The password for the internal user.
- `username` (String) The username of the internal user.

### Optional

- `admin` (Boolean) If the user will have admin rights within Harbor (Default: `false`)
- `comment` (String) Any comments for that are need for the internal user.
- `password` (String, Sensitive) The password for the internal user. Conflicts with `password_wo_version`.
- `password_wo` (String, Write-only) Write-only alternative for `password`. Must be used together with `password_wo_version`.
- `password_wo_version` (Number) Rotation trigger for write-only password updates. Must be used together with `password_wo`.

### Read-Only

Expand Down
72 changes: 67 additions & 5 deletions provider/resource_robot_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import (

"github.com/goharbor/terraform-provider-harbor/client"
"github.com/goharbor/terraform-provider-harbor/models"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceRobotAccount() *schema.Resource {
return &schema.Resource{
ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
validation.PreferWriteOnlyAttribute(cty.GetAttrPath("secret"), cty.GetAttrPath("secret_wo")),
},
Schema: map[string]*schema.Schema{
"robot_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -52,6 +57,31 @@ func resourceRobotAccount() *schema.Resource {
Optional: true,
Computed: true,
Sensitive: true,
ConflictsWith: []string{
"secret_wo",
"secret_wo_version",
},
},
"secret_wo": {
Type: schema.TypeString,
Optional: true,
WriteOnly: true,
RequiredWith: []string{
"secret_wo_version",
},
ConflictsWith: []string{
"secret",
},
},
"secret_wo_version": {
Type: schema.TypeInt,
Optional: true,
RequiredWith: []string{
"secret_wo",
},
ConflictsWith: []string{
"secret",
},
},
"permissions": {
Type: schema.TypeSet,
Expand Down Expand Up @@ -114,6 +144,10 @@ func resourceRobotAccountCreate(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)

body := client.RobotBody(d)
secretWriteOnly, err := getWriteOnlyString(d, "secret_wo")
if err != nil {
return err
}

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

if d.Get("secret").(string) != "" {
secret := d.Get("secret").(string)
if secretWriteOnly != "" {
secret = secretWriteOnly
}

if secret != "" {
robotID := strconv.Itoa(jsonData.ID)
secret := models.RobotSecret{
Secret: d.Get("secret").(string),
Secret: secret,
}
_, _, _, err := apiClient.SendRequest("PATCH", models.PathRobots+"/"+robotID, secret, 200)
if err != nil {
Expand Down Expand Up @@ -241,6 +280,12 @@ func resourceRobotAccountRead(d *schema.ResourceData, m interface{}) error {

func resourceRobotAccountUpdate(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)
oldSecret, _ := d.GetChange("secret")
oldSecretWOVersion, _ := d.GetChange("secret_wo_version")
secretWriteOnly, err := getWriteOnlyString(d, "secret_wo")
if err != nil {
return err
}

body := client.RobotBody(d)

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

_, _, _, err := apiClient.SendRequest("PUT", d.Id(), body, 200)
_, _, _, err = apiClient.SendRequest("PUT", d.Id(), body, 200)
if err != nil {
return err
}

if d.HasChange("secret") {
if d.HasChange("secret") || d.HasChange("secret_wo_version") {
secretValue := d.Get("secret").(string)
if d.HasChange("secret_wo_version") {
if secretWriteOnly == "" && !d.HasChange("secret") {
_ = d.Set("secret_wo_version", oldSecretWOVersion)
return fmt.Errorf("secret_wo must be configured when secret_wo_version changes")
}
if secretWriteOnly != "" {
secretValue = secretWriteOnly
}
}

secret := models.RobotSecret{
Secret: d.Get("secret").(string),
Secret: secretValue,
}
_, _, _, err := apiClient.SendRequest("PATCH", d.Id(), secret, 200)
if err != nil {
if d.HasChange("secret") {
_ = d.Set("secret", oldSecret)
}
if d.HasChange("secret_wo_version") {
_ = d.Set("secret_wo_version", oldSecretWOVersion)
}
return err
}
}
Expand Down
50 changes: 50 additions & 0 deletions provider/resource_robot_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@ func TestAccRobotProject(t *testing.T) {
})
}

func TestAccRobotProjectWriteOnlySecret(t *testing.T) {
randStr := randomString(4)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRobotDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckRobotProjectWriteOnlySecret("acctest_robot_" + strings.ToLower(randStr)),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceExists("harbor_project.main"),

testAccCheckResourceExists(harborRobotAccount),
resource.TestCheckResourceAttr(
harborRobotAccount, "name", "test_robot_project_wo"),
),
},
},
})
}

func testAccCheckRobotDestroy(s *terraform.State) error {
apiClient := testAccProvider.Meta().(*client.Client)

Expand Down Expand Up @@ -140,3 +162,31 @@ func testAccCheckRobotProject(projectName string) string {
}
`, projectName)
}

func testAccCheckRobotProjectWriteOnlySecret(projectName string) string {
return fmt.Sprintf(`
resource "harbor_robot_account" "main" {
name = "test_robot_project_wo"
description = "project level robot account with write-only secret"
level = "project"
secret_wo = "robotSecret12345"
secret_wo_version = 1
permissions {
access {
action = "pull"
resource = "repository"
}
access {
action = "push"
resource = "repository"
}
kind = "project"
namespace = harbor_project.main.name
}
}
resource "harbor_project" "main" {
name = "%v"
}
`, projectName)
}
Loading
Loading