Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AWS environments #177

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions examples/resources/cdp_environments_aws_environment/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,99 @@ output "environment_name" {
output "crn" {
value = cdp_environments_aws_environment.example.crn
}

output "status" {
value = cdp_environments_aws_environment.example.status
}

output "status_reason" {
value = cdp_environments_aws_environment.example.status_reason
}

output "cloud_platform" {
value = cdp_environments_aws_environment.example.region
}

output "security_access" {
value = cdp_environments_aws_environment.example.security_access
}

output "network_cidr" {
value = cdp_environments_aws_environment.example.network_cidr
}

output "authentication" {
value = cdp_environments_aws_environment.example.authentication
}

output "log_storage" {
value = cdp_environments_aws_environment.example.log_storage
}

output "proxy_config_name" {
value = cdp_environments_aws_environment.example.proxy_config_name
}

output "tags" {
value = cdp_environments_aws_environment.example.tags
}

output "create_private_subnets" {
value = cdp_environments_aws_environment.example.create_private_subnets
}

output "create_service_endpoints" {
value = cdp_environments_aws_environment.example.create_service_endpoints
}

output "s3_guard_table_name" {
value = cdp_environments_aws_environment.example.s3_guard_table_name
}

output "credential_name" {
value = cdp_environments_aws_environment.example.credential_name
}

output "description" {
value = cdp_environments_aws_environment.example.description
}

output "enable_tunnel" {
value = cdp_environments_aws_environment.example.enable_tunnel
}

output "encryption_key_arn" {
value = cdp_environments_aws_environment.example.encryption_key_arn
}

output "endpoint_access_gateway_scheme" {
value = cdp_environments_aws_environment.example.endpoint_access_gateway_scheme
}

output "endpoint_access_gateway_subnet_ids" {
value = cdp_environments_aws_environment.example.endpoint_access_gateway_subnet_ids
}

output "freeipa" {
value = cdp_environments_aws_environment.example.freeipa
}

output "report_deployment_logs" {
value = cdp_environments_aws_environment.example.report_deployment_logs
}

output "subnet_ids" {
value = cdp_environments_aws_environment.example.subnet_ids
}

output "tunnel_type" {
value = cdp_environments_aws_environment.example.tunnel_type
}

output "workload_analytics" {
value = cdp_environments_aws_environment.example.workload_analytics
}

output "vpc_id" {
value = cdp_environments_aws_environment.example.vpc_id
}
29 changes: 29 additions & 0 deletions resources/environments/environment_action_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"

"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 Down Expand Up @@ -106,3 +107,31 @@ func waitForCreateEnvironmentWithDiagnosticHandle(ctx context.Context, client *c
}
return descEnvResp.GetPayload().Environment, nil
}

func waitForStartEnvironmentWithDiagnosticHandle(ctx context.Context, client *environmentsclient.Environments, id string, envName string, resp *resource.UpdateResponse, options *utils.PollingOptions,
stateSaverCb func(*environmentsmodels.Environment)) (*environmentsmodels.Environment, error) {
if err := waitForEnvironmentToBeAvailable(id, timeoutOneHour, callFailureThreshold, client, ctx, options, stateSaverCb); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "starting Environment failed")
return nil, err
}

environmentName := envName
descParams := operations.NewDescribeEnvironmentParamsWithContext(ctx)
descParams.WithInput(&environmentsmodels.DescribeEnvironmentRequest{
EnvironmentName: &environmentName,
})
descEnvResp, err := client.Operations.DescribeEnvironment(descParams)
if err != nil {
if isEnvNotFoundError(err) {
resp.Diagnostics.AddWarning("Resource not found on provider", "Environment not found, removing from state.")
tflog.Warn(ctx, "Environment not found, removing from state", map[string]interface{}{
"id": id,
})
resp.State.RemoveResource(ctx)
return nil, err
}
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "starting Environment failed")
return nil, err
}
return descEnvResp.GetPayload().Environment, nil
}
2 changes: 2 additions & 0 deletions resources/environments/model_aws_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@ type SecurityAccess struct {
SecurityGroupIDForKnox types.String `tfsdk:"security_group_id_for_knox"`

SecurityGroupIDsForKnox types.Set `tfsdk:"security_group_ids_for_knox"`

GatewayNodeSecurityGroupID types.String `tfsdk:"gateway_node_security_group_id"`
}
57 changes: 56 additions & 1 deletion resources/environments/polling.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ func waitForEnvironmentToBeAvailable(environmentName string, fallbackTimeout tim
"ENVIRONMENT_RESOURCE_ENCRYPTION_INITIALIZATION_IN_PROGRESS",
"ENVIRONMENT_VALIDATION_IN_PROGRESS",
"ENVIRONMENT_INITIALIZATION_IN_PROGRESS",
"FREEIPA_CREATION_IN_PROGRESS"},
"FREEIPA_CREATION_IN_PROGRESS",
"START_DATALAKE_STARTED",
"START_DATAHUB_STARTED",
"START_SYNCHRONIZE_USERS_STARTED",
"START_FREEIPA_STARTED",
"ENV_STOPPED"},
Target: []string{"AVAILABLE"},
Delay: 5 * time.Second,
Timeout: *timeout,
Expand Down Expand Up @@ -151,6 +156,56 @@ func waitForEnvironmentToBeAvailable(environmentName string, fallbackTimeout tim
return err
}

func waitForEnvironmentToBeStopped(environmentName string, fallbackTimeout time.Duration, callFailureThresholdDefault int, client *client.Environments, ctx context.Context, pollingOptions *utils.PollingOptions) error {
timeout, err := utils.CalculateTimeoutOrDefault(ctx, pollingOptions, fallbackTimeout)
if err != nil {
return err
}
callFailureThreshold, failureThresholdError := utils.CalculateCallFailureThresholdOrDefault(ctx, pollingOptions, callFailureThresholdDefault)
if failureThresholdError != nil {
return failureThresholdError
}
callFailedCount := 0
stateConf := &retry.StateChangeConf{
Pending: []string{
"STOP_DATAHUB_STARTED",
"STOP_DATALAKE_STARTED",
"STOP_FREEIPA_STARTED",
"VERTICAL_SCALE_ON_FREEIPA_IN_PROGRESS",
},
Target: []string{"ENV_STOPPED"},
Delay: 5 * time.Second,
Timeout: *timeout,
PollInterval: 10 * time.Second,
Refresh: func() (interface{}, string, error) {
tflog.Debug(ctx, fmt.Sprintf("About to describe environment %s", environmentName))
params := operations.NewDescribeEnvironmentParamsWithContext(ctx)
params.WithInput(&environmentsmodels.DescribeEnvironmentRequest{EnvironmentName: &environmentName})
resp, err := client.Operations.DescribeEnvironment(params)
if err != nil {
if isEnvNotFoundError(err) {
tflog.Debug(ctx, fmt.Sprintf("Recoverable error describing environment: %s", err))
callFailedCount = 0
return nil, "", nil
}
callFailedCount++
if callFailedCount <= callFailureThreshold {
tflog.Warn(ctx, fmt.Sprintf("Error describing environment with call failure due to [%s] but threshold limit is not reached yet (%d out of %d).", err.Error(), callFailedCount, callFailureThreshold))
return nil, "", nil
}
tflog.Error(ctx, fmt.Sprintf("Error describing environment (due to: %s) and call failure threshold limit exceeded.", err))
return nil, "", err
}
callFailedCount = 0
tflog.Info(ctx, fmt.Sprintf("Described environment's status: %s", *resp.GetPayload().Environment.Status))
return checkResponseStatusForError(resp)
},
}
_, err = stateConf.WaitForStateContext(ctx)

return err
}

func checkResponseStatusForError(resp *operations.DescribeEnvironmentOK) (interface{}, string, error) {
if utils.ContainsAsSubstring([]string{"FAILED", "ERROR"}, *resp.GetPayload().Environment.Status) {
return nil, "", fmt.Errorf("unexpected Enviornment status: %s. Reason: %s", *resp.GetPayload().Environment.Status, resp.GetPayload().Environment.StatusReason)
Expand Down
127 changes: 122 additions & 5 deletions resources/environments/resource_aws_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ package environments

import (
"context"
"reflect"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

"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 Down Expand Up @@ -123,7 +125,25 @@ func (r *awsEnvironmentResource) Read(ctx context.Context, req resource.ReadRequ
}

func (r *awsEnvironmentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan awsEnvironmentResourceModel
var state awsEnvironmentResourceModel
planDiags := req.Plan.Get(ctx, &plan)
var stateDiags = req.State.Get(ctx, &state)
resp.Diagnostics.Append(planDiags...)
resp.Diagnostics.Append(stateDiags...)
if resp.Diagnostics.HasError() {
tflog.Error(ctx, "Got Error while trying to set plan")
return
}

updateEnvironment(ctx, &plan, &state, r.client.Environments, resp)
updateFreeIpa(ctx, &plan, &state, r.client.Environments, resp)

stateDiags = resp.State.Set(ctx, state)
if resp.Diagnostics.HasError() {
return
}
resp.State.Set(ctx, state)
}

func (r *awsEnvironmentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
Expand Down Expand Up @@ -211,11 +231,12 @@ func toAwsEnvironmentResource(ctx context.Context, env *environmentsmodels.Envir
sgIDsknox = model.SecurityAccess.SecurityGroupIDsForKnox
}
model.SecurityAccess = &SecurityAccess{
Cidr: types.StringValue(env.SecurityAccess.Cidr),
DefaultSecurityGroupID: types.StringValue(env.SecurityAccess.DefaultSecurityGroupID),
DefaultSecurityGroupIDs: dsgIDs,
SecurityGroupIDForKnox: types.StringValue(env.SecurityAccess.SecurityGroupIDForKnox),
SecurityGroupIDsForKnox: sgIDsknox,
Cidr: types.StringValue(env.SecurityAccess.Cidr),
DefaultSecurityGroupID: types.StringValue(env.SecurityAccess.DefaultSecurityGroupID),
DefaultSecurityGroupIDs: dsgIDs,
SecurityGroupIDForKnox: types.StringValue(env.SecurityAccess.SecurityGroupIDForKnox),
SecurityGroupIDsForKnox: sgIDsknox,
GatewayNodeSecurityGroupID: model.SecurityAccess.GatewayNodeSecurityGroupID,
}
}
model.Status = types.StringPointerValue(env.Status)
Expand All @@ -230,3 +251,99 @@ func toAwsEnvironmentResource(ctx context.Context, env *environmentsmodels.Envir
model.TunnelType = types.StringValue(string(env.TunnelType))
model.WorkloadAnalytics = types.BoolValue(env.WorkloadAnalytics)
}

func updateEnvironment(ctx context.Context, plan *awsEnvironmentResourceModel, state *awsEnvironmentResourceModel, client *environmentsclient.Environments, resp *resource.UpdateResponse) *resource.UpdateResponse {
if plan.CredentialName.ValueString() != state.CredentialName.ValueString() {
params := operations.NewChangeEnvironmentCredentialParamsWithContext(ctx)
params.WithInput(&environmentsmodels.ChangeEnvironmentCredentialRequest{
CredentialName: plan.CredentialName.ValueStringPointer(),
EnvironmentName: state.EnvironmentName.ValueStringPointer(),
})
_, err := client.Operations.ChangeEnvironmentCredential(params)
if err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "change AWS Environment credential")
return resp
}
}

if state.EncryptionKeyArn != plan.EncryptionKeyArn {
if err := updateAwsDiskEncryptionParameters(ctx, client, *plan); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update disk encryption parameters")
return resp
}
state.EncryptionKeyArn = plan.EncryptionKeyArn
}

if plan.Authentication != nil && !reflect.DeepEqual(plan.Authentication, state.Authentication) {
if err := updateSshKey(ctx, client, plan.Authentication, plan.EnvironmentName.ValueStringPointer()); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update SSH key")
return resp
}
state.Authentication = plan.Authentication
}

if !reflect.DeepEqual(utils.FromSetValueToStringList(plan.SubnetIds), utils.FromSetValueToStringList(state.SubnetIds)) ||
!reflect.DeepEqual(plan.EndpointAccessGatewaySubnetIds, state.EndpointAccessGatewaySubnetIds) {
if err := updateSubnet(ctx, client, *plan); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update subnet")
return resp
}
state.SubnetIds = plan.SubnetIds
state.EndpointAccessGatewaySubnetIds = plan.EndpointAccessGatewaySubnetIds
}

if plan.SecurityAccess != nil && !reflect.DeepEqual(plan.SecurityAccess, state.SecurityAccess) {
if err := updateSecurityAccess(ctx, client, *plan); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update security access")
return resp
}
state.SecurityAccess = plan.SecurityAccess
}

if !plan.Tags.IsNull() && !reflect.DeepEqual(plan.Tags, state.Tags) {
if err := updateTags(ctx, client, *plan); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update tags")
return resp
}
state.Tags = plan.Tags
}

if plan.ProxyConfigName != state.ProxyConfigName {
if err := updateProxyConfig(ctx, client, *plan); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "update proxy config")
return resp
}
state.ProxyConfigName = plan.ProxyConfigName
}
return resp
}

func startEnvironment(ctx context.Context, state *awsEnvironmentResourceModel, resp *resource.UpdateResponse, client *environmentsclient.Environments) error {
if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) {
stateSaver := func(env *environmentsmodels.Environment) {
toAwsEnvironmentResource(ctx, utils.LogEnvironmentSilently(ctx, env, describeLogPrefix), state, state.PollingOptions, &resp.Diagnostics)
}
_, err := waitForStartEnvironmentWithDiagnosticHandle(ctx, client, state.ID.ValueString(), state.EnvironmentName.ValueString(), resp, state.PollingOptions, stateSaver)
if err != nil {
return err
}
}
return nil
}

func stopAndWaitForEnvironment(ctx context.Context, environment string, pollingOptions *utils.PollingOptions, resp *resource.UpdateResponse, client *environmentsclient.Environments) error {
params := operations.NewStopEnvironmentParamsWithContext(ctx)
params.WithInput(&environmentsmodels.StopEnvironmentRequest{
EnvironmentName: &environment,
})
_, err := client.Operations.StopEnvironment(params)
if err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "stop Environment")
return err
}
if err := waitForEnvironmentToBeStopped(environment, timeoutOneHour, callFailureThreshold, client, ctx, pollingOptions); err != nil {
utils.AddEnvironmentDiagnosticsError(err, &resp.Diagnostics, "create Environment failed")
return err
}
return nil
}
Loading