From 59e69cb91f323b0f2b2ad13f33e328de37e59a62 Mon Sep 17 00:00:00 2001 From: Ravina Dhruve <136399755+ravinadhruve10@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:07:09 -0700 Subject: [PATCH] fix(secure-onboarding) Fix resource update operation (#435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(secure-onboarding) Fix resource update operation The TF provider’s knowledge of cloud account object is older and different from the actual BE cloud account. This is because BE has modified the account object with fields like organization_id during org onboarding. Hence, during intended PUTs, TF overwrites these fields resulting in unexpected state. Fix summary: ------------ 1. Add and expose the organization_id in provider's account resource schema (as a computed field only). This is to let the customers know if their onboarded account is part of any org when they fetch the real existing infrastructure objects using GET calls. 2. During resource updates (PUT API calls) restrict and reject with error when the customer tries to update any non-updatable resource fields. 3. Minor refactoring for cleaner code. Testing done: --------------- Validated the scenarios :- - During tf apply again --> it returns and updates organization_id on the account. - No unnecessary PUTs - On triggering an intended PUT :- - restricts any non-updatable fields - PUT works as expected, doesn't oevrwrite organization_id to "" * Fix lint issue * Update docs with the missing attributes --- sysdig/common.go | 1 + ...source_sysdig_secure_cloud_auth_account.go | 75 +++++++++++++++---- ...e_sysdig_secure_cloud_auth_account_test.go | 33 ++++---- website/docs/r/secure_cloud_auth_account.md | 6 +- 4 files changed, 86 insertions(+), 29 deletions(-) diff --git a/sysdig/common.go b/sysdig/common.go index ea2f54d1..8b309470 100644 --- a/sysdig/common.go +++ b/sysdig/common.go @@ -59,4 +59,5 @@ const ( SchemaCloudProviderType = "provider_type" SchemaFeature = "feature" SchemaManagementAccountId = "management_account_id" + SchemaOrganizationIDKey = "organization_id" ) diff --git a/sysdig/resource_sysdig_secure_cloud_auth_account.go b/sysdig/resource_sysdig_secure_cloud_auth_account.go index 98a345bc..cb39335a 100644 --- a/sysdig/resource_sysdig_secure_cloud_auth_account.go +++ b/sysdig/resource_sysdig_secure_cloud_auth_account.go @@ -4,6 +4,7 @@ import ( "context" b64 "encoding/base64" "encoding/json" + "errors" "fmt" "reflect" "strings" @@ -153,6 +154,10 @@ func resourceSysdigSecureCloudauthAccount() *schema.Resource { Optional: true, Elem: accountComponents, }, + SchemaOrganizationIDKey: { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -173,6 +178,10 @@ func resourceSysdigSecureCloudauthAccountCreate(ctx context.Context, data *schem } data.SetId(cloudauthAccount.Id) + err = data.Set(SchemaOrganizationIDKey, cloudauthAccount.OrganizationId) + if err != nil { + return diag.FromErr(err) + } return nil } @@ -206,8 +215,23 @@ func resourceSysdigSecureCloudauthAccountUpdate(ctx context.Context, data *schem return diag.FromErr(err) } - _, errStatus, err := client.UpdateCloudauthAccountSecure(ctx, data.Id(), cloudauthAccountFromResourceData(data)) + existingCloudAccount, errStatus, err := client.GetCloudauthAccountSecure(ctx, data.Id()) + if err != nil { + if strings.Contains(errStatus, "404") { + return nil + } + return diag.FromErr(err) + } + + newCloudAccount := cloudauthAccountFromResourceData(data) + // validate and reject non-updatable resource schema fields upfront + err = validateCloudauthAccountUpdate(existingCloudAccount, newCloudAccount) + if err != nil { + return diag.FromErr(err) + } + + _, errStatus, err = client.UpdateCloudauthAccountSecure(ctx, data.Id(), newCloudAccount) if err != nil { if strings.Contains(errStatus, "404") { return nil @@ -236,6 +260,19 @@ func resourceSysdigSecureCloudauthAccountDelete(ctx context.Context, data *schem return nil } +/* +This function validates and restricts any fields not allowed to be updated during resource updates. +*/ +func validateCloudauthAccountUpdate(existingCloudAccount *v2.CloudauthAccountSecure, newCloudAccount *v2.CloudauthAccountSecure) error { + if existingCloudAccount.Enabled != newCloudAccount.Enabled || existingCloudAccount.Provider != newCloudAccount.Provider || + existingCloudAccount.ProviderId != newCloudAccount.ProviderId || existingCloudAccount.OrganizationId != newCloudAccount.OrganizationId { + errorInvalidResourceUpdate := fmt.Sprintf("Bad Request. Updating restricted fields not allowed: %s", []string{"enabled", "provider_type", "provider_id", "organization_id"}) + return errors.New(errorInvalidResourceUpdate) + } + + return nil +} + /* This function converts a schema set to map for iteration. */ @@ -439,11 +476,12 @@ func cloudauthAccountFromResourceData(data *schema.ResourceData) *v2.CloudauthAc return &v2.CloudauthAccountSecure{ CloudAccount: cloudauth.CloudAccount{ - Enabled: data.Get(SchemaEnabled).(bool), - ProviderId: data.Get(SchemaCloudProviderId).(string), - Provider: cloudauth.Provider(cloudauth.Provider_value[data.Get(SchemaCloudProviderType).(string)]), - Components: accountComponents, - Feature: accountFeatures, + Enabled: data.Get(SchemaEnabled).(bool), + OrganizationId: data.Get(SchemaOrganizationIDKey).(string), + ProviderId: data.Get(SchemaCloudProviderId).(string), + Provider: cloudauth.Provider(cloudauth.Provider_value[data.Get(SchemaCloudProviderType).(string)]), + Components: accountComponents, + Feature: accountFeatures, }, } } @@ -498,6 +536,19 @@ func featureToResourceData(features *cloudauth.AccountFeatures) []interface{} { return nil } +func componentsToResourceData(components []*cloudauth.AccountComponent) []map[string]interface{} { + componentsList := []map[string]interface{}{} + + for _, comp := range components { + componentsList = append(componentsList, map[string]interface{}{ + SchemaType: comp.Type.String(), + SchemaInstance: comp.Instance, + }) + } + + return componentsList +} + func cloudauthAccountToResourceData(data *schema.ResourceData, cloudAccount *v2.CloudauthAccountSecure) error { err := data.Set(SchemaIDKey, cloudAccount.Id) if err != nil { @@ -524,16 +575,12 @@ func cloudauthAccountToResourceData(data *schema.ResourceData, cloudAccount *v2. return err } - components := []map[string]interface{}{} - - for _, comp := range cloudAccount.Components { - components = append(components, map[string]interface{}{ - SchemaType: comp.Type.String(), - SchemaInstance: comp.Instance, - }) + err = data.Set(SchemaComponent, componentsToResourceData(cloudAccount.Components)) + if err != nil { + return err } - err = data.Set(SchemaComponent, components) + err = data.Set(SchemaOrganizationIDKey, cloudAccount.OrganizationId) if err != nil { return err } diff --git a/sysdig/resource_sysdig_secure_cloud_auth_account_test.go b/sysdig/resource_sysdig_secure_cloud_auth_account_test.go index 095cead4..f4218ac1 100644 --- a/sysdig/resource_sysdig_secure_cloud_auth_account_test.go +++ b/sysdig/resource_sysdig_secure_cloud_auth_account_test.go @@ -80,19 +80,6 @@ func TestAccSecureCloudAuthAccountFC(t *testing.T) { } func secureCloudAuthAccountWithFC(accountID string) string { - type sample_service_account_key struct { - ProjectId string `json:"project_id"` - PrivateKeyId string `json:"private_key_id"` - PrivateKey string `json:"private_key"` - } - test_service_account_key := &sample_service_account_key{ - ProjectId: fmt.Sprintf("sample-1-%s", accountID), - PrivateKeyId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - PrivateKey: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n", - } - test_service_account_keyJSON, _ := json.Marshal(test_service_account_key) - test_service_account_key_encoded := b64.StdEncoding.EncodeToString([]byte(string(test_service_account_keyJSON))) - return fmt.Sprintf(` resource "sysdig_secure_cloud_auth_account" "sample-1" { provider_id = "sample-1-%s" @@ -121,5 +108,23 @@ resource "sysdig_secure_cloud_auth_account" "sample-1" { ignore_changes = [component] } } -`, accountID, test_service_account_key_encoded) +`, accountID, getEncodedServiceAccountKey("sample-1", accountID)) +} + +func getEncodedServiceAccountKey(resourceName string, accountID string) string { + type sample_service_account_key struct { + Type string `json:"type"` + ProjectId string `json:"project_id"` + PrivateKeyId string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + } + test_service_account_key := &sample_service_account_key{ + Type: "service_account", + ProjectId: fmt.Sprintf("%s-%s", resourceName, accountID), + PrivateKeyId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + PrivateKey: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n", + } + test_service_account_key_bytes, _ := json.Marshal(test_service_account_key) + test_service_account_key_encoded := b64.StdEncoding.EncodeToString(test_service_account_key_bytes) + return test_service_account_key_encoded } diff --git a/website/docs/r/secure_cloud_auth_account.md b/website/docs/r/secure_cloud_auth_account.md index ab16887e..9c4fdc96 100644 --- a/website/docs/r/secure_cloud_auth_account.md +++ b/website/docs/r/secure_cloud_auth_account.md @@ -38,4 +38,8 @@ resource "sysdig_secure_cloud_auth_account" "sample" { ## Attributes Reference -No additional attributes are exported. +In addition to all arguments above, the following attributes are exported: + +* `id` - (Computed) The ID of the cloud account. + +* `organization_id` - (Computed) The ID of the organization, if the cloud account is part of any organization. \ No newline at end of file