feat: Add is_primary_for_scope attribute to boundary_auth_method and boundary_auth_method_password resources (#753)

This commit is contained in:
Rayenn Hamrouni authored and GitHub committed 2025-11-11 13:55:33 -08:00
1 parent 3e539fec8a
commit 8c933816a5
9 files changed
+283 -5

No files matched your search

+7
View File
@@ -25,6 +25,12 @@ resource "boundary_auth_method" "password" {
scope_id = boundary_scope.org.id
type = "password"
}
resource "boundary_auth_method" "password_is_primary" {
scope_id = boundary_scope.org.id
type = "password"
is_primary_for_scope = true
}
```
<!-- schema generated by tfplugindocs -->
@@ -38,6 +44,7 @@ resource "boundary_auth_method" "password" {
### Optional
- `description` (String) The auth method description.
- `is_primary_for_scope` (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides.
- `min_login_name_length` (Number, Deprecated) The minimum login name length.
- `min_password_length` (Number, Deprecated) The minimum password length.
- `name` (String) The auth method name. Defaults to the resource name.
+30
View File
@@ -10,7 +10,26 @@ description: |-
The auth method resource allows you to configure a Boundary auth_method_password.
## Example Usage
```terraform
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_auth_method_password" "password" {
scope_id = boundary_scope.org.id
}
resource "boundary_auth_method_password" "password_is_primary" {
scope_id = boundary_scope.org.id
is_primary_for_scope = true
}
```
<!-- schema generated by tfplugindocs -->
## Schema
@@ -22,6 +41,7 @@ The auth method resource allows you to configure a Boundary auth_method_password
### Optional
- `description` (String) The auth method description.
- `is_primary_for_scope` (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides.
- `min_login_name_length` (Number) The minimum login name length.
- `min_password_length` (Number) The minimum password length.
- `name` (String) The auth method name. Defaults to the resource name.
@@ -30,3 +50,13 @@ The auth method resource allows you to configure a Boundary auth_method_password
### Read-Only
- `id` (String) The ID of the account.
## Import
Import is supported using the following syntax:
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
```shell
terraform import boundary_auth_method_password.foo <my-id>
```
@@ -10,3 +10,9 @@ resource "boundary_auth_method" "password" {
scope_id = boundary_scope.org.id
type = "password"
}
resource "boundary_auth_method" "password_is_primary" {
scope_id = boundary_scope.org.id
type = "password"
is_primary_for_scope = true
}
@@ -0,0 +1 @@
terraform import boundary_auth_method_password.foo <my-id>
@@ -0,0 +1,16 @@
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_auth_method_password" "password" {
scope_id = boundary_scope.org.id
}
resource "boundary_auth_method_password" "password_is_primary" {
scope_id = boundary_scope.org.id
is_primary_for_scope = true
}
+67 -5
View File
@@ -73,6 +73,11 @@ func resourceAuthMethod() *schema.Resource {
Computed: true,
Deprecated: "Will be removed in favor of using attributes parameter",
},
authmethodIsPrimaryForScopeKey: {
Description: "When true, makes this auth method the primary auth method for the scope in which it resides.",
Type: schema.TypeBool,
Optional: true,
},
},
}
}
@@ -91,6 +96,10 @@ func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interfa
return err
}
if p, ok := raw[authmethodIsPrimaryForScopeKey]; ok {
d.Set(authmethodIsPrimaryForScopeKey, p.(bool))
}
switch raw["type"].(string) {
case authmethodTypePassword:
if attrsVal, ok := raw["attributes"]; ok {
@@ -160,6 +169,19 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf("nil auth method after create")
}
amid := amcr.GetResponse().Map["id"].(string)
// update scope when set to primary
if p, ok := d.GetOk(authmethodIsPrimaryForScopeKey); ok {
if p.(bool) {
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, amid, meta); err != nil {
return diag.Errorf("%v", err)
}
amcr.GetResponse().Map[authmethodIsPrimaryForScopeKey] = true
}
}
if err := setFromAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
@@ -183,6 +205,15 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
return diag.Errorf("auth method nil after read")
}
serr, isPrimary := readScopeIsPrimaryAuthMethodId(ctx, amrr.GetResponse().Map["scope_id"].(string), amrr.GetResponse().Map["id"].(string), meta)
if serr != nil {
return diag.Errorf("%v", serr)
}
if isPrimary {
amrr.GetResponse().Map[authmethodIsPrimaryForScopeKey] = true
}
if err := setFromAuthMethodResponseMap(d, amrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
@@ -225,13 +256,44 @@ func resourceAuthMethodUpdate(ctx context.Context, d *schema.ResourceData, meta
}
}
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amu, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
if d.HasChange(authmethodIsPrimaryForScopeKey) {
amrr, err := amClient.Read(ctx, d.Id())
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
if amrr == nil {
return diag.Errorf("error updating auth method: nil resource")
}
scopeId := amrr.GetResponse().Map["scope_id"].(string)
authMethodId := amrr.GetResponse().Map["id"].(string)
isPrimary := d.Get(authmethodIsPrimaryForScopeKey).(bool)
if isPrimary {
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, authMethodId, meta); err != nil {
return diag.Errorf("%v", err)
}
}
}
setFromAuthMethodResponseMap(d, amu.GetResponse().Map)
if len(opts) > 0 {
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amu, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
if d.HasChange(authmethodIsPrimaryForScopeKey) {
amu.GetResponse().Map[authmethodIsPrimaryForScopeKey] = d.Get(authmethodIsPrimaryForScopeKey).(bool)
}
setFromAuthMethodResponseMap(d, amu.GetResponse().Map)
}
// If only is_primary_for_scope changed
if d.HasChange(authmethodIsPrimaryForScopeKey) {
return resourceAuthMethodPasswordRead(ctx, d, meta)
}
return nil
}
@@ -18,6 +18,7 @@ const (
authmethodTypePassword = "password"
authmethodMinLoginNameLengthKey = "min_login_name_length"
authmethodMinPasswordLengthKey = "min_password_length"
authmethodIsPrimaryForScopeKey = "is_primary_for_scope"
)
func resourceAuthMethodPassword() *schema.Resource {
@@ -72,6 +73,11 @@ func resourceAuthMethodPassword() *schema.Resource {
Optional: true,
Computed: true,
},
authmethodIsPrimaryForScopeKey: {
Description: "When true, makes this auth method the primary auth method for the scope in which it resides.",
Type: schema.TypeBool,
Optional: true,
},
},
}
}
@@ -82,6 +88,10 @@ func setFromPasswordAuthMethodResponseMap(d *schema.ResourceData, raw map[string
d.Set(ScopeIdKey, raw[ScopeIdKey])
d.Set(TypeKey, raw[TypeKey])
if p, ok := raw[authmethodIsPrimaryForScopeKey]; ok {
d.Set(authmethodIsPrimaryForScopeKey, p.(bool))
}
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
@@ -158,6 +168,19 @@ func resourceAuthMethodPasswordCreate(ctx context.Context, d *schema.ResourceDat
return diag.Errorf("nil auth method after create")
}
amid := amcr.GetResponse().Map["id"].(string)
// update scope when set to primary
if p, ok := d.GetOk(authmethodIsPrimaryForScopeKey); ok {
if p.(bool) {
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, amid, meta); err != nil {
return diag.Errorf("%v", err)
}
amcr.GetResponse().Map[authmethodIsPrimaryForScopeKey] = true
}
}
return setFromPasswordAuthMethodResponseMap(d, amcr.GetResponse().Map)
}
@@ -177,6 +200,15 @@ func resourceAuthMethodPasswordRead(ctx context.Context, d *schema.ResourceData,
return diag.Errorf("auth method nil after read")
}
serr, isPrimary := readScopeIsPrimaryAuthMethodId(ctx, amrr.GetResponse().Map["scope_id"].(string), amrr.GetResponse().Map["id"].(string), meta)
if serr != nil {
return diag.Errorf("%v", serr)
}
if isPrimary {
amrr.GetResponse().Map[authmethodIsPrimaryForScopeKey] = true
}
return setFromPasswordAuthMethodResponseMap(d, amrr.GetResponse().Map)
}
@@ -218,6 +250,24 @@ func resourceAuthMethodPasswordUpdate(ctx context.Context, d *schema.ResourceDat
}
}
if d.HasChange(authmethodIsPrimaryForScopeKey) {
amrr, err := amClient.Read(ctx, d.Id())
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
if amrr == nil {
return diag.Errorf("error updating auth method: nil resource")
}
scopeId := amrr.GetResponse().Map["scope_id"].(string)
authMethodId := amrr.GetResponse().Map["id"].(string)
if d.Get(authmethodIsPrimaryForScopeKey).(bool) {
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, authMethodId, meta); err != nil {
return diag.Errorf("%v", err)
}
}
}
if len(opts) > 0 {
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amur, err := amClient.Update(ctx, d.Id(), 0, opts...)
@@ -225,8 +275,18 @@ func resourceAuthMethodPasswordUpdate(ctx context.Context, d *schema.ResourceDat
return diag.Errorf("error updating auth method: %v", err)
}
if d.HasChange(authmethodIsPrimaryForScopeKey) {
amur.GetResponse().Map[authmethodIsPrimaryForScopeKey] = d.Get(authmethodIsPrimaryForScopeKey).(bool)
}
return setFromPasswordAuthMethodResponseMap(d, amur.GetResponse().Map)
}
// If only is_primary_for_scope changed
if d.HasChange(authmethodIsPrimaryForScopeKey) {
return resourceAuthMethodPasswordRead(ctx, d, meta)
}
return nil
}
@@ -35,6 +35,16 @@ resource "boundary_auth_method_password" "foo" {
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooAuthMethodDescUpdate)
fooAuthMethodIsPrimaryUpdate = fmt.Sprintf(`
resource "boundary_auth_method_password" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
is_primary_for_scope = true
depends_on = [boundary_role.org1_admin]
}`, fooAuthMethodDesc)
)
func TestAccAuthMethodPassword(t *testing.T) {
@@ -55,6 +65,7 @@ func TestAccAuthMethodPassword(t *testing.T) {
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_password.foo", false),
),
},
importStep("boundary_auth_method_password.foo"),
@@ -66,9 +77,47 @@ func TestAccAuthMethodPassword(t *testing.T) {
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_password.foo", false),
),
},
importStep("boundary_auth_method_password.foo"),
},
})
}
func TestAccAuthMethodPasswordIsPrimary(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, passwordAuthMethodType),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, fooAuthMethod),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_password.foo", false),
),
},
importStep("boundary_auth_method_password.foo"),
{
// update
Config: testConfig(url, fooOrg, fooAuthMethodIsPrimaryUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_password.foo", true),
),
},
},
})
}
@@ -42,6 +42,16 @@ resource "boundary_auth_method" "foo" {
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooBaseAuthMethodDescUpdate)
fooBaseAuthMethodIsPrimaryUpdate = fmt.Sprintf(`
resource "boundary_auth_method" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
is_primary_for_scope = true
depends_on = [boundary_role.org1_admin]
}`, fooAuthMethodDesc)
)
func TestAccBaseAuthMethodPassword(t *testing.T) {
@@ -81,6 +91,43 @@ func TestAccBaseAuthMethodPassword(t *testing.T) {
})
}
func TestAccBaseAuthMethodPasswordIsPrimary(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, passwordAuthMethodType),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, fooBaseAuthMethod),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method.foo", false),
),
},
importStep("boundary_auth_method.foo"),
{
// update
Config: testConfig(url, fooOrg, fooBaseAuthMethodIsPrimaryUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method.foo", true),
),
},
},
})
}
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]