From f58a751ee13161b33bac50e1191849035532b28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Gergely?= Date: Thu, 6 Nov 2025 11:12:01 +0100 Subject: [PATCH] CDPCP-15624 - environment deployment fails if freeipa.architecture is not given --- .github/workflows/test.yml | 4 +- .gitignore | 1 + .golangci.yml | 59 +++++++++++-------- resources/datahub/utils.go | 2 +- resources/datalake/resource_aws_datalake.go | 2 +- resources/datalake/resource_azure_datalake.go | 2 +- resources/datalake/resource_gcp_datalake.go | 2 +- resources/dw/cluster/aws/resource_cluster.go | 4 +- .../dw/databasecatalog/resource_catalog.go | 2 +- resources/environments/converter_freeipa.go | 2 +- .../environments/resource_aws_environment.go | 2 +- .../resource_azure_environment.go | 2 +- .../environments/resource_gcp_environment.go | 2 +- resources/environments/resource_user_sync.go | 2 +- resources/environments/utils.go | 9 +++ resources/environments/utils_test.go | 33 +++++++++++ resources/opdb/resource_database.go | 6 +- resources/opdb/utils.go | 2 +- 18 files changed, 96 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c74ecb5a..4a0619a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: terraform_wrapper: false - name: Run linters - uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0 + uses: golangci/golangci-lint-action@v8.0.0 with: args: --timeout 3m @@ -67,7 +67,7 @@ jobs: with: go-version-file: 'go.mod' - name: Run linters - uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0 + uses: golangci/golangci-lint-action@v8.0.0 with: args: --timeout 3m diff --git a/.gitignore b/.gitignore index 112a268b..dd8834e2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ workspace.xml **/.terraform **/terraform.tfstate **/terraform.tfstate.backup +**/.terraform.tfstate.lock.info # Coverage related report file(s) coverage.* diff --git a/.golangci.yml b/.golangci.yml index cea55da8..de850db6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,36 +1,47 @@ ---- +version: "2" run: tests: true - +output: + formats: + text: + path: stdout + print-issued-lines: true linters: - disable-all: true + default: none enable: - errcheck - - gosimple - govet - ineffassign - staticcheck - unused - - gci - -linters-settings: - gci: - sections: - - standard # Standard section: captures all standard packages. - - default # Default section: contains all imports that could not be matched to another section type. - - prefix(github.com/cloudera/terraform-provider-cdp) # Custom section: groups all imports with the specified Prefix. - - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. - - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. - # Skip generated files. - # Default: true - skip-generated: true - -output: - formats: - - format: colored-line-number - path: stdout - print-issued-lines: true - + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 +formatters: + enable: + - gci + settings: + gci: + sections: + - standard + - default + - prefix(github.com/cloudera/terraform-provider-cdp) + - blank + - dot + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/resources/datahub/utils.go b/resources/datahub/utils.go index b7b5169f..74cb38ae 100644 --- a/resources/datahub/utils.go +++ b/resources/datahub/utils.go @@ -19,7 +19,7 @@ import ( func checkIfClusterCreationFailed(resp *operations.DescribeClusterOK) (interface{}, string, error) { if utils.ContainsAsSubstring(failedStatusKeywords, resp.GetPayload().Cluster.Status) { - return nil, "", fmt.Errorf("Cluster status became unacceptable: %s", resp.GetPayload().Cluster.Status) + return nil, "", fmt.Errorf("cluster status became unacceptable: %s", resp.GetPayload().Cluster.Status) } return resp, resp.GetPayload().Cluster.Status, nil } diff --git a/resources/datalake/resource_aws_datalake.go b/resources/datalake/resource_aws_datalake.go index 98e0b056..1d6cd5ff 100644 --- a/resources/datalake/resource_aws_datalake.go +++ b/resources/datalake/resource_aws_datalake.go @@ -140,7 +140,7 @@ func (r *awsDatalakeResource) Create(ctx context.Context, req resource.CreateReq return } - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { stateSaver := func(dlDtl *datalakemodels.DatalakeDetails) { datalakeDetailsToAwsDatalakeResourceModel(ctx, dlDtl, &state, state.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, state) diff --git a/resources/datalake/resource_azure_datalake.go b/resources/datalake/resource_azure_datalake.go index af98872f..7b1fbd56 100644 --- a/resources/datalake/resource_azure_datalake.go +++ b/resources/datalake/resource_azure_datalake.go @@ -143,7 +143,7 @@ func (r *azureDatalakeResource) Create(ctx context.Context, req resource.CreateR return } - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { stateSaver := func(dlDtl *datalakemodels.DatalakeDetails) { datalakeDetailsToAzureDatalakeResourceModel(ctx, dlDtl, &state, state.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, state) diff --git a/resources/datalake/resource_gcp_datalake.go b/resources/datalake/resource_gcp_datalake.go index 82616c5b..26b1217e 100644 --- a/resources/datalake/resource_gcp_datalake.go +++ b/resources/datalake/resource_gcp_datalake.go @@ -87,7 +87,7 @@ func (r *gcpDatalakeResource) Create(ctx context.Context, req resource.CreateReq return } - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { stateSaver := func(dlDtl *datalakemodels.DatalakeDetails) { datalakeDetailsToGcpDatalakeResourceModel(ctx, dlDtl, &state, state.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, state) diff --git a/resources/dw/cluster/aws/resource_cluster.go b/resources/dw/cluster/aws/resource_cluster.go index ad237d9a..ae32488b 100644 --- a/resources/dw/cluster/aws/resource_cluster.go +++ b/resources/dw/cluster/aws/resource_cluster.go @@ -111,7 +111,7 @@ func (r *dwClusterResource) Create(ctx context.Context, req resource.CreateReque return } - if !(plan.PollingOptions != nil && plan.PollingOptions.Async.ValueBool()) { + if plan.PollingOptions == nil || !plan.PollingOptions.Async.ValueBool() { callFailedCount := 0 stateConf := &retry.StateChangeConf{ Pending: []string{"Accepted", "Creating", "Created", "Starting"}, @@ -168,7 +168,7 @@ func (r *dwClusterResource) Delete(ctx context.Context, req resource.DeleteReque return } - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { callFailedCount := 0 stateConf := &retry.StateChangeConf{ Pending: []string{"Deleting", "Running"}, diff --git a/resources/dw/databasecatalog/resource_catalog.go b/resources/dw/databasecatalog/resource_catalog.go index ee13e993..5b599816 100644 --- a/resources/dw/databasecatalog/resource_catalog.go +++ b/resources/dw/databasecatalog/resource_catalog.go @@ -68,7 +68,7 @@ func (r *dwDatabaseCatalogResource) Create(ctx context.Context, req resource.Cre clusterID := plan.ClusterID.ValueStringPointer() - if opts := plan.PollingOptions; !(opts != nil && opts.Async.ValueBool()) { + if opts := plan.PollingOptions; opts == nil || !opts.Async.ValueBool() { callFailedCount := 0 stateConf := &retry.StateChangeConf{ Pending: []string{"Accepted", "Creating", "Created", "Loading", "Starting"}, diff --git a/resources/environments/converter_freeipa.go b/resources/environments/converter_freeipa.go index 4acee078..02bdb6b4 100644 --- a/resources/environments/converter_freeipa.go +++ b/resources/environments/converter_freeipa.go @@ -88,7 +88,7 @@ func FreeIpaResponseToModel(ipaResp *environmentsmodels.FreeipaDetails, model *t Instances: ipaInstances, MultiAz: types.BoolValue(ipaResp.MultiAz), Recipes: recipes, - Architecture: types.StringValue(freeIpaDetails.Architecture.ValueString()), + Architecture: getStringValueIfNotEmpty(freeIpaDetails.Architecture.ValueString()), }) diags.Append(ipaDiags...) diff --git a/resources/environments/resource_aws_environment.go b/resources/environments/resource_aws_environment.go index dfae0ad9..94a25b5e 100644 --- a/resources/environments/resource_aws_environment.go +++ b/resources/environments/resource_aws_environment.go @@ -97,7 +97,7 @@ func (r *awsEnvironmentResource) Create(ctx context.Context, req resource.Create if err != nil { return } - if !(data.PollingOptions != nil && data.PollingOptions.Async.ValueBool()) { + if data.PollingOptions == nil || !data.PollingOptions.Async.ValueBool() { stateSaver := func(env *environmentsmodels.Environment) { toAwsEnvironmentResource(ctx, utils.LogEnvironmentSilently(ctx, env, describeLogPrefix), &data, data.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, data) diff --git a/resources/environments/resource_azure_environment.go b/resources/environments/resource_azure_environment.go index 9bb13f4f..20f4ff45 100644 --- a/resources/environments/resource_azure_environment.go +++ b/resources/environments/resource_azure_environment.go @@ -98,7 +98,7 @@ func (r *azureEnvironmentResource) Create(ctx context.Context, req resource.Crea if err != nil { return } - if !(data.PollingOptions != nil && data.PollingOptions.Async.ValueBool()) { + if data.PollingOptions == nil || !data.PollingOptions.Async.ValueBool() { stateSaver := func(env *environmentsmodels.Environment) { toAzureEnvironmentResource(ctx, utils.LogEnvironmentSilently(ctx, env, describeLogPrefix), &data, data.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, data) diff --git a/resources/environments/resource_gcp_environment.go b/resources/environments/resource_gcp_environment.go index eac80971..e601490a 100644 --- a/resources/environments/resource_gcp_environment.go +++ b/resources/environments/resource_gcp_environment.go @@ -96,7 +96,7 @@ func (r *gcpEnvironmentResource) Create(ctx context.Context, req resource.Create if err != nil { return } - if !(data.PollingOptions != nil && data.PollingOptions.Async.ValueBool()) { + if data.PollingOptions == nil || !data.PollingOptions.Async.ValueBool() { stateSaver := func(env *environmentsmodels.Environment) { toGcpEnvironmentResource(ctx, utils.LogEnvironmentSilently(ctx, env, describeLogPrefix), &data, data.PollingOptions, &resp.Diagnostics) diags = resp.State.Set(ctx, data) diff --git a/resources/environments/resource_user_sync.go b/resources/environments/resource_user_sync.go index b6a6eef8..33f5386f 100644 --- a/resources/environments/resource_user_sync.go +++ b/resources/environments/resource_user_sync.go @@ -151,7 +151,7 @@ func (r *userSyncResource) Create(ctx context.Context, req resource.CreateReques opID := res.Payload.OperationID tflog.Debug(ctx, fmt.Sprintf("User sync operation ID: %s", *opID)) - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { tflog.Debug(ctx, "User sync polling starts") err = waitForUserSync(*opID, time.Hour*1, callFailureThreshold, r.client.Environments, ctx, state.PollingOptions) if err != nil { diff --git a/resources/environments/utils.go b/resources/environments/utils.go index 5b060adf..a0f41e17 100644 --- a/resources/environments/utils.go +++ b/resources/environments/utils.go @@ -14,6 +14,7 @@ import ( "context" "fmt" "math" + "strings" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -71,3 +72,11 @@ func safeIntToInt32(n int) (int32, error) { } return int32(n), nil } + +func getStringValueIfNotEmpty(s string) types.String { + val := strings.TrimSpace(s) + if len(val) > 0 { + return types.StringValue(val) + } + return types.StringNull() +} diff --git a/resources/environments/utils_test.go b/resources/environments/utils_test.go index e84af983..2877e9ea 100644 --- a/resources/environments/utils_test.go +++ b/resources/environments/utils_test.go @@ -31,6 +31,39 @@ func TestConvertTagsWhenInputIsEmpty(t *testing.T) { } } +func TestGetStringValueIfNotEmpty(t *testing.T) { + tests := []struct { + name string + input string + expected types.String + }{ + { + name: "Empty string", + input: "", + expected: types.StringNull(), + }, + { + name: "Normal content", + input: "arm", + expected: types.StringValue("arm"), + }, + { + name: "String with spaces", + input: " ", + expected: types.StringNull(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getStringValueIfNotEmpty(tt.input) + if result != tt.expected { + t.Errorf("Unexpected result for input '%s'. Expected: %v, got: %v", tt.input, tt.expected, result) + } + }) + } +} + func TestConvertTagsWhenInputIsNotEmpty(t *testing.T) { key, value := "someKey-1", "someValue-1" inMap, _ := types.MapValue(types.StringType, map[string]attr.Value{key: types.StringValue(value)}) diff --git a/resources/opdb/resource_database.go b/resources/opdb/resource_database.go index 7d93314d..86343700 100644 --- a/resources/opdb/resource_database.go +++ b/resources/opdb/resource_database.go @@ -83,7 +83,7 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques return } - if !(data.PollingOptions != nil && data.PollingOptions.Async.ValueBool()) { + if data.PollingOptions == nil || !data.PollingOptions.Async.ValueBool() { status, err := waitForToBeAvailable(data.DatabaseName.ValueString(), data.Environment.ValueString(), r.client.Opdb, ctx, data.PollingOptions) tflog.Debug(ctx, fmt.Sprintf("Database polling finished, setting status from '%s' to '%s'", data.Status.ValueString(), status)) data.Status = types.StringValue(status) @@ -189,7 +189,7 @@ func (r *databaseResource) Update(ctx context.Context, req resource.UpdateReques return } - if !(data.PollingOptions != nil && data.PollingOptions.Async.ValueBool()) { + if data.PollingOptions == nil || !data.PollingOptions.Async.ValueBool() { status, err := waitForToBeAvailable(data.DatabaseName.ValueString(), data.Environment.ValueString(), r.client.Opdb, ctx, data.PollingOptions) tflog.Debug(ctx, fmt.Sprintf("Database polling finished, setting status from '%s' to '%s'", data.Status.ValueString(), status)) data.Status = types.StringValue(status) @@ -230,7 +230,7 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques return } - if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) { + if state.PollingOptions == nil || !state.PollingOptions.Async.ValueBool() { err = waitForToBeDeleted(state.DatabaseName.ValueString(), state.Environment.ValueString(), r.client.Opdb, ctx, state.PollingOptions) if err != nil { diff --git a/resources/opdb/utils.go b/resources/opdb/utils.go index 253add82..d583415d 100644 --- a/resources/opdb/utils.go +++ b/resources/opdb/utils.go @@ -21,7 +21,7 @@ import ( func checkIfDatabaseCreationFailed(resp *operations.DescribeDatabaseOK) (interface{}, string, error) { if utils.ContainsAsSubstring(failedStatusKeywords, string(resp.GetPayload().DatabaseDetails.Status)) { - return nil, "", fmt.Errorf("Cluster status became unacceptable: %s", types.StringValue(string(resp.GetPayload().DatabaseDetails.Status))) + return nil, "", fmt.Errorf("cluster status became unacceptable: %s", types.StringValue(string(resp.GetPayload().DatabaseDetails.Status))) } return resp, string(resp.GetPayload().DatabaseDetails.Status), nil }