From 5d817455b7f4b01654d1bb9eafc33a735aa0d801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Gergely?= <16577123+gregito@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:36:46 +0100 Subject: [PATCH] CDPCP-13058 - Allow in-place update of AWS credential (#176) --- .golangci.yml | 2 +- docs/resources/dw_database_catalog.md | 42 +++++++ docs/resources/environments_aws_credential.md | 2 + .../resources/environments_aws_environment.md | 1 + .../environments_azure_environment.md | 1 + .../resources/environments_gcp_environment.md | 1 + .../environments/model_aws_credential.go | 23 ++++ .../environments/resource_aws_credential.go | 109 ++++++++++-------- .../environments/schema_aws_credential.go | 68 +++++++++++ 9 files changed, 198 insertions(+), 51 deletions(-) create mode 100644 docs/resources/dw_database_catalog.md create mode 100644 resources/environments/model_aws_credential.go create mode 100644 resources/environments/schema_aws_credential.go diff --git a/.golangci.yml b/.golangci.yml index 6f54be8a..f80f8ffc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,7 +27,7 @@ linters-settings: output: format: colored-line-number - print-issued-lines: false + print-issued-lines: true issues: max-issues-per-linter: 0 diff --git a/docs/resources/dw_database_catalog.md b/docs/resources/dw_database_catalog.md new file mode 100644 index 00000000..3a2c8eb1 --- /dev/null +++ b/docs/resources/dw_database_catalog.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cdp_dw_database_catalog Resource - terraform-provider-cdp" +subcategory: "" +description: |- + Creates an AWS Data Warehouse database catalog. +--- + +# cdp_dw_database_catalog (Resource) + +Creates an AWS Data Warehouse database catalog. + + + + +## Schema + +### Required + +- `cluster_id` (String) The unique identifier of the cluster. + +### Optional + +- `polling_options` (Attributes) Polling related configuration options that could specify various values that will be used during CDP resource creation. (see [below for nested schema](#nestedatt--polling_options)) + +### Read-Only + +- `id` (String) The ID of this resource. +- `last_updated` (String) Timestamp of the last Terraform update of the order. +- `name` (String) The name of the database catalog. +- `status` (String) The status of the database catalog. + + +### Nested Schema for `polling_options` + +Optional: + +- `async` (Boolean) Boolean value that specifies if Terraform should wait for resource creation/deletion. +- `call_failure_threshold` (Number) Threshold value that specifies how many times should a single call failure happen before giving up the polling. +- `polling_timeout` (Number) Timeout value in minutes that specifies for how long should the polling go for resource creation/deletion. + + diff --git a/docs/resources/environments_aws_credential.md b/docs/resources/environments_aws_credential.md index 88a5b2a0..08f888f7 100644 --- a/docs/resources/environments_aws_credential.md +++ b/docs/resources/environments_aws_credential.md @@ -52,6 +52,8 @@ output "crn" { ### Optional - `description` (String) +- `skip_org_policy_decisions` (Boolean) Whether to skip organizational policy decision checks or not. +- `verify_permissions` (Boolean) Whether to verify permissions upon saving or not. ### Read-Only diff --git a/docs/resources/environments_aws_environment.md b/docs/resources/environments_aws_environment.md index cf3069d5..bc831bfb 100644 --- a/docs/resources/environments_aws_environment.md +++ b/docs/resources/environments_aws_environment.md @@ -68,6 +68,7 @@ output "crn" { ### Optional +- `cascading_delete` (Boolean) - `create_private_subnets` (Boolean) - `create_service_endpoints` (Boolean) - `description` (String) diff --git a/docs/resources/environments_azure_environment.md b/docs/resources/environments_azure_environment.md index 76694bf2..5e9718cc 100644 --- a/docs/resources/environments_azure_environment.md +++ b/docs/resources/environments_azure_environment.md @@ -79,6 +79,7 @@ output "crn" { ### Optional +- `cascading_delete` (Boolean) - `create_private_endpoints` (Boolean) - `description` (String) - `enable_outbound_load_balancer` (Boolean) diff --git a/docs/resources/environments_gcp_environment.md b/docs/resources/environments_gcp_environment.md index 22af7b76..bb204b38 100644 --- a/docs/resources/environments_gcp_environment.md +++ b/docs/resources/environments_gcp_environment.md @@ -87,6 +87,7 @@ output "shared_project_id" { ### Optional - `availability_zones` (List of String) The zones of the environment in the given region. Multi-zone selection is not supported in GCP yet. It accepts only one zone until support is added. +- `cascading_delete` (Boolean) - `description` (String) A description of the environment. - `enable_tunnel` (Boolean) Whether to enable SSH tunneling for the environment. - `encryption_key` (String) Key Resource ID of the customer managed encryption key to encrypt GCP resources. diff --git a/resources/environments/model_aws_credential.go b/resources/environments/model_aws_credential.go new file mode 100644 index 00000000..1721de27 --- /dev/null +++ b/resources/environments/model_aws_credential.go @@ -0,0 +1,23 @@ +// Copyright 2024 Cloudera. All Rights Reserved. +// +// This file is licensed under the Apache License Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +// +// This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied. Refer to the License for the specific +// permissions and limitations governing your use of the file. + +package environments + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type awsCredentialResourceModel struct { + ID types.String `tfsdk:"id"` + Crn types.String `tfsdk:"crn"` + RoleArn types.String `tfsdk:"role_arn"` + Description types.String `tfsdk:"description"` + CredentialName types.String `tfsdk:"credential_name"` + VerifyPermissions types.Bool `tfsdk:"verify_permissions"` + SkipOrgPolicyDecisions types.Bool `tfsdk:"skip_org_policy_decisions"` +} diff --git a/resources/environments/resource_aws_credential.go b/resources/environments/resource_aws_credential.go index 43c80a22..867f589c 100644 --- a/resources/environments/resource_aws_credential.go +++ b/resources/environments/resource_aws_credential.go @@ -12,17 +12,17 @@ package environments import ( "context" + "errors" "time" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/cdp" + environmentsclient "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/client" "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/client/operations" environmentsmodels "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/models" "github.com/cloudera/terraform-provider-cdp/utils" @@ -42,14 +42,6 @@ type awsCredentialResource struct { client *cdp.Client } -type awsCredentialResourceModel struct { - ID types.String `tfsdk:"id"` - CredentialName types.String `tfsdk:"credential_name"` - RoleArn types.String `tfsdk:"role_arn"` - Crn types.String `tfsdk:"crn"` - Description types.String `tfsdk:"description"` -} - func NewAwsCredentialResource() resource.Resource { return &awsCredentialResource{} } @@ -58,44 +50,6 @@ func (r *awsCredentialResource) Metadata(_ context.Context, req resource.Metadat resp.TypeName = req.ProviderTypeName + "_environments_aws_credential" } -func (r *awsCredentialResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "The AWS credential is used for authorization to provision resources such as compute instances within your cloud provider account.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "credential_name": schema.StringAttribute{ - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "role_arn": schema.StringAttribute{ - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "description": schema.StringAttribute{ - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - "crn": schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - } -} - func (r *awsCredentialResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { r.client = utils.GetCdpClientForResource(req, resp) } @@ -137,6 +91,17 @@ func (r *awsCredentialResource) Create(ctx context.Context, req resource.CreateR return } + // AWS credential creation does not support a couple of fields to be set during creation but can be + // updated afterward thus if they are set in the plan, we need to update the credential right away + if credentialNeedsToBeUpdatedAfterCreation(plan) { + tflog.Debug(ctx, "Updating AWS credential required due to plan configuration") + updateErr := r.updateCredential(ctx, client, plan) + if updateErr != nil { + utils.AddEnvironmentDiagnosticsError(updateErr, &resp.Diagnostics, "update AWS Credential") + return + } + } + // Save plan into Terraform state diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -201,7 +166,22 @@ func FindCredentialByName(ctx context.Context, cdpClient *cdp.Client, credential } func (r *awsCredentialResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - // There is no UpdateAWSCredential API in CDP. + var plan awsCredentialResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client := r.client.Environments + + err := r.updateCredential(ctx, client, plan) + + if err != nil { + utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update AWS Credential") + return + } + resp.State.Set(ctx, plan) } func (r *awsCredentialResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { @@ -226,3 +206,32 @@ func (r *awsCredentialResource) Delete(ctx context.Context, req resource.DeleteR func (r *awsCredentialResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } + +func (r *awsCredentialResource) updateCredential(ctx context.Context, client *environmentsclient.Environments, plan awsCredentialResourceModel) error { + params := operations.NewUpdateAwsCredentialParamsWithContext(ctx) + params.WithInput(&environmentsmodels.UpdateAwsCredentialRequest{ + RoleArn: plan.RoleArn.ValueStringPointer(), + Description: plan.Description.ValueString(), + CredentialName: plan.CredentialName.ValueStringPointer(), + VerifyPermissions: plan.VerifyPermissions.ValueBoolPointer(), + SkipOrgPolicyDecisions: plan.SkipOrgPolicyDecisions.ValueBoolPointer(), + }) + return retry.RetryContext(ctx, credentialCreateRetryDuration, func() *retry.RetryError { + tflog.Debug(ctx, "Updating AWS credential") + _, err := client.Operations.UpdateAwsCredential(params) + if err != nil { + var envErr *operations.UpdateAwsCredentialDefault + if errors.As(err, &envErr) { + if utils.IsRetryableError(envErr.Code()) { + return retry.RetryableError(err) + } + } + return retry.NonRetryableError(err) + } + return nil + }) +} + +func credentialNeedsToBeUpdatedAfterCreation(plan awsCredentialResourceModel) bool { + return (!plan.SkipOrgPolicyDecisions.IsNull() && plan.SkipOrgPolicyDecisions.ValueBool()) || (!plan.VerifyPermissions.IsNull() || plan.VerifyPermissions.ValueBool()) +} diff --git a/resources/environments/schema_aws_credential.go b/resources/environments/schema_aws_credential.go new file mode 100644 index 00000000..9f2a8dfc --- /dev/null +++ b/resources/environments/schema_aws_credential.go @@ -0,0 +1,68 @@ +// Copyright 2024 Cloudera. All Rights Reserved. +// +// This file is licensed under the Apache License Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +// +// This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied. Refer to the License for the specific +// permissions and limitations governing your use of the file. + +package environments + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +func (r *awsCredentialResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The AWS credential is used for authorization to provision resources such as compute instances within your cloud provider account.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "credential_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "role_arn": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "skip_org_policy_decisions": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "Whether to skip organizational policy decision checks or not.", + }, + "verify_permissions": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "Whether to verify permissions upon saving or not.", + }, + "crn": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +}