Skip to content

Commit

Permalink
feat: add role attributes to custom roles (#286)
Browse files Browse the repository at this point in the history
* upgrade go client from v16 to v17

* update ExperimentsBetaApi to ExperimentsApi

* in data source as well

* fix test

* don't use foundation module

* fix data source test

* run go mod tidy && go mod vendor

* use the non-beta client for experiments api

* make deprecated is_active field optional and computed

* consolidate redundant tests

* update tests to use a role attribute

* add conflicts with field to ensure policy & policy_statements do not get set together

* update description for policy_statements

* add note about syntax

* doc tweaks

* regen docs
  • Loading branch information
sloloris authored Feb 6, 2025
1 parent 8c766fe commit 5160b78
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 119 deletions.
6 changes: 3 additions & 3 deletions docs/resources/custom_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ resource "launchdarkly_custom_role" "example" {

### Optional

- `base_permissions` (String) The base permission level - either reader or no_access. Defaults to reader
- `description` (String) Description of the custom role
- `base_permissions` (String) The base permission level - either reader or no_access. Defaults to reader.
- `description` (String) Description of the custom role.
- `policy` (Block Set, Deprecated) (see [below for nested schema](#nestedblock--policy))
- `policy_statements` (Block List) (see [below for nested schema](#nestedblock--policy_statements))
- `policy_statements` (Block List) An array of the policy statements that define the permissions for the custom role. This field accepts [role attributes](https://docs.launchdarkly.com/home/getting-started/vocabulary#role-attribute). To use role attributes, use the syntax `$${roleAttribute/<YOUR_ROLE_ATTRIBUTE>}` in lieu of your usual resource keys. (see [below for nested schema](#nestedblock--policy_statements))

### Read-Only

Expand Down
9 changes: 5 additions & 4 deletions launchdarkly/policies_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (

func policyArraySchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Set: policyHash,
Optional: true,
Deprecated: "'policy' is now deprecated. Please migrate to 'policy_statements' to maintain future compatability.",
Type: schema.TypeSet,
Set: policyHash,
Optional: true,
Deprecated: "'policy' is now deprecated. Please migrate to 'policy_statements' to maintain future compatability.",
ConflictsWith: []string{POLICY_STATEMENTS},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
RESOURCES: {
Expand Down
13 changes: 9 additions & 4 deletions launchdarkly/resource_launchdarkly_custom_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@ This resource allows you to create and manage custom roles within your LaunchDar
DESCRIPTION: {
Type: schema.TypeString,
Optional: true,
Description: "Description of the custom role",
Description: "Description of the custom role.",
},
BASE_PERMISSIONS: {
Type: schema.TypeString,
Optional: true,
Description: "The base permission level - either reader or no_access. Defaults to reader",
Description: "The base permission level - either reader or no_access. Defaults to reader.",
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"reader", "no_access"}, false)),
Default: "reader",
},
POLICY: policyArraySchema(),
POLICY_STATEMENTS: policyStatementsSchema(policyStatementSchemaOptions{optional: true}),
POLICY: policyArraySchema(),
POLICY_STATEMENTS: policyStatementsSchema(
policyStatementSchemaOptions{
optional: true,
conflictsWith: []string{POLICY},
description: "An array of the policy statements that define the permissions for the custom role. This field accepts [role attributes](https://docs.launchdarkly.com/home/getting-started/vocabulary#role-attribute). To use role attributes, use the syntax `$${roleAttribute/<YOUR_ROLE_ATTRIBUTE>}` in lieu of your usual resource keys.",
}),
},
}
}
Expand Down
168 changes: 60 additions & 108 deletions launchdarkly/resource_launchdarkly_custom_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ const (
}
}
`
// IMPORTANT TO NOTE that the $ character must be escaped in terraform by using a double $$
// otherwas ${} will be interpreted as a terraform variable and throw an error
testAccCustomRoleUpdate = `
resource "launchdarkly_custom_role" "test" {
key = "%s"
name = "Updated - %s"
policy {
actions = ["*"]
effect = "allow"
resources = ["proj/*:env/staging"]
resources = ["proj/*:env/$${roleAttribute/devEnvironments}"]
}
}
`
Expand All @@ -42,19 +44,7 @@ resource "launchdarkly_custom_role" "test" {
policy_statements {
actions = ["*"]
effect = "allow"
resources = ["proj/*:env/staging"]
}
}
`
testAccCustomRoleCreateWithNotStatements = `
resource "launchdarkly_custom_role" "test" {
key = "%s"
name = "Custom role - %s"
description = "Don't allow all actions on non-staging environments"
policy_statements {
not_actions = ["*"]
effect = "allow"
not_resources = ["proj/*:env/staging"]
resources = ["proj/$${roleAttribute/devProjects}:env/staging"]
}
}
`
Expand All @@ -69,6 +59,18 @@ resource "launchdarkly_custom_role" "test" {
resources = ["proj/*:env/production"]
}
}
`
testAccCustomRoleCreateWithNotStatements = `
resource "launchdarkly_custom_role" "test" {
key = "%s"
name = "Custom role - %s"
description = "Don't allow all actions on non-staging environments"
policy_statements {
not_actions = ["*"]
effect = "allow"
not_resources = ["proj/*:env/staging"]
}
}
`
testAccCustomRoleUpdateWithNotStatements = `
resource "launchdarkly_custom_role" "test" {
Expand All @@ -84,7 +86,7 @@ resource "launchdarkly_custom_role" "test" {
`
)

func TestAccCustomRole_Create(t *testing.T) {
func TestAccCustomRole_CreateAndUpdate(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
Expand All @@ -110,11 +112,27 @@ func TestAccCustomRole_Create(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "policy.0.effect", "deny"),
),
},
{
Config: fmt.Sprintf(testAccCustomRoleUpdate, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
resource.TestCheckResourceAttr(resourceName, KEY, key),
resource.TestCheckResourceAttr(resourceName, NAME, "Updated - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, ""), // should be empty after removal
resource.TestCheckResourceAttr(resourceName, BASE_PERMISSIONS, "reader"),
resource.TestCheckResourceAttr(resourceName, "policy.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy.0.resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.resources.0", "proj/*:env/${roleAttribute/devEnvironments}"),
resource.TestCheckResourceAttr(resourceName, "policy.0.effect", "allow"),
),
},
},
})
}

func TestAccCustomRole_CreateWithStatements(t *testing.T) {
func TestAccCustomRole_CreateAndUpdateWithStatements(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
Expand All @@ -136,7 +154,7 @@ func TestAccCustomRole_CreateWithStatements(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.0", "proj/*:env/staging"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.0", "proj/${roleAttribute/devProjects}:env/staging"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "allow"),
),
},
Expand All @@ -145,35 +163,20 @@ func TestAccCustomRole_CreateWithStatements(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccCustomRole_CreateWithNotStatements(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCustomRoleCreateWithNotStatements, key, name),
Config: fmt.Sprintf(testAccCustomRoleUpdateWithStatements, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
resource.TestCheckResourceAttr(resourceName, KEY, key),
resource.TestCheckResourceAttr(resourceName, NAME, "Custom role - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Don't allow all actions on non-staging environments"),
resource.TestCheckResourceAttr(resourceName, BASE_PERMISSIONS, "reader"),
resource.TestCheckResourceAttr(resourceName, NAME, "Updated role - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Deny all actions on production environments"),
resource.TestCheckResourceAttr(resourceName, "policy.#", "0"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_resources.0", "proj/*:env/staging"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "allow"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.0", "proj/*:env/production"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "deny"),
),
},
{
Expand All @@ -185,7 +188,7 @@ func TestAccCustomRole_CreateWithNotStatements(t *testing.T) {
})
}

func TestAccCustomRole_Update(t *testing.T) {
func TestAccCustomRole_CreateAndUpdateWithNotStatements(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
Expand All @@ -196,82 +199,26 @@ func TestAccCustomRole_Update(t *testing.T) {
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCustomRoleCreate, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
),
},
{
Config: fmt.Sprintf(testAccCustomRoleUpdate, key, name),
Config: fmt.Sprintf(testAccCustomRoleCreateWithNotStatements, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
resource.TestCheckResourceAttr(resourceName, KEY, key),
resource.TestCheckResourceAttr(resourceName, NAME, "Updated - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, ""), // should be empty after removal
resource.TestCheckResourceAttr(resourceName, NAME, "Custom role - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Don't allow all actions on non-staging environments"),
resource.TestCheckResourceAttr(resourceName, BASE_PERMISSIONS, "reader"),
resource.TestCheckResourceAttr(resourceName, "policy.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy.0.resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy.0.resources.0", "proj/*:env/staging"),
resource.TestCheckResourceAttr(resourceName, "policy.0.effect", "allow"),
),
},
},
})
}

func TestAccCustomRole_UpdateWithStatements(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCustomRoleCreate, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
),
},
{
Config: fmt.Sprintf(testAccCustomRoleUpdateWithStatements, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
resource.TestCheckResourceAttr(resourceName, KEY, key),
resource.TestCheckResourceAttr(resourceName, NAME, "Updated role - "+name),
resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Deny all actions on production environments"),
resource.TestCheckResourceAttr(resourceName, "policy.#", "0"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.0", "proj/*:env/production"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "deny"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_actions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_actions.0", "*"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_resources.#", "1"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.not_resources.0", "proj/*:env/staging"),
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "allow"),
),
},
},
})
}

func TestAccCustomRole_UpdateWithNotStatements(t *testing.T) {
key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_custom_role.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCustomRoleCreateWithStatements, key, name),
Check: resource.ComposeTestCheckFunc(
testAccCheckCustomRoleExists(resourceName),
),
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: fmt.Sprintf(testAccCustomRoleUpdateWithNotStatements, key, name),
Expand All @@ -289,6 +236,11 @@ func TestAccCustomRole_UpdateWithNotStatements(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "deny"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down

0 comments on commit 5160b78

Please sign in to comment.