Skip to content

Commit

Permalink
CDPCP-13058 - Allow in-place update of AWS credential (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
gregito authored Nov 13, 2024
1 parent f10863d commit 5d81745
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions docs/resources/dw_database_catalog.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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.

<a id="nestedatt--polling_options"></a>
### 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.


2 changes: 2 additions & 0 deletions docs/resources/environments_aws_credential.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/resources/environments_aws_environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ output "crn" {

### Optional

- `cascading_delete` (Boolean)
- `create_private_subnets` (Boolean)
- `create_service_endpoints` (Boolean)
- `description` (String)
Expand Down
1 change: 1 addition & 0 deletions docs/resources/environments_azure_environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ output "crn" {

### Optional

- `cascading_delete` (Boolean)
- `create_private_endpoints` (Boolean)
- `description` (String)
- `enable_outbound_load_balancer` (Boolean)
Expand Down
1 change: 1 addition & 0 deletions docs/resources/environments_gcp_environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions resources/environments/model_aws_credential.go
Original file line number Diff line number Diff line change
@@ -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"`
}
109 changes: 59 additions & 50 deletions resources/environments/resource_aws_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{}
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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) {
Expand All @@ -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())
}
68 changes: 68 additions & 0 deletions resources/environments/schema_aws_credential.go
Original file line number Diff line number Diff line change
@@ -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(),
},
},
},
}
}

0 comments on commit 5d81745

Please sign in to comment.