diff --git a/docs/index.md b/docs/index.md index 426af813..af2252bb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,7 +31,7 @@ terraform { required_providers { zitadel = { source = "zitadel/zitadel" - version = "1.0.7" + version = "1.1.0" } } } diff --git a/docs/resources/machine_user.md b/docs/resources/machine_user.md index eb9541af..9c1eefec 100644 --- a/docs/resources/machine_user.md +++ b/docs/resources/machine_user.md @@ -17,6 +17,7 @@ resource "zitadel_machine_user" "default" { user_name = "machine@example.com" name = "name" description = "a machine user" + with_secret = false } ``` @@ -33,9 +34,12 @@ resource "zitadel_machine_user" "default" { - `access_token_type` (String) Access token type, supported values: ACCESS_TOKEN_TYPE_BEARER, ACCESS_TOKEN_TYPE_JWT - `description` (String) Description of the user - `org_id` (String) ID of the organization +- `with_secret` (Boolean) Generate machine secret, only applicable if creation or change from false ### Read-Only +- `client_id` (String, Sensitive) Value of the client ID if withSecret is true +- `client_secret` (String, Sensitive) Value of the client secret if withSecret is true - `id` (String) The ID of this resource. - `login_names` (List of String) Loginnames - `preferred_login_name` (String) Preferred login name @@ -44,6 +48,6 @@ resource "zitadel_machine_user" "default" { ## Import ```terraform -# The resource can be imported using the ID format ``, e.g. -terraform import machine_user.imported '123456789012345678:123456789012345678' +# The resource can be imported using the ID format ``, e.g. +terraform import machine_user.imported '123456789012345678:123456789012345678:true:my-machine-user:j76mh34CHVrGGoXPQOg80lch67FIxwc2qIXjBkZoB6oMbf31eGMkB6bvRyaPjR2t' ``` diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 70fb7c3b..649ac1c7 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { zitadel = { source = "zitadel/zitadel" - version = "1.0.7" + version = "1.1.0" } } } diff --git a/examples/provider/resources/machine_user-import.sh b/examples/provider/resources/machine_user-import.sh index 3bcd0fbb..d5eb9f00 100644 --- a/examples/provider/resources/machine_user-import.sh +++ b/examples/provider/resources/machine_user-import.sh @@ -1,2 +1,2 @@ -# The resource can be imported using the ID format ``, e.g. -terraform import machine_user.imported '123456789012345678:123456789012345678' +# The resource can be imported using the ID format ``, e.g. +terraform import machine_user.imported '123456789012345678:123456789012345678:true:my-machine-user:j76mh34CHVrGGoXPQOg80lch67FIxwc2qIXjBkZoB6oMbf31eGMkB6bvRyaPjR2t' diff --git a/examples/provider/resources/machine_user.tf b/examples/provider/resources/machine_user.tf index 358ec2e9..717ed894 100644 --- a/examples/provider/resources/machine_user.tf +++ b/examples/provider/resources/machine_user.tf @@ -3,4 +3,5 @@ resource "zitadel_machine_user" "default" { user_name = "machine@example.com" name = "name" description = "a machine user" + with_secret = false } diff --git a/zitadel/helper/import.go b/zitadel/helper/import.go index 4ff96c21..76b10b1d 100644 --- a/zitadel/helper/import.go +++ b/zitadel/helper/import.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "sort" + "strconv" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -201,6 +202,10 @@ func ConvertNonEmpty(importValue string) (interface{}, error) { return importValue, nil } +func ConvertBool(importValue string) (interface{}, error) { + return strconv.ParseBool(importValue) +} + // ImportIDValidationError wraps err with a help message about the expected format if it is not nil func ImportIDValidationError(givenID string, requiredKeys, optionalKeys []string, err error) error { if err == nil { diff --git a/zitadel/machine_user/const.go b/zitadel/machine_user/const.go index 154e42b6..ff26d6da 100644 --- a/zitadel/machine_user/const.go +++ b/zitadel/machine_user/const.go @@ -15,6 +15,9 @@ const ( nameVar = "name" DescriptionVar = "description" accessTokenTypeVar = "access_token_type" + WithSecretVar = "with_secret" + clientIDVar = "client_id" + clientSecretVar = "client_secret" ) var ( diff --git a/zitadel/machine_user/funcs.go b/zitadel/machine_user/funcs.go index a3720645..cd5ae36b 100644 --- a/zitadel/machine_user/funcs.go +++ b/zitadel/machine_user/funcs.go @@ -58,6 +58,22 @@ func create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia return diag.Errorf("failed to create machine user: %v", err) } d.SetId(respUser.UserId) + + if d.Get(WithSecretVar).(bool) { + resp, err := client.GenerateMachineSecret(helper.CtxWithOrgID(ctx, d), &management.GenerateMachineSecretRequest{ + UserId: respUser.UserId, + }) + if err != nil { + return diag.Errorf("failed to generate machine user secret: %v", err) + } + if err := d.Set(clientIDVar, resp.GetClientId()); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientIDVar, err) + } + if err := d.Set(clientSecretVar, resp.GetClientSecret()); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientSecretVar, err) + } + } + // To avoid diffs for terraform plan -refresh=false right after creation, we query and set the computed values. // The acceptance tests rely on this, too. return read(ctx, d, m) @@ -97,6 +113,36 @@ func update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia return diag.Errorf("failed to update machine user: %v", err) } } + + if d.HasChange(WithSecretVar) { + if d.Get(WithSecretVar).(bool) { + resp, err := client.GenerateMachineSecret(helper.CtxWithOrgID(ctx, d), &management.GenerateMachineSecretRequest{ + UserId: d.Id(), + }) + if err != nil { + return diag.Errorf("failed to generate machine user secret: %v", err) + } + if err := d.Set(clientIDVar, resp.GetClientId()); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientIDVar, err) + } + if err := d.Set(clientSecretVar, resp.GetClientSecret()); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientSecretVar, err) + } + } else { + _, err := client.RemoveMachineSecret(helper.CtxWithOrgID(ctx, d), &management.RemoveMachineSecretRequest{ + UserId: d.Id(), + }) + if err != nil { + return diag.Errorf("failed to remove machine user secret: %v", err) + } + if err := d.Set(clientIDVar, ""); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientIDVar, err) + } + if err := d.Set(clientSecretVar, ""); err != nil { + return diag.Errorf("failed to set %s of user: %v", clientSecretVar, err) + } + } + } return nil } diff --git a/zitadel/machine_user/resource.go b/zitadel/machine_user/resource.go index 208d97de..1cff76a0 100644 --- a/zitadel/machine_user/resource.go +++ b/zitadel/machine_user/resource.go @@ -60,11 +60,34 @@ func GetResource() *schema.Resource { }, Default: defaultAccessTokenType, }, + WithSecretVar: { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Generate machine secret, only applicable if creation or change from false", + }, + clientIDVar: { + Type: schema.TypeString, + Computed: true, + Description: "Value of the client ID if withSecret is true", + Sensitive: true, + }, + clientSecretVar: { + Type: schema.TypeString, + Computed: true, + Description: "Value of the client secret if withSecret is true", + Sensitive: true, + }, }, ReadContext: read, CreateContext: create, DeleteContext: delete, UpdateContext: update, - Importer: helper.ImportWithIDAndOptionalOrg(UserIDVar), + Importer: helper.ImportWithIDAndOptionalOrg( + UserIDVar, + helper.NewImportAttribute(WithSecretVar, helper.ConvertBool, false), + helper.NewImportAttribute(clientIDVar, helper.ConvertNonEmpty, true), + helper.NewImportAttribute(clientSecretVar, helper.ConvertNonEmpty, true), + ), } } diff --git a/zitadel/machine_user/resource_test.go b/zitadel/machine_user/resource_test.go index 42d484fd..79594b17 100644 --- a/zitadel/machine_user/resource_test.go +++ b/zitadel/machine_user/resource_test.go @@ -2,6 +2,7 @@ package machine_user_test import ( "fmt" + "strconv" "strings" "testing" @@ -33,6 +34,9 @@ func TestAccMachineUser(t *testing.T) { test_utils.CheckIsNotFoundFromPropertyCheck(checkRemoteProperty(frame), ""), test_utils.ChainImportStateIdFuncs( test_utils.ImportResourceId(frame.BaseTestFrame), + func(state *terraform.State) (string, error) { + return strconv.FormatBool(test_utils.AttributeValue(t, machine_user.WithSecretVar, exampleAttributes).True()), nil + }, test_utils.ImportOrgId(frame), ), )