From 0ad741aa0fc323c01e75ea1112128f5c5bddf572 Mon Sep 17 00:00:00 2001 From: Grace Rehn Date: Mon, 2 Dec 2024 11:12:08 +1000 Subject: [PATCH] feat: Add support for generic oidc accounts --- .../import.sh | 1 + .../resource.tf | 7 ++ octopusdeploy/provider.go | 1 + .../resource_generic_oidc_account.go | 95 +++++++++++++++ .../resource_generic_oidc_account_test.go | 78 +++++++++++++ octopusdeploy/schema_generic_oidc_account.go | 110 ++++++++++++++++++ octopusdeploy/schema_queries.go | 3 +- octopusdeploy/schema_utilities.go | 3 +- 8 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 examples/resources/octopusdeploy_generic_openid_connect_account/import.sh create mode 100644 examples/resources/octopusdeploy_generic_openid_connect_account/resource.tf create mode 100644 octopusdeploy/resource_generic_oidc_account.go create mode 100644 octopusdeploy/resource_generic_oidc_account_test.go create mode 100644 octopusdeploy/schema_generic_oidc_account.go diff --git a/examples/resources/octopusdeploy_generic_openid_connect_account/import.sh b/examples/resources/octopusdeploy_generic_openid_connect_account/import.sh new file mode 100644 index 000000000..7691c5cdb --- /dev/null +++ b/examples/resources/octopusdeploy_generic_openid_connect_account/import.sh @@ -0,0 +1 @@ +terraform import [options] octopusdeploy_generic_openid_connect_account. \ No newline at end of file diff --git a/examples/resources/octopusdeploy_generic_openid_connect_account/resource.tf b/examples/resources/octopusdeploy_generic_openid_connect_account/resource.tf new file mode 100644 index 000000000..11893d6c3 --- /dev/null +++ b/examples/resources/octopusdeploy_generic_openid_connect_account/resource.tf @@ -0,0 +1,7 @@ +resource "octopusdeploy_azure_openid_connect" "example" { + name = "Generic OpenID Connect Account (OK to Delete)" + execution_subject_keys = ["space", "project"] + health_subject_keys = ["space", "target", "type"] + account_test_subject_keys = ["space", "type"] + audience = "api://Default" +} \ No newline at end of file diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index a1c1ba54f..a6e09f9e9 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -46,6 +46,7 @@ func Provider() *schema.Provider { "octopusdeploy_deployment_process": resourceDeploymentProcess(), "octopusdeploy_dynamic_worker_pool": resourceDynamicWorkerPool(), "octopusdeploy_gcp_account": resourceGoogleCloudPlatformAccount(), + "octopusdeploy_generic_openid_connect_account": resourceGenericOpenIDConnectAccount(), "octopusdeploy_kubernetes_agent_deployment_target": resourceKubernetesAgentDeploymentTarget(), "octopusdeploy_kubernetes_agent_worker": resourceKubernetesAgentWorker(), "octopusdeploy_kubernetes_cluster_deployment_target": resourceKubernetesClusterDeploymentTarget(), diff --git a/octopusdeploy/resource_generic_oidc_account.go b/octopusdeploy/resource_generic_oidc_account.go new file mode 100644 index 000000000..52a7fdb02 --- /dev/null +++ b/octopusdeploy/resource_generic_oidc_account.go @@ -0,0 +1,95 @@ +package octopusdeploy + +import ( + "context" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "log" +) + +func resourceGenericOpenIDConnectAccount() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGenericOpenIDConnectAccountCreate, + DeleteContext: resourceGenericOpenIDConnectAccountDelete, + Description: "This resource manages Generic OpenID Connect accounts in Octopus Deploy.", + Importer: getImporter(), + ReadContext: resourceGenericOpenIDConnectAccountRead, + Schema: getGenericOpenIdConnectAccountSchema(), + UpdateContext: resourceGenericOpenIDConnectAccountUpdate, + } +} + +func resourceGenericOpenIDConnectAccountCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + account := expandGenericOpenIDConnectAccount(d) + + log.Printf("[INFO] creating Generic OpenID Connect account: %#v", account) + + client := m.(*client.Client) + createdAccount, err := accounts.Add(client, account) + if err != nil { + return diag.FromErr(err) + } + + if err := setGenericOpenIDConnectAccount(ctx, d, createdAccount.(*accounts.GenericOIDCAccount)); err != nil { + return diag.FromErr(err) + } + + d.SetId(createdAccount.GetID()) + + log.Printf("[INFO] Generic OpenID Connect account created (%s)", d.Id()) + return nil +} + +func resourceGenericOpenIDConnectAccountDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[INFO] deleting Generic OpenID Connect account (%s)", d.Id()) + + client := m.(*client.Client) + if err := accounts.DeleteByID(client, d.Get("space_id").(string), d.Id()); err != nil { + return diag.FromErr(err) + } + + d.SetId("") + + log.Printf("[INFO] Generic OpenID Connect account deleted") + return nil +} + +func resourceGenericOpenIDConnectAccountRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[INFO] reading Generic OpenID Connect account (%s)", d.Id()) + + client := m.(*client.Client) + accountResource, err := accounts.GetByID(client, d.Get("space_id").(string), d.Id()) + if err != nil { + return errors.ProcessApiError(ctx, d, err, "Generic OpenID Connect account") + } + + genericOIDCAccount := accountResource.(*accounts.GenericOIDCAccount) + if err := setGenericOpenIDConnectAccount(ctx, d, genericOIDCAccount); err != nil { + return diag.FromErr(err) + } + + log.Printf("[INFO] Generic OpenID Connect account read (%s)", d.Id()) + return nil +} + +func resourceGenericOpenIDConnectAccountUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + account := expandGenericOpenIDConnectAccount(d) + + log.Printf("[INFO] updating Generic OpenID Connect account %#v", account) + + client := m.(*client.Client) + updatedAccount, err := accounts.Update(client, account) + if err != nil { + return diag.FromErr(err) + } + + if err := setGenericOpenIDConnectAccount(ctx, d, updatedAccount.(*accounts.GenericOIDCAccount)); err != nil { + return diag.FromErr(err) + } + + log.Printf("[INFO] Generic OpenID Connect account updated (%s)", d.Id()) + return nil +} diff --git a/octopusdeploy/resource_generic_oidc_account_test.go b/octopusdeploy/resource_generic_oidc_account_test.go new file mode 100644 index 000000000..2f88daca3 --- /dev/null +++ b/octopusdeploy/resource_generic_oidc_account_test.go @@ -0,0 +1,78 @@ +package octopusdeploy + +import ( + "fmt" + internalTest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test" + "testing" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccOctopusDeployGenericOpenIDConnectAccountBasic(t *testing.T) { + internalTest.SkipCI(t, "audience is not set on initial creation") + localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + prefix := "octopusdeploy_generic_openid_connect_account." + localName + + description := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + tenantedDeploymentMode := core.TenantedDeploymentModeTenantedOrUntenanted + + executionKeys := []string{"space"} + healthKeys := []string{"target"} + accountKeys := []string{"type"} + audience := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + + newDescription := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + + resource.Test(t, resource.TestCase{ + CheckDestroy: testAccountCheckDestroy, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: ProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Check: resource.ComposeTestCheckFunc( + testAccountExists(prefix), + resource.TestCheckResourceAttr(prefix, "description", description), + resource.TestCheckResourceAttr(prefix, "name", name), + resource.TestCheckResourceAttr(prefix, "tenanted_deployment_participation", string(tenantedDeploymentMode)), + resource.TestCheckResourceAttr(prefix, "execution_subject_keys.0", executionKeys[0]), + resource.TestCheckResourceAttr(prefix, "health_subject_keys.0", healthKeys[0]), + resource.TestCheckResourceAttr(prefix, "account_test_subject_keys.0", accountKeys[0]), + resource.TestCheckResourceAttr(prefix, "audience", audience), + ), + Config: testGenericOpenIDConnectAccountBasic(localName, name, description, tenantedDeploymentMode, executionKeys, healthKeys, accountKeys, audience), + }, + { + Check: resource.ComposeTestCheckFunc( + testAccountExists(prefix), + resource.TestCheckResourceAttr(prefix, "description", newDescription), + resource.TestCheckResourceAttr(prefix, "name", name), + resource.TestCheckResourceAttr(prefix, "tenanted_deployment_participation", string(tenantedDeploymentMode)), + resource.TestCheckResourceAttr(prefix, "execution_subject_keys.0", executionKeys[0]), + resource.TestCheckResourceAttr(prefix, "health_subject_keys.0", healthKeys[0]), + resource.TestCheckResourceAttr(prefix, "account_test_subject_keys.0", accountKeys[0]), + resource.TestCheckResourceAttr(prefix, "audience", audience), + ), + Config: testGenericOpenIDConnectAccountBasic(localName, name, newDescription, tenantedDeploymentMode, executionKeys, healthKeys, accountKeys, audience), + }, + }, + }) +} + +func testGenericOpenIDConnectAccountBasic(localName string, name string, description string, tenantedDeploymentParticipation core.TenantedDeploymentMode, execution_subject_keys []string, health_subject_keys []string, account_test_subject_keys []string, audience string) string { + return fmt.Sprintf(`resource "octopusdeploy_generic_openid_connect_account" "%s" { + description = "%s" + name = "%s" + tenanted_deployment_participation = "%s" + execution_subject_keys = %s + health_subject_keys = %s + account_test_subject_keys = %s + audience = "%s" + } + + data "octopusdeploy_accounts" "test" { + ids = [octopusdeploy_generic_openid_connect_account.%s.id] + }`, localName, description, name, tenantedDeploymentParticipation, StringArrayToTerraformArrayFormat(execution_subject_keys), StringArrayToTerraformArrayFormat(health_subject_keys), StringArrayToTerraformArrayFormat(account_test_subject_keys), audience, localName) +} diff --git a/octopusdeploy/schema_generic_oidc_account.go b/octopusdeploy/schema_generic_oidc_account.go new file mode 100644 index 000000000..ea4c34b85 --- /dev/null +++ b/octopusdeploy/schema_generic_oidc_account.go @@ -0,0 +1,110 @@ +package octopusdeploy + +import ( + "context" + "fmt" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func expandGenericOpenIDConnectAccount(d *schema.ResourceData) *accounts.GenericOIDCAccount { + name := d.Get("name").(string) + + account, _ := accounts.NewGenericOIDCAccount(name) + account.ID = d.Id() + + if v, ok := d.GetOk("description"); ok { + account.SetDescription(v.(string)) + } + + if v, ok := d.GetOk("environments"); ok { + account.EnvironmentIDs = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("name"); ok { + account.SetName(v.(string)) + } + + if v, ok := d.GetOk("space_id"); ok { + account.SetSpaceID(v.(string)) + } + + if v, ok := d.GetOk("tenanted_deployment_participation"); ok { + account.TenantedDeploymentMode = core.TenantedDeploymentMode(v.(string)) + } + + if v, ok := d.GetOk("tenant_tags"); ok { + account.TenantTags = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("tenants"); ok { + account.TenantIDs = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("execution_subject_keys"); ok { + account.DeploymentSubjectKeys = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("health_subject_keys"); ok { + account.HealthCheckSubjectKeys = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("account_test_subject_keys"); ok { + account.AccountTestSubjectKeys = getSliceFromTerraformTypeList(v) + } + + return account +} + +func getGenericOpenIdConnectAccountSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "description": getDescriptionSchema("Azure OpenID Connect account"), + "environments": getEnvironmentsSchema(), + "id": getIDSchema(), + "name": getNameSchema(true), + "space_id": getSpaceIDSchema(), + "tenanted_deployment_participation": getTenantedDeploymentSchema(), + "tenants": getTenantsSchema(), + "tenant_tags": getTenantTagsSchema(), + "execution_subject_keys": getSubjectKeysSchema(SchemaSubjectKeysDescriptionExecution), + "health_subject_keys": getSubjectKeysSchema(SchemaSubjectKeysDescriptionHealth), + "account_test_subject_keys": getSubjectKeysSchema(SchemaSubjectKeysDescriptionAccountTest), + "audience": getOidcAudienceSchema(), + } +} + +func setGenericOpenIDConnectAccount(ctx context.Context, d *schema.ResourceData, account *accounts.GenericOIDCAccount) error { + d.Set("description", account.GetDescription()) + d.Set("id", account.GetID()) + d.Set("name", account.GetName()) + d.Set("space_id", account.GetSpaceID()) + d.Set("tenanted_deployment_participation", account.GetTenantedDeploymentMode()) + d.Set("audience", account.Audience) + + if err := d.Set("environments", account.GetEnvironmentIDs()); err != nil { + return fmt.Errorf("error setting environments: %s", err) + } + + if err := d.Set("tenants", account.GetTenantIDs()); err != nil { + return fmt.Errorf("error setting tenants: %s", err) + } + + if err := d.Set("tenant_tags", account.TenantTags); err != nil { + return fmt.Errorf("error setting tenant_tags: %s", err) + } + + if err := d.Set("execution_subject_keys", account.DeploymentSubjectKeys); err != nil { + return fmt.Errorf("error setting execution_subject_keys: %s", err) + } + + if err := d.Set("health_subject_keys", account.HealthCheckSubjectKeys); err != nil { + return fmt.Errorf("error setting health_subject_keys: %s", err) + } + + if err := d.Set("account_test_subject_keys", account.AccountTestSubjectKeys); err != nil { + return fmt.Errorf("error setting account_test_subject_keys: %s", err) + } + + return nil +} diff --git a/octopusdeploy/schema_queries.go b/octopusdeploy/schema_queries.go index 1322c9311..651fc55d8 100644 --- a/octopusdeploy/schema_queries.go +++ b/octopusdeploy/schema_queries.go @@ -7,7 +7,7 @@ import ( func getQueryAccountType() *schema.Schema { return &schema.Schema{ - Description: "A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AmazonWebServicesOidcAccount`, `AzureServicePrincipal`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.", + Description: "A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AmazonWebServicesOidcAccount`, `AzureServicePrincipal`, `AzureSubscription`, `GenericOidcAccount`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.", Optional: true, Type: schema.TypeString, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ @@ -17,6 +17,7 @@ func getQueryAccountType() *schema.Schema { "AzureServicePrincipal", "AzureOIDC", "AzureSubscription", + "GenericOidcAccount", "None", "SshKeyPair", "Token", diff --git a/octopusdeploy/schema_utilities.go b/octopusdeploy/schema_utilities.go index 1cb481661..3b80f9098 100644 --- a/octopusdeploy/schema_utilities.go +++ b/octopusdeploy/schema_utilities.go @@ -9,7 +9,7 @@ import ( func getAccountTypeSchema(isRequired bool) *schema.Schema { schema := &schema.Schema{ - Description: "Specifies the type of the account. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureOIDC`, `AzureSubscription`, `AmazonWebServicesOidcAccount`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.", + Description: "Specifies the type of the account. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureOIDC`, `AzureSubscription`, `AmazonWebServicesOidcAccount`, `GenericOidcAccount`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.", ForceNew: true, Type: schema.TypeString, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ @@ -18,6 +18,7 @@ func getAccountTypeSchema(isRequired bool) *schema.Schema { "AzureServicePrincipal", "AzureOIDC", "AzureSubscription", + "GenericOidcAccount", "None", "SshKeyPair", "Token",