From 9043f1c35d422fca35656ba1b8e29d651891dff8 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 16 May 2024 10:27:53 +0530 Subject: [PATCH 01/35] dynamo-update tests --- .../resource_duplo_aws_dynamodb_table_v2.go | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 78f91f9d..9f8f15c4 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -541,13 +541,13 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat return diag.FromErr(err) } fallthrough - case shouldUpdateSSESepecification(existing, rq): - log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) - if err != nil { - return diag.FromErr(err) - } - fallthrough + // case !reflect.DeepEqual(existing.SSEDescription, rq.SSESpecification): //shouldUpdateSSESepecification(existing, rq): + // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) + // if err != nil { + // return diag.FromErr(err) + // } + // fallthrough case shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq): // SSESpecification & DeletionProtectionEnabled must be updated alone. // Passing these values with the rest of the update table request willcause @@ -561,7 +561,14 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat return diag.Errorf(e, tenantID, name, err) } } - + isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) + if isSSESUpdatable { + log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) + _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) + if err != nil { + return diag.FromErr(err) + } + } // Generate the ID for the resource and set it. id := fmt.Sprintf("%s/%s", tenantID, name) getResource := func() (interface{}, duplosdk.ClientError) { @@ -629,8 +636,8 @@ func expandDynamoDBTable(d *schema.ResourceData) (*duplosdk.DuploDynamoDBTableRe KeySchema: expandDynamoDBKeySchema(d), } - if v, ok := d.GetOk("deletion_protection_enabled"); ok { - state := v.(bool) + if d.HasChange("deletion_protection_enabled") { + state := d.Get("deletion_protection_enabled").(bool) req.DeletionProtectionEnabled = &state } @@ -1026,5 +1033,8 @@ func shouldUpdateSSESepecification( table *duplosdk.DuploDynamoDBTableV2, request *duplosdk.DuploDynamoDBTableRequestV2, ) bool { + if table.SSEDescription == nil && request.SSESpecification == nil { + return false + } return !reflect.DeepEqual(table.SSEDescription, request.SSESpecification) } From 50c96f737e3fa36ac2d52c02605ec458e091dd0f Mon Sep 17 00:00:00 2001 From: nikhil Date: Fri, 17 May 2024 16:05:53 +0530 Subject: [PATCH 02/35] in progress --- .../resource_duplo_aws_dynamodb_table_v2.go | 166 ++++++++++++------ duplosdk/aws_dynamo_db.go | 29 ++- duplosdk/client.go | 2 +- 3 files changed, 133 insertions(+), 64 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 9f8f15c4..72d094e0 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -521,55 +521,63 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } - - // Updating Point In Time Recovery status - isPITREnabled := existing.PointInTimeRecoveryStatus == "ENABLED" - targetPITRStatus := d.Get("is_point_in_time_recovery").(bool) - - switch { - case isPITREnabled != targetPITRStatus: - log.Printf("[INFO] Updating Point In Recovery for DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err = c.DynamoDBTableV2PointInRecovery(tenantID, fullname, targetPITRStatus) - if err != nil { - return diag.FromErr(err) - } - fallthrough - case rq.DeletionProtectionEnabled != nil && existing.DeletionProtectionEnabled != *rq.DeletionProtectionEnabled: - log.Printf("[INFO] Updating Deletion Protection for DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err := c.DuploDynamoDBTableV2UpdateDeletionProtection(tenantID, rq) - if err != nil { - return diag.FromErr(err) - } - fallthrough - // case !reflect.DeepEqual(existing.SSEDescription, rq.SSESpecification): //shouldUpdateSSESepecification(existing, rq): - // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) - // if err != nil { - // return diag.FromErr(err) - // } - // fallthrough - case shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq): - // SSESpecification & DeletionProtectionEnabled must be updated alone. - // Passing these values with the rest of the update table request willcause - // cause a error. (Per .NET AWS SDK@3.7) - rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil - - log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err = c.DynamoDBTableUpdateV2(tenantID, rq) - if err != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, name, err) - } - } - isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) - if isSSESUpdatable { - log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) - if err != nil { - return diag.FromErr(err) - } - } - // Generate the ID for the resource and set it. + diagErr := updatePointInTimeRecovery(c, d, tenantID, fullname) + if diagErr != nil { + return diagErr + } + + diagErr = updateDeleteProtection(c, d, tenantID, fullname) + if diagErr != nil { + return diagErr + } + diagErr = updateGlobalSecondaryIndex(c, d, tenantID, fullname) + if diagErr != nil { + return diagErr + } + //switch { + //case isPITREnabled != targetPITRStatus: + // log.Printf("[INFO] Updating Point In Recovery for DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err = c.DynamoDBTableV2PointInRecovery(tenantID, fullname, targetPITRStatus) + // if err != nil { + // return diag.FromErr(err) + // } + // fallthrough + //case rq.DeletionProtectionEnabled != nil && existing.DeletionProtectionEnabled != *rq.DeletionProtectionEnabled: + // log.Printf("[INFO] Updating Deletion Protection for DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err := c.DuploDynamoDBTableV2UpdateDeletionProtection(tenantID, rq) + // if err != nil { + // return diag.FromErr(err) + // } + // fallthrough + // // case !reflect.DeepEqual(existing.SSEDescription, rq.SSESpecification): //shouldUpdateSSESepecification(existing, rq): + // // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) + // // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) + // // if err != nil { + // // return diag.FromErr(err) + // // } + // // fallthrough + //case shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq): + // // SSESpecification & DeletionProtectionEnabled must be updated alone. + // // Passing these values with the rest of the update table request willcause + // // cause a error. (Per .NET AWS SDK@3.7) + // rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil + // + // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err = c.DynamoDBTableUpdateV2(tenantID, rq) + // if err != nil { + // e := "Error updating tenant %s DynamoDB table '%s': %s" + // return diag.Errorf(e, tenantID, name, err) + // } + //} + //isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) + //if isSSESUpdatable { + // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) + // if err != nil { + // return diag.FromErr(err) + // } + //} + //// Generate the ID for the resource and set it. id := fmt.Sprintf("%s/%s", tenantID, name) getResource := func() (interface{}, duplosdk.ClientError) { return c.DynamoDBTableGet(tenantID, fullname) @@ -597,6 +605,61 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat return diags } +func updatePointInTimeRecovery(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string) diag.Diagnostics { + if d.HasChange("is_point_in_time_recovery") { + targetPITRStatus := d.Get("is_point_in_time_recovery").(bool) + _, err := c.DynamoDBTableV2PointInRecovery(tenantID, fullname, targetPITRStatus) + if err != nil { + return diag.FromErr(err) + } + } + return nil +} +func updateDeleteProtection(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string) diag.Diagnostics { + r := duplosdk.DuploDynamoDBTableRequestV2{} + if d.HasChange("deletion_protection_enabled") { + state := d.Get("deletion_protection_enabled").(bool) + r.DeletionProtectionEnabled = &state + + r.TableName = fullname + + _, err := c.DuploDynamoDBTableV2UpdateDeletionProtection(tenantID, &r) + if err != nil { + return diag.FromErr(err) + } + } + return nil +} + +func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string) diag.Diagnostics { + + if d.HasChange("global_secondary_index") { + r := duplosdk.DuploDynamoDBTableRequestV2{} + billingMode := d.Get("billing_mode").(string) + r.TableName = d.Get("name").(string) + globalSecondaryIndexes := []duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} + gsiSet := d.Get("global_secondary_index").(*schema.Set) + + for _, gsiObject := range gsiSet.List() { + gsi := gsiObject.(map[string]interface{}) + if err := validateGSIProvisionedThroughput(gsi, billingMode); err != nil { + return diag.Errorf("failed to create GSI: %w", err) + } + + gsiObject := expandGlobalSecondaryIndex(gsi, billingMode) + globalSecondaryIndexes = append(globalSecondaryIndexes, *gsiObject) + } + r.GlobalSecondaryIndexes = &[]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} + r.GlobalSecondaryIndexes = &globalSecondaryIndexes + _, diagErr := c.DynamoDBTableUpdateV2(tenantID, &r) + if diagErr != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, fullname, diagErr) + } + } + + return nil +} func resourceAwsDynamoDBTableDeleteV2(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { id := d.Id() tenantID, name, err := parseAwsDynamoDBTableIdParts(id) @@ -636,11 +699,6 @@ func expandDynamoDBTable(d *schema.ResourceData) (*duplosdk.DuploDynamoDBTableRe KeySchema: expandDynamoDBKeySchema(d), } - if d.HasChange("deletion_protection_enabled") { - state := d.Get("deletion_protection_enabled").(bool) - req.DeletionProtectionEnabled = &state - } - if v, ok := d.GetOk("attribute"); ok { aSet := v.(*schema.Set) req.AttributeDefinitions = expandAttributes(aSet.List()) diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 20b3b251..f91106ae 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -141,7 +141,7 @@ type DuploDynamoDBTableV2BillingModeSummary struct { type DuploDynamoDBTableRequestV2 struct { TableName string `json:"TableName"` BillingMode string `json:"BillingMode,omitempty"` - DeletionProtectionEnabled *bool `json:"DeletionProtectionEnabled"` + DeletionProtectionEnabled *bool `json:"DeletionProtectionEnabled,omitempty"` Tags *[]DuploKeyStringValue `json:"Tags,omitempty"` KeySchema *[]DuploDynamoDBKeySchemaV2 `json:"KeySchema,omitempty"` AttributeDefinitions *[]DuploDynamoDBAttributeDefinionV2 `json:"AttributeDefinitions,omitempty"` @@ -149,7 +149,7 @@ type DuploDynamoDBTableRequestV2 struct { StreamSpecification *DuploDynamoDBTableV2StreamSpecification `json:"StreamSpecification,omitempty"` SSESpecification *DuploDynamoDBTableV2SSESpecification `json:"SSESpecification,omitempty"` LocalSecondaryIndexes *[]DuploDynamoDBTableV2LocalSecondaryIndex `json:"LocalSecondaryIndexes,omitempty"` - GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex `json:"GlobalSecondaryIndexes,omitempty"` + GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex `json:"GlobalSecondaryIndexeUpdatess,omitempty"` } type DuploDynamoDBTagResourceRequest struct { @@ -216,6 +216,22 @@ func (c *Client) DynamoDBTableUpdateV2( return &rp, err } +func (c *Client) DynamoDBTableUpdateV21( + tenantID string, + rq *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { + rp := DuploDynamoDBTableV2{} + //rs := &[]DuploDynamoDBTableV2GlobalSecondaryIndex{} + //rs = rq.GlobalSecondaryIndexes + err := c.putAPI( + fmt.Sprintf("DynamoDBTableUpdate(%s, %s)", tenantID, rq.TableName), + fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2/%s", tenantID, rq.TableName), + rq, + &rp, + ) + rp.TenantID = tenantID + return &rp, err +} + func (c *Client) DynamoDBTableUpdateTagsV2( tenantId string, rq *DuploDynamoDBTagResourceRequest) (*DuploDynamoDBTagResourceResponse, ClientError) { @@ -307,12 +323,7 @@ func (c *Client) DuploDynamoDBTableV2UpdateSSESpecification( // "DeletionProtection modification must be the only operation in the request" func (c *Client) DuploDynamoDBTableV2UpdateDeletionProtection( tenantID string, - rq *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { + r *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { - r := DuploDynamoDBTableRequestV2{} - - r.TableName = rq.TableName - r.DeletionProtectionEnabled = rq.DeletionProtectionEnabled - - return c.DynamoDBTableUpdateV2(tenantID, &r) + return c.DynamoDBTableUpdateV2(tenantID, r) } diff --git a/duplosdk/client.go b/duplosdk/client.go index d7469c03..94c85463 100644 --- a/duplosdk/client.go +++ b/duplosdk/client.go @@ -221,7 +221,7 @@ func (c *Client) doAPI(verb string, apiName string, apiPath string, rp interface func (c *Client) doAPIWithRequestBody(verb string, apiName string, apiPath string, rq interface{}, rp interface{}) ClientError { apiName = fmt.Sprintf("%sAPI %s", strings.ToLower(verb), apiName) url := fmt.Sprintf("%s/%s", c.HostURL, apiPath) - + log.Printf("REQUEST NODY BEFORE SERIALIZE %+v", rq) // Build the request rqBody, err := json.Marshal(rq) if err != nil { From f486d4c8d3e1b61f609196c2d6019ab6bf937591 Mon Sep 17 00:00:00 2001 From: nikhil Date: Mon, 20 May 2024 11:39:55 +0530 Subject: [PATCH 03/35] need backend support for dynamod db --- .../resource_duplo_aws_dynamodb_table_v2.go | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 72d094e0..7a9a644b 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -530,9 +530,15 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat if diagErr != nil { return diagErr } - diagErr = updateGlobalSecondaryIndex(c, d, tenantID, fullname) - if diagErr != nil { - return diagErr + if shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq) { + rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil + // + log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) + _, err = c.DynamoDBTableUpdateV2(tenantID, rq) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } } //switch { //case isPITREnabled != targetPITRStatus: @@ -557,26 +563,25 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat // // } // // fallthrough //case shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq): - // // SSESpecification & DeletionProtectionEnabled must be updated alone. - // // Passing these values with the rest of the update table request willcause - // // cause a error. (Per .NET AWS SDK@3.7) - // rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil - // - // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err = c.DynamoDBTableUpdateV2(tenantID, rq) - // if err != nil { - // e := "Error updating tenant %s DynamoDB table '%s': %s" - // return diag.Errorf(e, tenantID, name, err) - // } - //} - //isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) - //if isSSESUpdatable { - // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) - // if err != nil { - // return diag.FromErr(err) - // } + // SSESpecification & DeletionProtectionEnabled must be updated alone. + // Passing these values with the rest of the update table request willcause + // cause a error. (Per .NET AWS SDK@3.7) + // rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil + // // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) + // _, err = c.DynamoDBTableUpdateV2(tenantID, rq) + // if err != nil { + // e := "Error updating tenant %s DynamoDB table '%s': %s" + // return diag.Errorf(e, tenantID, name, err) + // } //} + isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) + if isSSESUpdatable { + log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) + _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) + if err != nil { + return diag.FromErr(err) + } + } //// Generate the ID for the resource and set it. id := fmt.Sprintf("%s/%s", tenantID, name) getResource := func() (interface{}, duplosdk.ClientError) { @@ -631,12 +636,12 @@ func updateDeleteProtection(c *duplosdk.Client, d *schema.ResourceData, tenantID return nil } -func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string) diag.Diagnostics { +func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string, existing *duplosdk.DuploDynamoDBTableV2) diag.Diagnostics { if d.HasChange("global_secondary_index") { r := duplosdk.DuploDynamoDBTableRequestV2{} billingMode := d.Get("billing_mode").(string) - r.TableName = d.Get("name").(string) + r.TableName = fullname globalSecondaryIndexes := []duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} gsiSet := d.Get("global_secondary_index").(*schema.Set) @@ -651,10 +656,13 @@ func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tena } r.GlobalSecondaryIndexes = &[]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} r.GlobalSecondaryIndexes = &globalSecondaryIndexes - _, diagErr := c.DynamoDBTableUpdateV2(tenantID, &r) - if diagErr != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, fullname, diagErr) + + if shouldUpdateGSI(existing, &r) { + _, diagErr := c.DynamoDBTableUpdateV2(tenantID, &r) + if diagErr != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, fullname, diagErr) + } } } From 0d25f1704e759b5ce412c15ebf4b19b7f14805b4 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 30 May 2024 12:13:14 +0530 Subject: [PATCH 04/35] Update GSI in progress --- .../resource_duplo_aws_dynamodb_table_v2.go | 16 +++++++++-- duplosdk/aws_dynamo_db.go | 28 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 7a9a644b..48bddb2b 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -530,11 +530,23 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat if diagErr != nil { return diagErr } - if shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq) { + + if shouldUpdateGSI(existing, rq) { + rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil + + log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) + _, err = c.DynamoDBTableUpdateGSIV2(tenantID, rq) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + + } + if shouldUpdateThroughput(existing, rq) { rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err = c.DynamoDBTableUpdateV2(tenantID, rq) + _, err = c.DynamoDBTableUpdateGSIV2(tenantID, rq) if err != nil { e := "Error updating tenant %s DynamoDB table '%s': %s" return diag.Errorf(e, tenantID, name, err) diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index f91106ae..0bc945f9 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -133,7 +133,12 @@ type DuploDynamoDBTableV2GlobalSecondaryIndex struct { KeySchema *[]DuploDynamoDBKeySchema `json:"KeySchema,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` } - +type UpdateGSIReq struct { + UpdateGSI UpdateGSI `json:"Update"` +} +type UpdateGSI struct { + GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex +} type DuploDynamoDBTableV2BillingModeSummary struct { BillingMode *DuploStringValue `json:"BillingMode,omitempty"` } @@ -216,6 +221,27 @@ func (c *Client) DynamoDBTableUpdateV2( return &rp, err } +func (c *Client) DynamoDBTableUpdateGSIV2( + tenantID string, + rq *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { + rp := DuploDynamoDBTableV2{} + u := UpdateGSI{ + rq.GlobalSecondaryIndexes, + } + ur := UpdateGSIReq{ + u, + } + + err := c.putAPI( + fmt.Sprintf("DynamoDBTableUpdate(%s, %s)", tenantID, rq.TableName), + fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2/%s/globalSecondaryIndex", tenantID, rq.TableName), + &ur, + &rp, + ) + rp.TenantID = tenantID + return &rp, err +} + func (c *Client) DynamoDBTableUpdateV21( tenantID string, rq *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { From 079a660d732fcf20d159b3c9829d08810ea566cc Mon Sep 17 00:00:00 2001 From: nikhil Date: Fri, 31 May 2024 15:17:38 +0530 Subject: [PATCH 05/35] in progress --- .../resource_duplo_aws_dynamodb_table.go | 4 +-- .../resource_duplo_aws_dynamodb_table_v2.go | 25 +++++++++++-------- duplosdk/aws_dynamo_db.go | 8 +++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table.go b/duplocloud/resource_duplo_aws_dynamodb_table.go index 3a48cf90..315e43fa 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table.go @@ -205,8 +205,8 @@ func expandDynamoDBTablePrimaryKey(duplo *duplosdk.DuploDynamoDBTable, d *schema primaryKeyName := keySchema.AttributeName d.Set("primary_key_name", primaryKeyName) - if keySchema.KeyType != nil { - d.Set("key_type", keySchema.KeyType.Value) + if keySchema.KeyType != "" { + d.Set("key_type", keySchema.KeyType) } if duplo.AttributeDefinitions != nil { diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 48bddb2b..0b2bde3e 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -217,6 +217,11 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Required: true, ForceNew: true, }, + "hash_key": { + Description: "The name of the hash key in the index; must be defined as an attribute in the resource.", + Type: schema.TypeString, + Required: true, + }, "non_key_attributes": { Description: "Only required with `INCLUDE` as a projection type; a list of attributes to project into the index. These do not need to be defined as attributes on the table.", Type: schema.TypeList, @@ -382,11 +387,11 @@ func resourceAwsDynamoDBTableReadV2(ctx context.Context, d *schema.ResourceData, } for _, attribute := range *duplo.KeySchema { - if attribute.KeyType.Value == duplosdk.DynamoDBKeyTypeHash { + if attribute.KeyType == duplosdk.DynamoDBKeyTypeHash { d.Set("hash_key", attribute.AttributeName) } - if attribute.KeyType.Value == duplosdk.DynamoDBKeyTypeRange { + if attribute.KeyType == duplosdk.DynamoDBKeyTypeRange { d.Set("range_key", attribute.AttributeName) } } @@ -818,17 +823,17 @@ func flattenTableGlobalSecondaryIndex(gsi *[]duplosdk.DuploDynamoDBTableV2Global for _, attribute := range *g.KeySchema { - if attribute.KeyType.Value == "HASH" { + if attribute.KeyType == "HASH" { gsi["hash_key"] = attribute.AttributeName } - if attribute.KeyType.Value == "RANGE" { + if attribute.KeyType == "RANGE" { gsi["range_key"] = attribute.AttributeName } } if g.Projection != nil { - gsi["projection_type"] = g.Projection.ProjectionType.Value + gsi["projection_type"] = g.Projection.ProjectionType gsi["non_key_attributes"] = g.Projection.NonKeyAttributes } @@ -853,14 +858,14 @@ func expandKeySchema(data map[string]interface{}) *[]duplosdk.DuploDynamoDBKeySc if v, ok := data["hash_key"]; ok && v != nil && v != "" { keySchema = append(keySchema, duplosdk.DuploDynamoDBKeySchema{ AttributeName: v.(string), - KeyType: &duplosdk.DuploStringValue{Value: "HASH"}, + KeyType: "HASH", }) } if v, ok := data["range_key"]; ok && v != nil && v != "" { keySchema = append(keySchema, duplosdk.DuploDynamoDBKeySchema{ AttributeName: v.(string), - KeyType: &duplosdk.DuploStringValue{Value: "RANGE"}, + KeyType: "RANGE", }) } return &keySchema @@ -868,7 +873,7 @@ func expandKeySchema(data map[string]interface{}) *[]duplosdk.DuploDynamoDBKeySc func expandProjection(data map[string]interface{}) *duplosdk.DuploDynamoDBTableV2Projection { projection := &duplosdk.DuploDynamoDBTableV2Projection{ - ProjectionType: &duplosdk.DuploStringValue{Value: data["projection_type"].(string)}, + ProjectionType: data["projection_type"].(string), } if v, ok := data["non_key_attributes"].([]interface{}); ok && len(v) > 0 { @@ -921,12 +926,12 @@ func flattenTableLocalSecondaryIndex(lsi *[]duplosdk.DuploDynamoDBTableV2LocalSe } if l.Projection != nil { - m["projection_type"] = l.Projection.ProjectionType.Value + m["projection_type"] = l.Projection.ProjectionType m["non_key_attributes"] = l.Projection.NonKeyAttributes } for _, attribute := range *l.KeySchema { - if attribute.KeyType.Value == "RANGE" { + if attribute.KeyType == "RANGE" { m["range_key"] = attribute.AttributeName } } diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 0bc945f9..47cb4c4a 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -74,8 +74,8 @@ type DuploDynamoDBProvisionedThroughput struct { // DuploDynamoDBKeySchema is a Duplo SDK object that represents a dynamodb key schema type DuploDynamoDBKeySchema struct { - AttributeName string `json:"AttributeName"` - KeyType *DuploStringValue `json:"KeyType,omitempty"` + AttributeName string `json:"AttributeName"` + KeyType string `json:"KeyType,omitempty"` } type DuploDynamoDBKeySchemaV2 struct { @@ -117,8 +117,8 @@ type DuploDynamoDBTableV2SSESpecification struct { } type DuploDynamoDBTableV2Projection struct { - NonKeyAttributes []string `json:"NonKeyAttributes,omitempty"` - ProjectionType *DuploStringValue `json:"ProjectionType,omitempty"` + NonKeyAttributes []string `json:"NonKeyAttributes,omitempty"` + ProjectionType string `json:"ProjectionType,omitempty"` } type DuploDynamoDBTableV2LocalSecondaryIndex struct { From 7a5f1241219a6feff309108df3b4c7c4306ef7be Mon Sep 17 00:00:00 2001 From: nikhil Date: Mon, 3 Jun 2024 12:03:01 +0530 Subject: [PATCH 06/35] update dynamod db in progress --- .../resource_duplo_aws_dynamodb_table_v2.go | 37 +++++++----- duplosdk/aws_dynamo_db.go | 56 +++++++++++++++++-- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 0b2bde3e..b11352a2 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -387,11 +387,11 @@ func resourceAwsDynamoDBTableReadV2(ctx context.Context, d *schema.ResourceData, } for _, attribute := range *duplo.KeySchema { - if attribute.KeyType == duplosdk.DynamoDBKeyTypeHash { + if attribute.KeyType.Value == duplosdk.DynamoDBKeyTypeHash { d.Set("hash_key", attribute.AttributeName) } - if attribute.KeyType == duplosdk.DynamoDBKeyTypeRange { + if attribute.KeyType.Value == duplosdk.DynamoDBKeyTypeRange { d.Set("range_key", attribute.AttributeName) } } @@ -653,7 +653,7 @@ func updateDeleteProtection(c *duplosdk.Client, d *schema.ResourceData, tenantID return nil } -func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string, existing *duplosdk.DuploDynamoDBTableV2) diag.Diagnostics { +func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string, existing *duplosdk.DuploDynamoDBTableV2Response) diag.Diagnostics { if d.HasChange("global_secondary_index") { r := duplosdk.DuploDynamoDBTableRequestV2{} @@ -805,7 +805,7 @@ func expandDynamoDBKeySchema(d *schema.ResourceData) *[]duplosdk.DuploDynamoDBKe return &ary } -func flattenTableGlobalSecondaryIndex(gsi *[]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex) []interface{} { +func flattenTableGlobalSecondaryIndex(gsi *[]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndexResponse) []interface{} { if len(*gsi) == 0 { return []interface{}{} } @@ -823,17 +823,17 @@ func flattenTableGlobalSecondaryIndex(gsi *[]duplosdk.DuploDynamoDBTableV2Global for _, attribute := range *g.KeySchema { - if attribute.KeyType == "HASH" { + if attribute.KeyType.Value == "HASH" { gsi["hash_key"] = attribute.AttributeName } - if attribute.KeyType == "RANGE" { + if attribute.KeyType.Value == "RANGE" { gsi["range_key"] = attribute.AttributeName } } if g.Projection != nil { - gsi["projection_type"] = g.Projection.ProjectionType + gsi["projection_type"] = g.Projection.ProjectionType.Value gsi["non_key_attributes"] = g.Projection.NonKeyAttributes } @@ -912,7 +912,7 @@ func expandProvisionedThroughputField(id string, data map[string]interface{}, ke return v } -func flattenTableLocalSecondaryIndex(lsi *[]duplosdk.DuploDynamoDBTableV2LocalSecondaryIndex) []interface{} { +func flattenTableLocalSecondaryIndex(lsi *[]duplosdk.DuploDynamoDBTableV2LocalSecondaryIndexResponse) []interface{} { if len(*lsi) == 0 { return []interface{}{} } @@ -926,12 +926,12 @@ func flattenTableLocalSecondaryIndex(lsi *[]duplosdk.DuploDynamoDBTableV2LocalSe } if l.Projection != nil { - m["projection_type"] = l.Projection.ProjectionType + m["projection_type"] = l.Projection.ProjectionType.Value m["non_key_attributes"] = l.Projection.NonKeyAttributes } for _, attribute := range *l.KeySchema { - if attribute.KeyType == "RANGE" { + if attribute.KeyType.Value == "RANGE" { m["range_key"] = attribute.AttributeName } } @@ -1076,7 +1076,7 @@ func dynamodbWaitUntilReady(ctx context.Context, c *duplosdk.Client, tenantID st // shouldUpdateGSI compares the DuploDynamoDBTableV2LocalSecondaryIndex of the // existing table with the updated table. Returns true if a change is detected. func shouldUpdateGSI( - table *duplosdk.DuploDynamoDBTableV2, + table *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, ) bool { if table.GlobalSecondaryIndexes == nil || request.GlobalSecondaryIndexes == nil { @@ -1101,10 +1101,21 @@ func shouldUpdateGSI( return false } +func globalIndexAction(existing *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2) { + if existing.GlobalSecondaryIndexes != nil && request.GlobalSecondaryIndexes != nil { + return "update" + } else if existing.GlobalSecondaryIndexes == nil && request.GlobalSecondaryIndexes != nil { + return "create" + } else { + return "nil" + } + +} + // shouldUpdateThroughput compares the DuploDynamoDBProvisionedThroughput of // the existing table and updated table. Returns true if a change is detected. func shouldUpdateThroughput( - table *duplosdk.DuploDynamoDBTableV2, + table *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, ) bool { return !reflect.DeepEqual(table.ProvisionedThroughput, request.ProvisionedThroughput) @@ -1113,7 +1124,7 @@ func shouldUpdateThroughput( // shouldUpdateSSESepecification compares the DuploDynamoDBTableV2SSESpecification of // the existing table and updated table. Returns true if a change is detected. func shouldUpdateSSESepecification( - table *duplosdk.DuploDynamoDBTableV2, + table *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, ) bool { if table.SSEDescription == nil && request.SSESpecification == nil { diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 47cb4c4a..40cd2a86 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -46,6 +46,29 @@ type DuploDynamoDBTableV2 struct { BillingModeSummary *DuploDynamoDBTableV2BillingModeSummary `json:"BillingModeSummary,omitempty"` } +type DuploDynamoDBTableV2Response struct { + // NOTE: The TenantID field does not come from the backend - we synthesize it + TenantID string `json:"-"` + + TableName string `json:"TableName"` + TableId string `json:"TableId"` + TableArn string `json:"TableArn,omitempty"` + DeletionProtectionEnabled bool `json:"DeletionProtectionEnabled,omitempty"` + PointInTimeRecoveryStatus string `json:"PointInTimeRecoveryStatus,omitempty"` + KeySchema *[]DuploDynamoDBKeySchemaResponse `json:"KeySchema,omitempty"` + AttributeDefinitions *[]DuploDynamoDBAttributeDefinion `json:"AttributeDefinitions,omitempty"` + TableStatus *DuploStringValue `json:"TableStatus,omitempty"` + TableSizeBytes int `json:"TableSizeBytes,omitempty"` + LocalSecondaryIndexes *[]DuploDynamoDBTableV2LocalSecondaryIndexResponse `json:"LocalSecondaryIndexes,omitempty"` + GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndexResponse `json:"GlobalSecondaryIndexes,omitempty"` + LatestStreamArn string `json:"LatestStreamArn,omitempty"` + LatestStreamLabel string `json:"LatestStreamLabel,omitempty"` + ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` + SSEDescription *DuploDynamoDBTableV2SSESpecification `json:"SSEDescription,omitempty"` + StreamSpecification *DuploDynamoDBTableV2StreamSpecification `json:"StreamSpecification,omitempty"` + BillingModeSummary *DuploDynamoDBTableV2BillingModeSummary `json:"BillingModeSummary,omitempty"` +} + type DuploDynamoDBTableV2TimeInRecovery struct { IsPointInTimeRecovery bool `json:"IsPointInTimeRecovery,omitempty"` } @@ -78,6 +101,11 @@ type DuploDynamoDBKeySchema struct { KeyType string `json:"KeyType,omitempty"` } +type DuploDynamoDBKeySchemaResponse struct { + AttributeName string `json:"AttributeName"` + KeyType DuploStringValue `json:"KeyType,omitempty"` +} + type DuploDynamoDBKeySchemaV2 struct { AttributeName string `json:"AttributeName"` KeyType string `json:"KeyType,omitempty"` @@ -121,18 +149,36 @@ type DuploDynamoDBTableV2Projection struct { ProjectionType string `json:"ProjectionType,omitempty"` } +type DuploDynamoDBTableV2ProjectionResponse struct { + NonKeyAttributes []string `json:"NonKeyAttributes,omitempty"` + ProjectionType *DuploStringValue `json:"ProjectionType,omitempty"` +} + type DuploDynamoDBTableV2LocalSecondaryIndex struct { IndexName string `json:"IndexName"` Projection *DuploDynamoDBTableV2Projection `json:"Projection,omitempty"` KeySchema *[]DuploDynamoDBKeySchema `json:"KeySchema,omitempty"` } +type DuploDynamoDBTableV2LocalSecondaryIndexResponse struct { + IndexName string `json:"IndexName"` + Projection *DuploDynamoDBTableV2ProjectionResponse `json:"Projection,omitempty"` + KeySchema *[]DuploDynamoDBKeySchemaResponse `json:"KeySchema,omitempty"` +} + type DuploDynamoDBTableV2GlobalSecondaryIndex struct { IndexName string `json:"IndexName"` Projection *DuploDynamoDBTableV2Projection `json:"Projection,omitempty"` KeySchema *[]DuploDynamoDBKeySchema `json:"KeySchema,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` } + +type DuploDynamoDBTableV2GlobalSecondaryIndexResponse struct { + IndexName string `json:"IndexName"` + Projection *DuploDynamoDBTableV2ProjectionResponse `json:"Projection,omitempty"` + KeySchema *[]DuploDynamoDBKeySchemaResponse `json:"KeySchema,omitempty"` + ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` +} type UpdateGSIReq struct { UpdateGSI UpdateGSI `json:"Update"` } @@ -154,7 +200,7 @@ type DuploDynamoDBTableRequestV2 struct { StreamSpecification *DuploDynamoDBTableV2StreamSpecification `json:"StreamSpecification,omitempty"` SSESpecification *DuploDynamoDBTableV2SSESpecification `json:"SSESpecification,omitempty"` LocalSecondaryIndexes *[]DuploDynamoDBTableV2LocalSecondaryIndex `json:"LocalSecondaryIndexes,omitempty"` - GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex `json:"GlobalSecondaryIndexeUpdatess,omitempty"` + GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex `json:"GlobalSecondaryIndexes,omitempty"` } type DuploDynamoDBTagResourceRequest struct { @@ -192,10 +238,10 @@ func (c *Client) DynamoDBTableCreate( func (c *Client) DynamoDBTableCreateV2( tenantID string, rq *DuploDynamoDBTableRequestV2, -) (*DuploDynamoDBTableV2, ClientError) { +) (*DuploDynamoDBTableV2Response, ClientError) { fmt.Println("calling DynamoDBTableCreateV2") - rp := DuploDynamoDBTableV2{} + rp := DuploDynamoDBTableV2Response{} err := c.postAPI( fmt.Sprintf("DynamoDBTableCreate(%s, %s)", tenantID, rq.TableName), fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2", tenantID), @@ -297,8 +343,8 @@ func (c *Client) DynamoDBTableGet(tenantID string, name string) (*DuploDynamoDBT return &rp, err } -func (c *Client) DynamoDBTableGetV2(tenantID string, name string) (*DuploDynamoDBTableV2, ClientError) { - rp := DuploDynamoDBTableV2{} +func (c *Client) DynamoDBTableGetV2(tenantID string, name string) (*DuploDynamoDBTableV2Response, ClientError) { + rp := DuploDynamoDBTableV2Response{} err := c.getAPI( fmt.Sprintf("DynamoDBTableGet(%s, %s)", tenantID, name), fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2/%s", tenantID, name), From b72182859ae27491d257855f16009502d28c26e8 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 4 Jun 2024 12:27:14 +0530 Subject: [PATCH 07/35] dynamo db fixes done added new support --- docs/data-sources/ecs_task_definition.md | 1 + docs/resources/aws_dynamodb_table_v2.md | 79 ++++++ docs/resources/aws_efs_file_system.md | 13 + docs/resources/ecs_task_definition.md | 1 + .../resource_duplo_aws_dynamodb_table_v2.go | 256 ++++++------------ duplosdk/aws_dynamo_db.go | 47 +++- .../resource.tf | 77 ++++++ 7 files changed, 294 insertions(+), 180 deletions(-) diff --git a/docs/data-sources/ecs_task_definition.md b/docs/data-sources/ecs_task_definition.md index 39ff4e82..a38eece6 100644 --- a/docs/data-sources/ecs_task_definition.md +++ b/docs/data-sources/ecs_task_definition.md @@ -26,6 +26,7 @@ description: |- - `cpu` (String) - `execution_role_arn` (String) - `family` (String) The name of the task definition to create. +- `full_family_name` (String) The name of the task definition to create. - `id` (String) The ID of this resource. - `inference_accelerator` (Set of Object) (see [below for nested schema](#nestedatt--inference_accelerator)) - `ipc_mode` (String) diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index 779cd7a2..a470d7d4 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -71,6 +71,83 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { } } + + +resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { + + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mytable" + read_capacity = 80 + write_capacity = 40 + billing_mode = "PROVISIONED" + is_point_in_time_recovery = false + deletion_protection_enabled = false + + tag { + key = "school" + value = "admission" + } + attribute { + name = "ForumName" + type = "S" + } + attribute { + name = "Subject" + type = "S" + } + attribute { + name = "LastPostDateTime" + type = "S" + } + attribute { + name = "PostMonth" + type = "S" + } + + attribute { + name = "GamerZone" + type = "S" + } + attribute { + name = "TopScore" + type = "N" + } + key_schema { + attribute_name = "ForumName" + key_type = "HASH" + } + key_schema { + attribute_name = "Subject" + key_type = "RANGE" + } + global_secondary_index { + name = "PostDate" + hash_key = "PostMonth" + range_key = "LastPostDateTime" + write_capacity = 2 + read_capacity = 2 + projection_type = "KEYS_ONLY" + } + global_secondary_index { + name = "GamerZone" + hash_key = "GamerZone" + range_key = "TopScore" + write_capacity = 5 + read_capacity = 5 + projection_type = "ALL" + #delete_index = true #To remove an global secondary index from associated table + } + server_side_encryption { + enabled = false + } + local_secondary_index { #local secondary index doesnot support updation + hash_key = "ForumName" + name = "LastPostIndex" + range_key = "LastPostDateTime" + projection_type = "KEYS_ONLY" + } + +} ``` @@ -128,6 +205,7 @@ Required: Optional: +- `delete_index` (Boolean) Set it to true when global index need to be deleted during update Defaults to `false`. - `non_key_attributes` (Set of String) Only required with `INCLUDE` as a projection type; a list of attributes to project into the index. These do not need to be defined as attributes on the table. - `range_key` (String) The name of the range key; must be defined. - `read_capacity` (Number) The number of read units for this index. Must be set if `billing_mode` is set to `PROVISIONED`. @@ -148,6 +226,7 @@ Required: Required: +- `hash_key` (String) The name of the hash key in the index; must be defined as an attribute in the resource. - `name` (String) The name of the index. - `projection_type` (String) One of `ALL`, `INCLUDE` or `KEYS_ONLY` where `ALL` projects every attribute into the index, `KEYS_ONLY` projects just the hash and range key into the index, and `INCLUDE` projects only the keys specified in the `non_key_attributes` parameter. - `range_key` (String) The name of the range key; must be defined. diff --git a/docs/resources/aws_efs_file_system.md b/docs/resources/aws_efs_file_system.md index 113356b5..7f1c6423 100644 --- a/docs/resources/aws_efs_file_system.md +++ b/docs/resources/aws_efs_file_system.md @@ -55,6 +55,7 @@ resource "duplocloud_aws_efs_file_system" "efs" { - `file_system_id` (String) The ID that identifies the file system. - `fullname` (String) Duplo generated name of the EFS. - `id` (String) The ID of this resource. +- `mount_targets` (List of Object) (see [below for nested schema](#nestedatt--mount_targets)) - `number_of_mount_targets` (Number) The current number of mount targets that the file system has. - `owner_id` (String) The AWS account that created the file system. - `size_in_bytes` (Number) The latest known metered size (in bytes) of data stored in the file system. @@ -86,6 +87,18 @@ Optional: - `create` (String) - `delete` (String) + + +### Nested Schema for `mount_targets` + +Read-Only: + +- `availability_zone` (String) +- `ip_address` (String) +- `lifecycle_state` (String) +- `mount_target_id` (String) +- `subnet_id` (String) + ## Import Import is supported using the following syntax: diff --git a/docs/resources/ecs_task_definition.md b/docs/resources/ecs_task_definition.md index b302761c..c0d8a0b2 100644 --- a/docs/resources/ecs_task_definition.md +++ b/docs/resources/ecs_task_definition.md @@ -73,6 +73,7 @@ resource "duplocloud_ecs_task_definition" "myservice" { - `arn` (String) The ARN of the task definition. - `execution_role_arn` (String) +- `full_family_name` (String) The name of the task definition to create. - `id` (String) The ID of this resource. - `revision` (Number) The current revision of the task definition. - `status` (String) The status of the task definition. diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index b11352a2..edb44551 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "reflect" "strings" "terraform-provider-duplocloud/duplosdk" "time" @@ -15,38 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -/* -//creating conflicts with name set up - - func BeforeHook(fn func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics) func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(*duplosdk.Client) - - err := prefixName(c, d) - if err != nil { - return diag.FromErr(err) - } - - return fn(ctx, d, m) - } - } - - func prefixName(c *duplosdk.Client, d *schema.ResourceData) duplosdk.ClientError { - tenantId, name := d.Get("tenant_id").(string), d.Get("name").(string) - - prefix, err := c.GetDuploServicesPrefix(tenantId) - if err != nil { - return err - } - - if !strings.HasPrefix(name, prefix) { - name = fmt.Sprintf("%s-%s", prefix, name) - d.Set("name", name) - } - - return nil - } -*/ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { return map[string]*schema.Schema{ "tenant_id": { @@ -202,13 +169,20 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Type: schema.TypeInt, Optional: true, }, + "delete_index": { + Description: "Set it to true when global index need to be deleted during update", + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, }, }, "local_secondary_index": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + DiffSuppressFunc: diffSuppressFuncIgnore, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -535,93 +509,47 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat if diagErr != nil { return diagErr } - - if shouldUpdateGSI(existing, rq) { - rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil - - log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err = c.DynamoDBTableUpdateGSIV2(tenantID, rq) - if err != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, name, err) - } - + diagErr = globalIndexUpdateAction(c, existing, rq, tenantID, fullname, d) + if diagErr != nil { + return diagErr } + if shouldUpdateThroughput(existing, rq) { rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - _, err = c.DynamoDBTableUpdateGSIV2(tenantID, rq) + _, err = c.DynamoDBTableUpdateV2(tenantID, rq) if err != nil { e := "Error updating tenant %s DynamoDB table '%s': %s" return diag.Errorf(e, tenantID, name, err) } } - //switch { - //case isPITREnabled != targetPITRStatus: - // log.Printf("[INFO] Updating Point In Recovery for DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err = c.DynamoDBTableV2PointInRecovery(tenantID, fullname, targetPITRStatus) - // if err != nil { - // return diag.FromErr(err) - // } - // fallthrough - //case rq.DeletionProtectionEnabled != nil && existing.DeletionProtectionEnabled != *rq.DeletionProtectionEnabled: - // log.Printf("[INFO] Updating Deletion Protection for DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err := c.DuploDynamoDBTableV2UpdateDeletionProtection(tenantID, rq) - // if err != nil { - // return diag.FromErr(err) - // } - // fallthrough - // // case !reflect.DeepEqual(existing.SSEDescription, rq.SSESpecification): //shouldUpdateSSESepecification(existing, rq): - // // log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) - // // _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) - // // if err != nil { - // // return diag.FromErr(err) - // // } - // // fallthrough - //case shouldUpdateGSI(existing, rq) || shouldUpdateThroughput(existing, rq): - // SSESpecification & DeletionProtectionEnabled must be updated alone. - // Passing these values with the rest of the update table request willcause - // cause a error. (Per .NET AWS SDK@3.7) - // rq.SSESpecification, rq.DeletionProtectionEnabled = nil, nil - // // log.Printf("[INFO] Updating DynamoDB table '%s' in tenant '%s'", name, tenantID) - // _, err = c.DynamoDBTableUpdateV2(tenantID, rq) - // if err != nil { - // e := "Error updating tenant %s DynamoDB table '%s': %s" - // return diag.Errorf(e, tenantID, name, err) - // } - //} + isSSESUpdatable := shouldUpdateSSESepecification(existing, rq) if isSSESUpdatable { + rq := &duplosdk.DuploDynamoDBTableRequestV2{ + TableName: fullname, + SSESpecification: &duplosdk.DuploDynamoDBTableV2SSESpecification{ + Enabled: rq.SSESpecification.Enabled, + }, + } log.Printf("[INFO] Updating SSE Specification for DynamoDB table '%s' in tenant '%s'", name, tenantID) _, err := c.DuploDynamoDBTableV2UpdateSSESpecification(tenantID, rq) if err != nil { return diag.FromErr(err) } } + err = dynamodbWaitUntilReady(ctx, c, tenantID, fullname, d.Timeout("update")) + if err != nil { + return diag.FromErr(err) + } //// Generate the ID for the resource and set it. id := fmt.Sprintf("%s/%s", tenantID, name) - getResource := func() (interface{}, duplosdk.ClientError) { - return c.DynamoDBTableGet(tenantID, fullname) - } - diags := waitForResourceToBePresentAfterUpdate(ctx, d, "dynamodb table", id, getResource) - // If there are diagnostics from waiting, return them. - if diags != nil { - return diags - } d.SetId(id) - // wait until the cache instances to be healthy. - if d.Get("wait_until_ready") == nil || d.Get("wait_until_ready").(bool) { - err = dynamodbWaitUntilReady(ctx, c, tenantID, fullname, d.Timeout("create")) - if err != nil { - return diag.FromErr(err) - } - } - // Perform a read after update to sync state. - diags = resourceAwsDynamoDBTableReadV2(ctx, d, m) + diags := resourceAwsDynamoDBTableReadV2(ctx, d, m) log.Printf("[TRACE] resourceAwsDynamoDBTableUpdateV2(%s, %s): end", tenantID, name) return diags @@ -653,38 +581,6 @@ func updateDeleteProtection(c *duplosdk.Client, d *schema.ResourceData, tenantID return nil } -func updateGlobalSecondaryIndex(c *duplosdk.Client, d *schema.ResourceData, tenantID, fullname string, existing *duplosdk.DuploDynamoDBTableV2Response) diag.Diagnostics { - - if d.HasChange("global_secondary_index") { - r := duplosdk.DuploDynamoDBTableRequestV2{} - billingMode := d.Get("billing_mode").(string) - r.TableName = fullname - globalSecondaryIndexes := []duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} - gsiSet := d.Get("global_secondary_index").(*schema.Set) - - for _, gsiObject := range gsiSet.List() { - gsi := gsiObject.(map[string]interface{}) - if err := validateGSIProvisionedThroughput(gsi, billingMode); err != nil { - return diag.Errorf("failed to create GSI: %w", err) - } - - gsiObject := expandGlobalSecondaryIndex(gsi, billingMode) - globalSecondaryIndexes = append(globalSecondaryIndexes, *gsiObject) - } - r.GlobalSecondaryIndexes = &[]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} - r.GlobalSecondaryIndexes = &globalSecondaryIndexes - - if shouldUpdateGSI(existing, &r) { - _, diagErr := c.DynamoDBTableUpdateV2(tenantID, &r) - if diagErr != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, fullname, diagErr) - } - } - } - - return nil -} func resourceAwsDynamoDBTableDeleteV2(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { id := d.Id() tenantID, name, err := parseAwsDynamoDBTableIdParts(id) @@ -849,6 +745,7 @@ func expandGlobalSecondaryIndex(data map[string]interface{}, billingMode string) KeySchema: expandKeySchema(data), Projection: expandProjection(data), ProvisionedThroughput: expandProvisionedThroughput(data, billingMode), + DeleteIndex: data["delete_index"].(bool), } } @@ -956,14 +853,14 @@ func expandLocalSecondaryIndexes(cfg []interface{}) *[]duplosdk.DuploDynamoDBTab return &indexes } -func flattenDynamoDBTableServerSideEncryption(spec *duplosdk.DuploDynamoDBTableV2SSESpecification) []interface{} { +func flattenDynamoDBTableServerSideEncryption(spec *duplosdk.DuploDynamoDBTableV2SSESpecificationResponse) []interface{} { if spec == nil { return []interface{}{} } m := map[string]interface{}{ - "enabled": spec.Enabled, - "kms_key_arn": spec.KMSMasterKeyId, + "enabled": spec.Status.Value == "ENABLED", + "kms_key_arn": spec.KMSMasterKeyArn, } return []interface{}{m} @@ -1073,43 +970,61 @@ func dynamodbWaitUntilReady(ctx context.Context, c *duplosdk.Client, tenantID st return err } -// shouldUpdateGSI compares the DuploDynamoDBTableV2LocalSecondaryIndex of the -// existing table with the updated table. Returns true if a change is detected. -func shouldUpdateGSI( - table *duplosdk.DuploDynamoDBTableV2Response, +func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, -) bool { - if table.GlobalSecondaryIndexes == nil || request.GlobalSecondaryIndexes == nil { - return true - } - if len(*table.GlobalSecondaryIndexes) != len(*request.GlobalSecondaryIndexes) { - return true - } - - for i, aIndex := range *table.GlobalSecondaryIndexes { - bIndex := (*request.GlobalSecondaryIndexes)[i] + tenantID, name string, d *schema.ResourceData) diag.Diagnostics { - isDeepEqual := reflect.DeepEqual( - aIndex.ProvisionedThroughput, - bIndex.ProvisionedThroughput, - ) - if aIndex.IndexName != bIndex.IndexName || !isDeepEqual { - return true + existingIndex := make(map[string]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndexResponse) + gsiu := []duplosdk.GlobalSecondaryIndexUpdates{} + if existing != nil && d.HasChange("global_secondary_index") { + for _, e := range *existing.GlobalSecondaryIndexes { + existingIndex[e.IndexName] = e } - } + for _, r := range *request.GlobalSecondaryIndexes { + if ev, ok := existingIndex[r.IndexName]; !ok { + t := duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} + t = r + cr := duplosdk.GlobalSecondaryIndexUpdates{ + Create: &t, + } + gsiu = append(gsiu, cr) - return false -} + } else { + if r.DeleteIndex { + del := duplosdk.GlobalSecondaryIndexUpdates{ + Delete: &duplosdk.Delete{ + IndexName: r.IndexName, + }, + } + gsiu = append(gsiu, del) -func globalIndexAction(existing *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2) { - if existing.GlobalSecondaryIndexes != nil && request.GlobalSecondaryIndexes != nil { - return "update" - } else if existing.GlobalSecondaryIndexes == nil && request.GlobalSecondaryIndexes != nil { - return "create" - } else { - return "nil" - } + } else { + ev = existingIndex[r.IndexName] + if (ev.ProvisionedThroughput.ReadCapacityUnits != r.ProvisionedThroughput.ReadCapacityUnits) || (ev.ProvisionedThroughput.WriteCapacityUnits != r.ProvisionedThroughput.WriteCapacityUnits) { + up := duplosdk.GlobalSecondaryIndexUpdates{ + Update: &duplosdk.Update{ + IndexName: r.IndexName, + ProvisionedThroughput: *r.ProvisionedThroughput, + }, + } + gsiu = append(gsiu, up) + } + } + } + } + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + } + return nil } // shouldUpdateThroughput compares the DuploDynamoDBProvisionedThroughput of @@ -1118,7 +1033,10 @@ func shouldUpdateThroughput( table *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, ) bool { - return !reflect.DeepEqual(table.ProvisionedThroughput, request.ProvisionedThroughput) + if (table.ProvisionedThroughput.ReadCapacityUnits != request.ProvisionedThroughput.ReadCapacityUnits) || (table.ProvisionedThroughput.WriteCapacityUnits != request.ProvisionedThroughput.WriteCapacityUnits) { + return true + } + return false } // shouldUpdateSSESepecification compares the DuploDynamoDBTableV2SSESpecification of @@ -1130,5 +1048,9 @@ func shouldUpdateSSESepecification( if table.SSEDescription == nil && request.SSESpecification == nil { return false } - return !reflect.DeepEqual(table.SSEDescription, request.SSESpecification) + status := "DISABLED" + if request.SSESpecification.Enabled { + status = "ENABLED" + } + return !(table.SSEDescription.Status.Value == status) } diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 40cd2a86..83cac72b 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -2,7 +2,6 @@ package duplosdk import ( "fmt" - "time" ) const DynamoDBProvisionedThroughputMinValue = 1 @@ -64,7 +63,7 @@ type DuploDynamoDBTableV2Response struct { LatestStreamArn string `json:"LatestStreamArn,omitempty"` LatestStreamLabel string `json:"LatestStreamLabel,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` - SSEDescription *DuploDynamoDBTableV2SSESpecification `json:"SSEDescription,omitempty"` + SSEDescription *DuploDynamoDBTableV2SSESpecificationResponse `json:"SSEDescription,omitempty"` StreamSpecification *DuploDynamoDBTableV2StreamSpecification `json:"StreamSpecification,omitempty"` BillingModeSummary *DuploDynamoDBTableV2BillingModeSummary `json:"BillingModeSummary,omitempty"` } @@ -74,14 +73,14 @@ type DuploDynamoDBTableV2TimeInRecovery struct { } type DuploDynamoDBTableV2ContinuousBackupsDescription struct { - LatestRestorableDateTime time.Time `json:"LatestRestorableDateTime,omitempty"` + //LatestRestorableDateTime time.Time `json:"LatestRestorableDateTime,omitempty"` ContinuousBackupsStatus struct { Value string `json:"Value,omitempty"` } `json:"ContinuousBackupsStatus,omitempty"` PointInTimeRecoveryDescription struct { - EarliestRestorableDateTime time.Time `json:"EarliestRestorableDateTime,omitempty"` + EarliestRestorableDateTime string `json:"EarliestRestorableDateTime,omitempty"` } `json:"PointInTimeRecoveryDescription,omitempty"` PointInTimeRecoveryStatus struct { @@ -144,6 +143,13 @@ type DuploDynamoDBTableV2SSESpecification struct { KMSMasterKeyArn string `json:"KMSMasterKeyArn,omitempty"` } +type DuploDynamoDBTableV2SSESpecificationResponse struct { + Status DuploStringValue `json:"Status,omitempty"` + KMSMasterKeyId string `json:"KMSMasterKeyId,omitempty"` + SSEType *DuploStringValue `json:"SSEType,omitempty"` + KMSMasterKeyArn string `json:"KMSMasterKeyArn,omitempty"` +} + type DuploDynamoDBTableV2Projection struct { NonKeyAttributes []string `json:"NonKeyAttributes,omitempty"` ProjectionType string `json:"ProjectionType,omitempty"` @@ -171,6 +177,7 @@ type DuploDynamoDBTableV2GlobalSecondaryIndex struct { Projection *DuploDynamoDBTableV2Projection `json:"Projection,omitempty"` KeySchema *[]DuploDynamoDBKeySchema `json:"KeySchema,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` + DeleteIndex bool `json:"-"` } type DuploDynamoDBTableV2GlobalSecondaryIndexResponse struct { @@ -269,19 +276,13 @@ func (c *Client) DynamoDBTableUpdateV2( func (c *Client) DynamoDBTableUpdateGSIV2( tenantID string, - rq *DuploDynamoDBTableRequestV2) (*DuploDynamoDBTableV2, ClientError) { + rq *ModifyGSI) (*DuploDynamoDBTableV2, ClientError) { rp := DuploDynamoDBTableV2{} - u := UpdateGSI{ - rq.GlobalSecondaryIndexes, - } - ur := UpdateGSIReq{ - u, - } err := c.putAPI( fmt.Sprintf("DynamoDBTableUpdate(%s, %s)", tenantID, rq.TableName), - fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2/%s/globalSecondaryIndex", tenantID, rq.TableName), - &ur, + fmt.Sprintf("v3/subscriptions/%s/aws/dynamodbTableV2/%s", tenantID, rq.TableName), + &rq, &rp, ) rp.TenantID = tenantID @@ -399,3 +400,23 @@ func (c *Client) DuploDynamoDBTableV2UpdateDeletionProtection( return c.DynamoDBTableUpdateV2(tenantID, r) } + +type ModifyGSI struct { + TableName string `json:"TableName"` + AttributeDefinitions []DuploDynamoDBAttributeDefinionV2 `json:"AttributeDefinitions,omitempty"` + GlobalSecondaryIndexUpdates []GlobalSecondaryIndexUpdates +} +type GlobalSecondaryIndexUpdates struct { + Create *DuploDynamoDBTableV2GlobalSecondaryIndex `json:"Create,omitempty"` + Delete *Delete `json:"Delete,omitempty"` + Update *Update `json:"Update,omitempty"` +} + +type Delete struct { + IndexName string `json:"IndexName"` +} + +type Update struct { + IndexName string `json:"IndexName"` + ProvisionedThroughput DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput"` +} diff --git a/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf b/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf index b014aca7..643e8dfb 100644 --- a/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf +++ b/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf @@ -56,3 +56,80 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { } } + + +resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { + + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mytable" + read_capacity = 80 + write_capacity = 40 + billing_mode = "PROVISIONED" + is_point_in_time_recovery = false + deletion_protection_enabled = false + + tag { + key = "school" + value = "admission" + } + attribute { + name = "ForumName" + type = "S" + } + attribute { + name = "Subject" + type = "S" + } + attribute { + name = "LastPostDateTime" + type = "S" + } + attribute { + name = "PostMonth" + type = "S" + } + + attribute { + name = "GamerZone" + type = "S" + } + attribute { + name = "TopScore" + type = "N" + } + key_schema { + attribute_name = "ForumName" + key_type = "HASH" + } + key_schema { + attribute_name = "Subject" + key_type = "RANGE" + } + global_secondary_index { + name = "PostDate" + hash_key = "PostMonth" + range_key = "LastPostDateTime" + write_capacity = 2 + read_capacity = 2 + projection_type = "KEYS_ONLY" + } + global_secondary_index { + name = "GamerZone" + hash_key = "GamerZone" + range_key = "TopScore" + write_capacity = 5 + read_capacity = 5 + projection_type = "ALL" + #delete_index = true #To remove an global secondary index from associated table + } + server_side_encryption { + enabled = false + } + local_secondary_index { #local secondary index doesnot support updation + hash_key = "ForumName" + name = "LastPostIndex" + range_key = "LastPostDateTime" + projection_type = "KEYS_ONLY" + } + +} From 03bb6bea1b0639c9ab62c600fa61d07cafd3c5d6 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 4 Jun 2024 18:12:09 +0530 Subject: [PATCH 08/35] working on untag resource, made attribute definition and tag_value required field and updated doc --- docs/resources/aws_dynamodb_table_v2.md | 26 ++-- .../resource_duplo_aws_dynamodb_table_v2.go | 53 ++++++-- duplocloud/utils.go | 20 +++ duplosdk/aws_dynamo_db.go | 8 +- terraform copy.tfstate | 122 ++++++++++++++++++ terraform.tfstate copy.backup | 121 +++++++++++++++++ 6 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 terraform copy.tfstate create mode 100644 terraform.tfstate copy.backup diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index a470d7d4..df968fcb 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -155,17 +155,17 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { ### Required +- `attribute` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--attribute)) +- `key_schema` (Block List, Min: 1) (see [below for nested schema](#nestedblock--key_schema)) - `name` (String) The name of the table, this needs to be unique within a region. - `tenant_id` (String) The GUID of the tenant that the dynamodb table will be created in. ### Optional -- `attribute` (Block Set) (see [below for nested schema](#nestedblock--attribute)) - `billing_mode` (String) Controls how you are charged for read and write throughput and how you manage capacity. The valid values are `PROVISIONED` and `PAY_PER_REQUEST`. Defaults to `PROVISIONED`. - `deletion_protection_enabled` (Boolean) Deletion protection keeps the tables from being deleted unintentionally. While this setting is on, you can't delete the table. - `global_secondary_index` (Block Set) Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc. (see [below for nested schema](#nestedblock--global_secondary_index)) - `is_point_in_time_recovery` (Boolean) The point in time recovery status of the dynamodb table. Enabled if true. -- `key_schema` (Block List) (see [below for nested schema](#nestedblock--key_schema)) - `local_secondary_index` (Block Set) (see [below for nested schema](#nestedblock--local_secondary_index)) - `read_capacity` (Number) The number of read units for this table. If the `billing_mode` is `PROVISIONED`, this field is required. - `server_side_encryption` (Block List, Max: 1) Encryption at rest options. AWS DynamoDB tables are automatically encrypted at rest with an AWS owned Customer Master Key if this argument isn't specified. (see [below for nested schema](#nestedblock--server_side_encryption)) @@ -194,6 +194,15 @@ Required: - `type` (String) Attribute type, which must be a scalar type: `S`, `N`, or `B` for (S)tring, (N)umber or (B)inary data + +### Nested Schema for `key_schema` + +Required: + +- `attribute_name` (String) The name of the attribute +- `key_type` (String) Applicable key types are `HASH` or `RANGE`. + + ### Nested Schema for `global_secondary_index` @@ -212,15 +221,6 @@ Optional: - `write_capacity` (Number) The number of write units for this index. Must be set if `billing_mode` is set to `PROVISIONED`. - -### Nested Schema for `key_schema` - -Required: - -- `attribute_name` (String) The name of the attribute -- `key_type` (String) Applicable key types are `HASH` or `RANGE`. - - ### Nested Schema for `local_secondary_index` @@ -256,6 +256,10 @@ Required: - `key` (String) - `value` (String) +Optional: + +- `delete_tag` (Boolean) Defaults to `false`. + ### Nested Schema for `timeouts` diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index edb44551..f385eff1 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -80,13 +80,12 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Type: schema.TypeList, Optional: true, Computed: true, - Elem: KeyValueSchema(), + Elem: DynamoDbV2TagSchema(), }, "attribute": { Type: schema.TypeSet, - Optional: true, - Computed: true, + Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -105,8 +104,7 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { "key_schema": { Type: schema.TypeList, - Optional: true, - Computed: true, + Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "attribute_name": { @@ -491,15 +489,30 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } - + tagsToUpdate := []duplosdk.DuploKeyStringValue{} + tagsToDelete := []string{} + for _, v := range *rq.Tags { + if v.DeleteTag { + tagsToDelete = append(tagsToDelete, v.Key) + } else { + tagsToUpdate = append(tagsToUpdate, duplosdk.DuploKeyStringValue{ + Key: v.Key, + Value: v.Value, + }) + } + } tagReq := &duplosdk.DuploDynamoDBTagResourceRequest{ ResourceArn: existing.TableArn, - Tags: rq.Tags, + Tags: &tagsToUpdate, } - _, err = tagDynamoDBtTableV2(tenantID, tagReq, m) + //if len(tagsToDelete) > 0 { + // tagReq.TagKeys = &tagsToDelete + //} + _, err = tagDynamoDBtTableV2(tenantID, tagReq, m) //taging and untaging dynamodb if err != nil { return diag.FromErr(err) } + diagErr := updatePointInTimeRecovery(c, d, tenantID, fullname) if diagErr != nil { return diagErr @@ -611,12 +624,32 @@ func resourceAwsDynamoDBTableDeleteV2(ctx context.Context, d *schema.ResourceDat return nil } +func expandTags(fieldName string, d *schema.ResourceData) *[]duplosdk.DyanmoDbV2Tag { + var ary []duplosdk.DyanmoDbV2Tag + + if v, ok := d.GetOk(fieldName); ok && v != nil && len(v.([]interface{})) > 0 { + kvs := v.([]interface{}) + log.Printf("[TRACE] duploKeyValueFromState ********: found %s", fieldName) + ary = make([]duplosdk.DyanmoDbV2Tag, 0, len(kvs)) + for _, raw := range kvs { + kv := raw.(map[string]interface{}) + ary = append(ary, duplosdk.DyanmoDbV2Tag{ + Key: kv["key"].(string), + Value: kv["value"].(string), + DeleteTag: kv["delete_tag"].(bool), + }) + } + } + + return &ary +} + func expandDynamoDBTable(d *schema.ResourceData) (*duplosdk.DuploDynamoDBTableRequestV2, error) { req := &duplosdk.DuploDynamoDBTableRequestV2{ TableName: d.Get("name").(string), BillingMode: d.Get("billing_mode").(string), - Tags: keyValueFromState("tag", d), + Tags: expandTags("tag", d), KeySchema: expandDynamoDBKeySchema(d), } @@ -1047,6 +1080,8 @@ func shouldUpdateSSESepecification( ) bool { if table.SSEDescription == nil && request.SSESpecification == nil { return false + } else if table.SSEDescription == nil && request.SSESpecification != nil { + return request.SSESpecification.Enabled } status := "DISABLED" if request.SSESpecification.Enabled { diff --git a/duplocloud/utils.go b/duplocloud/utils.go index e19b127e..81e5013d 100644 --- a/duplocloud/utils.go +++ b/duplocloud/utils.go @@ -213,6 +213,26 @@ func KeyValueSchema() *schema.Resource { } } +func DynamoDbV2TagSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "delete_tag": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + } +} + // CustomDataExSchema returns a Terraform schema to represent a key value pair with a type func CustomDataExSchema() *schema.Resource { return &schema.Resource{ diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 83cac72b..58cb5648 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -200,7 +200,7 @@ type DuploDynamoDBTableRequestV2 struct { TableName string `json:"TableName"` BillingMode string `json:"BillingMode,omitempty"` DeletionProtectionEnabled *bool `json:"DeletionProtectionEnabled,omitempty"` - Tags *[]DuploKeyStringValue `json:"Tags,omitempty"` + Tags *[]DyanmoDbV2Tag `json:"Tags,omitempty"` KeySchema *[]DuploDynamoDBKeySchemaV2 `json:"KeySchema,omitempty"` AttributeDefinitions *[]DuploDynamoDBAttributeDefinionV2 `json:"AttributeDefinitions,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` @@ -210,9 +210,15 @@ type DuploDynamoDBTableRequestV2 struct { GlobalSecondaryIndexes *[]DuploDynamoDBTableV2GlobalSecondaryIndex `json:"GlobalSecondaryIndexes,omitempty"` } +type DyanmoDbV2Tag struct { + Key string `json:"Key"` + Value string `json:"Value,omitempty"` + DeleteTag bool `json:"-"` +} type DuploDynamoDBTagResourceRequest struct { ResourceArn string `json:"ResourceArn,omitempty"` // The ARN of the resource to tag Tags *[]DuploKeyStringValue `json:"Tags,omitempty"` // A list of tags to associate with the resource + TagKeys *[]string `json:"TagKeys,omitempty"` } type DuploDynamoDBTagResourceResponse struct { diff --git a/terraform copy.tfstate b/terraform copy.tfstate new file mode 100644 index 00000000..667472f6 --- /dev/null +++ b/terraform copy.tfstate @@ -0,0 +1,122 @@ +{ + "version": 4, + "terraform_version": "1.5.7", + "serial": 93, + "lineage": "c47c6917-e98d-5f34-3628-8fb99687e564", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "duplocloud_aws_dynamodb_table_v2", + "name": "tst-dynamodb-table", + "provider": "provider[\"registry.terraform.io/duplocloud/duplocloud\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:dynamodb:us-west-2:182680712604:table/duploservices-nikhiltest-dynamo13New", + "attribute": [ + { + "name": "ForumName", + "type": "S" + }, + { + "name": "GamerZone", + "type": "S" + }, + { + "name": "LastPostDateTime", + "type": "S" + }, + { + "name": "PostMonth", + "type": "S" + }, + { + "name": "Subject", + "type": "S" + }, + { + "name": "TopScore", + "type": "N" + } + ], + "billing_mode": "PROVISIONED", + "deletion_protection_enabled": false, + "fullname": "duploservices-nikhiltest-dynamo13New", + "global_secondary_index": [ + { + "delete_index": false, + "hash_key": "GamerZone", + "name": "GamerZone", + "non_key_attributes": [], + "projection_type": "ALL", + "range_key": "TopScore", + "read_capacity": 5, + "write_capacity": 5 + }, + { + "delete_index": false, + "hash_key": "PostMonth", + "name": "PostDate", + "non_key_attributes": [], + "projection_type": "KEYS_ONLY", + "range_key": "LastPostDateTime", + "read_capacity": 2, + "write_capacity": 2 + } + ], + "id": "333b86cd-5043-433e-8c58-d32267a44610/dynamo13New", + "is_point_in_time_recovery": false, + "key_schema": [ + { + "attribute_name": "ForumName", + "key_type": "HASH" + }, + { + "attribute_name": "Subject", + "key_type": "RANGE" + } + ], + "local_secondary_index": [ + { + "hash_key": "", + "name": "LastPostIndex", + "non_key_attributes": [], + "projection_type": "KEYS_ONLY", + "range_key": "LastPostDateTime" + } + ], + "name": "dynamo13New", + "read_capacity": 80, + "server_side_encryption": [], + "status": "ACTIVE", + "stream_arn": "", + "stream_enabled": false, + "stream_label": "", + "stream_view_type": "", + "tag": [ + { + "delete_tag": true, + "key": "trinity", + "value": "bomb" + }, + { + "delete_tag": false, + "key": "school", + "value": "student" + } + ], + "tenant_id": "333b86cd-5043-433e-8c58-d32267a44610", + "timeouts": null, + "wait_until_ready": true, + "write_capacity": 40 + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozNjAwMDAwMDAwMDAwLCJkZWxldGUiOjkwMDAwMDAwMDAwMH19" + } + ] + } + ], + "check_results": null +} diff --git a/terraform.tfstate copy.backup b/terraform.tfstate copy.backup new file mode 100644 index 00000000..14e39e40 --- /dev/null +++ b/terraform.tfstate copy.backup @@ -0,0 +1,121 @@ +{ + "version": 4, + "terraform_version": "1.5.7", + "serial": 91, + "lineage": "c47c6917-e98d-5f34-3628-8fb99687e564", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "duplocloud_aws_dynamodb_table_v2", + "name": "tst-dynamodb-table", + "provider": "provider[\"registry.terraform.io/duplocloud/duplocloud\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:dynamodb:us-west-2:182680712604:table/duploservices-nikhiltest-dynamo13New", + "attribute": [ + { + "name": "ForumName", + "type": "S" + }, + { + "name": "GamerZone", + "type": "S" + }, + { + "name": "LastPostDateTime", + "type": "S" + }, + { + "name": "PostMonth", + "type": "S" + }, + { + "name": "Subject", + "type": "S" + }, + { + "name": "TopScore", + "type": "N" + } + ], + "billing_mode": "PROVISIONED", + "deletion_protection_enabled": false, + "fullname": "duploservices-nikhiltest-dynamo13New", + "global_secondary_index": [ + { + "delete_index": false, + "hash_key": "GamerZone", + "name": "GamerZone", + "non_key_attributes": [], + "projection_type": "ALL", + "range_key": "TopScore", + "read_capacity": 5, + "write_capacity": 5 + }, + { + "delete_index": false, + "hash_key": "PostMonth", + "name": "PostDate", + "non_key_attributes": [], + "projection_type": "KEYS_ONLY", + "range_key": "LastPostDateTime", + "read_capacity": 2, + "write_capacity": 2 + } + ], + "id": "333b86cd-5043-433e-8c58-d32267a44610/dynamo13New", + "is_point_in_time_recovery": false, + "key_schema": [ + { + "attribute_name": "ForumName", + "key_type": "HASH" + }, + { + "attribute_name": "Subject", + "key_type": "RANGE" + } + ], + "local_secondary_index": [ + { + "hash_key": "", + "name": "LastPostIndex", + "non_key_attributes": [], + "projection_type": "KEYS_ONLY", + "range_key": "LastPostDateTime" + } + ], + "name": "dynamo13New", + "read_capacity": 80, + "server_side_encryption": [], + "status": "ACTIVE", + "stream_arn": "", + "stream_enabled": false, + "stream_label": "", + "stream_view_type": "", + "tag": [ + { + "delete_tag": true, + "key": "trinity", + "value": "bomb" + }, + { + "delete_tag": false, + "key": "school", + "value": "student" + } + ], + "tenant_id": "333b86cd-5043-433e-8c58-d32267a44610", + "timeouts": null, + "wait_until_ready": true, + "write_capacity": 40 + }, + "sensitive_attributes": [] + } + ] + } + ], + "check_results": null +} From 92f4e48b8064070d1c8fa9523168cd00b6d7eed8 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 6 Jun 2024 11:26:15 +0530 Subject: [PATCH 09/35] commented untag logic --- .../resource_duplo_aws_dynamodb_table_v2.go | 26 ++-- terraform copy.tfstate | 122 ------------------ terraform.tfstate copy.backup | 121 ----------------- 3 files changed, 13 insertions(+), 256 deletions(-) delete mode 100644 terraform copy.tfstate delete mode 100644 terraform.tfstate copy.backup diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index f385eff1..9a9a691e 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -80,7 +80,7 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Type: schema.TypeList, Optional: true, Computed: true, - Elem: DynamoDbV2TagSchema(), + Elem: KeyValueSchema(), //DynamoDbV2TagSchema(), }, "attribute": { @@ -490,16 +490,16 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat return diag.FromErr(err) } tagsToUpdate := []duplosdk.DuploKeyStringValue{} - tagsToDelete := []string{} + //tagsToDelete := []string{} for _, v := range *rq.Tags { - if v.DeleteTag { - tagsToDelete = append(tagsToDelete, v.Key) - } else { - tagsToUpdate = append(tagsToUpdate, duplosdk.DuploKeyStringValue{ - Key: v.Key, - Value: v.Value, - }) - } + // if v.DeleteTag { + // tagsToDelete = append(tagsToDelete, v.Key) //waiting for backend change + // } else { + tagsToUpdate = append(tagsToUpdate, duplosdk.DuploKeyStringValue{ + Key: v.Key, + Value: v.Value, + }) + // } } tagReq := &duplosdk.DuploDynamoDBTagResourceRequest{ ResourceArn: existing.TableArn, @@ -634,9 +634,9 @@ func expandTags(fieldName string, d *schema.ResourceData) *[]duplosdk.DyanmoDbV2 for _, raw := range kvs { kv := raw.(map[string]interface{}) ary = append(ary, duplosdk.DyanmoDbV2Tag{ - Key: kv["key"].(string), - Value: kv["value"].(string), - DeleteTag: kv["delete_tag"].(bool), + Key: kv["key"].(string), + Value: kv["value"].(string), + // DeleteTag: kv["delete_tag"].(bool), }) } } diff --git a/terraform copy.tfstate b/terraform copy.tfstate deleted file mode 100644 index 667472f6..00000000 --- a/terraform copy.tfstate +++ /dev/null @@ -1,122 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.5.7", - "serial": 93, - "lineage": "c47c6917-e98d-5f34-3628-8fb99687e564", - "outputs": {}, - "resources": [ - { - "mode": "managed", - "type": "duplocloud_aws_dynamodb_table_v2", - "name": "tst-dynamodb-table", - "provider": "provider[\"registry.terraform.io/duplocloud/duplocloud\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "arn": "arn:aws:dynamodb:us-west-2:182680712604:table/duploservices-nikhiltest-dynamo13New", - "attribute": [ - { - "name": "ForumName", - "type": "S" - }, - { - "name": "GamerZone", - "type": "S" - }, - { - "name": "LastPostDateTime", - "type": "S" - }, - { - "name": "PostMonth", - "type": "S" - }, - { - "name": "Subject", - "type": "S" - }, - { - "name": "TopScore", - "type": "N" - } - ], - "billing_mode": "PROVISIONED", - "deletion_protection_enabled": false, - "fullname": "duploservices-nikhiltest-dynamo13New", - "global_secondary_index": [ - { - "delete_index": false, - "hash_key": "GamerZone", - "name": "GamerZone", - "non_key_attributes": [], - "projection_type": "ALL", - "range_key": "TopScore", - "read_capacity": 5, - "write_capacity": 5 - }, - { - "delete_index": false, - "hash_key": "PostMonth", - "name": "PostDate", - "non_key_attributes": [], - "projection_type": "KEYS_ONLY", - "range_key": "LastPostDateTime", - "read_capacity": 2, - "write_capacity": 2 - } - ], - "id": "333b86cd-5043-433e-8c58-d32267a44610/dynamo13New", - "is_point_in_time_recovery": false, - "key_schema": [ - { - "attribute_name": "ForumName", - "key_type": "HASH" - }, - { - "attribute_name": "Subject", - "key_type": "RANGE" - } - ], - "local_secondary_index": [ - { - "hash_key": "", - "name": "LastPostIndex", - "non_key_attributes": [], - "projection_type": "KEYS_ONLY", - "range_key": "LastPostDateTime" - } - ], - "name": "dynamo13New", - "read_capacity": 80, - "server_side_encryption": [], - "status": "ACTIVE", - "stream_arn": "", - "stream_enabled": false, - "stream_label": "", - "stream_view_type": "", - "tag": [ - { - "delete_tag": true, - "key": "trinity", - "value": "bomb" - }, - { - "delete_tag": false, - "key": "school", - "value": "student" - } - ], - "tenant_id": "333b86cd-5043-433e-8c58-d32267a44610", - "timeouts": null, - "wait_until_ready": true, - "write_capacity": 40 - }, - "sensitive_attributes": [], - "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozNjAwMDAwMDAwMDAwLCJkZWxldGUiOjkwMDAwMDAwMDAwMH19" - } - ] - } - ], - "check_results": null -} diff --git a/terraform.tfstate copy.backup b/terraform.tfstate copy.backup deleted file mode 100644 index 14e39e40..00000000 --- a/terraform.tfstate copy.backup +++ /dev/null @@ -1,121 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.5.7", - "serial": 91, - "lineage": "c47c6917-e98d-5f34-3628-8fb99687e564", - "outputs": {}, - "resources": [ - { - "mode": "managed", - "type": "duplocloud_aws_dynamodb_table_v2", - "name": "tst-dynamodb-table", - "provider": "provider[\"registry.terraform.io/duplocloud/duplocloud\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "arn": "arn:aws:dynamodb:us-west-2:182680712604:table/duploservices-nikhiltest-dynamo13New", - "attribute": [ - { - "name": "ForumName", - "type": "S" - }, - { - "name": "GamerZone", - "type": "S" - }, - { - "name": "LastPostDateTime", - "type": "S" - }, - { - "name": "PostMonth", - "type": "S" - }, - { - "name": "Subject", - "type": "S" - }, - { - "name": "TopScore", - "type": "N" - } - ], - "billing_mode": "PROVISIONED", - "deletion_protection_enabled": false, - "fullname": "duploservices-nikhiltest-dynamo13New", - "global_secondary_index": [ - { - "delete_index": false, - "hash_key": "GamerZone", - "name": "GamerZone", - "non_key_attributes": [], - "projection_type": "ALL", - "range_key": "TopScore", - "read_capacity": 5, - "write_capacity": 5 - }, - { - "delete_index": false, - "hash_key": "PostMonth", - "name": "PostDate", - "non_key_attributes": [], - "projection_type": "KEYS_ONLY", - "range_key": "LastPostDateTime", - "read_capacity": 2, - "write_capacity": 2 - } - ], - "id": "333b86cd-5043-433e-8c58-d32267a44610/dynamo13New", - "is_point_in_time_recovery": false, - "key_schema": [ - { - "attribute_name": "ForumName", - "key_type": "HASH" - }, - { - "attribute_name": "Subject", - "key_type": "RANGE" - } - ], - "local_secondary_index": [ - { - "hash_key": "", - "name": "LastPostIndex", - "non_key_attributes": [], - "projection_type": "KEYS_ONLY", - "range_key": "LastPostDateTime" - } - ], - "name": "dynamo13New", - "read_capacity": 80, - "server_side_encryption": [], - "status": "ACTIVE", - "stream_arn": "", - "stream_enabled": false, - "stream_label": "", - "stream_view_type": "", - "tag": [ - { - "delete_tag": true, - "key": "trinity", - "value": "bomb" - }, - { - "delete_tag": false, - "key": "school", - "value": "student" - } - ], - "tenant_id": "333b86cd-5043-433e-8c58-d32267a44610", - "timeouts": null, - "wait_until_ready": true, - "write_capacity": 40 - }, - "sensitive_attributes": [] - } - ] - } - ], - "check_results": null -} From e50549b4a23539f68b406ad1adf1bb501fb1bf91 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 6 Jun 2024 11:37:13 +0530 Subject: [PATCH 10/35] updated doc --- docs/resources/aws_dynamodb_table_v2.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index df968fcb..521fc16d 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -256,10 +256,6 @@ Required: - `key` (String) - `value` (String) -Optional: - -- `delete_tag` (Boolean) Defaults to `false`. - ### Nested Schema for `timeouts` From 51ed769285fa48ae463a1bceb5df6aa35ffd38da Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 6 Jun 2024 11:40:47 +0530 Subject: [PATCH 11/35] fixed lint issue --- duplocloud/utils.go | 56 +++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/duplocloud/utils.go b/duplocloud/utils.go index 81e5013d..7c12c284 100644 --- a/duplocloud/utils.go +++ b/duplocloud/utils.go @@ -581,41 +581,43 @@ func waitForResourceToBePresentAfterCreate(ctx context.Context, d *schema.Resour return nil } +/* func waitForResourceToBePresentAfterUpdate( - ctx context.Context, - d *schema.ResourceData, - resourceType string, - resourceId string, - getResource func() (interface{}, duplosdk.ClientError)) diag.Diagnostics { - err := retry.RetryContext(ctx, d.Timeout("update"), func() *retry.RetryError { - resource, errGet := getResource() - - if errGet != nil { - if errGet.Status() == 404 { - s := "expected %s '%s' to be present after update, but got a 404" + + ctx context.Context, + d *schema.ResourceData, + resourceType string, + resourceId string, + getResource func() (interface{}, duplosdk.ClientError)) diag.Diagnostics { + err := retry.RetryContext(ctx, d.Timeout("update"), func() *retry.RetryError { + resource, errGet := getResource() + + if errGet != nil { + if errGet.Status() == 404 { + s := "expected %s '%s' to be present after update, but got a 404" + e := fmt.Errorf(s, resourceType, resourceId) + return retry.RetryableError(e) + } + + s := "error retrieving %s '%s': %s" + e := fmt.Errorf(s, resourceType, resourceId, errGet) + return retry.NonRetryableError(e) + } + + if isInterfaceNil(resource) { + s := "expected %s '%s' to be present after update, but got: nil" e := fmt.Errorf(s, resourceType, resourceId) return retry.RetryableError(e) } - s := "error retrieving %s '%s': %s" - e := fmt.Errorf(s, resourceType, resourceId, errGet) - return retry.NonRetryableError(e) - } - - if isInterfaceNil(resource) { - s := "expected %s '%s' to be present after update, but got: nil" - e := fmt.Errorf(s, resourceType, resourceId) - return retry.RetryableError(e) + return nil + }) + if err != nil { + return diag.Errorf("error updating %s '%s': %s", resourceType, resourceId, err) } - return nil - }) - if err != nil { - return diag.Errorf("error updating %s '%s': %s", resourceType, resourceId, err) } - return nil -} - +*/ func isInterfaceNil(v interface{}) bool { return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) } From 6456b40040f2bdc58e962a61e485aae74dd58617 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 6 Jun 2024 12:03:18 +0530 Subject: [PATCH 12/35] removed ineffectual assignment to t --- duplocloud/resource_duplo_aws_dynamodb_table_v2.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 9a9a691e..92adf755 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -1015,8 +1015,12 @@ func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoD } for _, r := range *request.GlobalSecondaryIndexes { if ev, ok := existingIndex[r.IndexName]; !ok { - t := duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{} - t = r + t := duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{ + IndexName: r.IndexName, + Projection: r.Projection, + KeySchema: r.KeySchema, + ProvisionedThroughput: r.ProvisionedThroughput, + } cr := duplosdk.GlobalSecondaryIndexUpdates{ Create: &t, } From 846a828399322c4a9558977e53bfcc3c41719f78 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 11 Jun 2024 12:24:08 +0530 Subject: [PATCH 13/35] dynamo suppress fix for localsecondaryindex and fullname fix for deletecontext --- duplocloud/resource_duplo_aws_dynamodb_table_v2.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 92adf755..eb9e8c50 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -180,7 +180,7 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Type: schema.TypeSet, Optional: true, ForceNew: true, - DiffSuppressFunc: diffSuppressFuncIgnore, + DiffSuppressFunc: diffSuppressWhenNotCreating, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -596,12 +596,12 @@ func updateDeleteProtection(c *duplosdk.Client, d *schema.ResourceData, tenantID func resourceAwsDynamoDBTableDeleteV2(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { id := d.Id() - tenantID, name, err := parseAwsDynamoDBTableIdParts(id) + tenantID, _, err := parseAwsDynamoDBTableIdParts(id) if err != nil { return diag.FromErr(err) } log.Printf("[TRACE] resourceAwsDynamoDBTableDeleteV2(%s, %s): start", tenantID, name) - + name := d.Get("fullname").(string) // Delete the function. c := m.(*duplosdk.Client) clientErr := c.DynamoDBTableDeleteV2(tenantID, name) From ad6bb1e79a8f333b655adf21b88efb0c7e1941a6 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 11 Jun 2024 21:19:18 +0530 Subject: [PATCH 14/35] fixed lint issue --- duplocloud/resource_duplo_aws_dynamodb_table_v2.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index eb9e8c50..62e450f8 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -600,8 +600,9 @@ func resourceAwsDynamoDBTableDeleteV2(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } - log.Printf("[TRACE] resourceAwsDynamoDBTableDeleteV2(%s, %s): start", tenantID, name) name := d.Get("fullname").(string) + + log.Printf("[TRACE] resourceAwsDynamoDBTableDeleteV2(%s, %s): start", tenantID, name) // Delete the function. c := m.(*duplosdk.Client) clientErr := c.DynamoDBTableDeleteV2(tenantID, name) From 7152ee9a5b17e28408906158bd030323cd545ef8 Mon Sep 17 00:00:00 2001 From: nikhil Date: Fri, 21 Jun 2024 12:27:12 +0530 Subject: [PATCH 15/35] under progress --- docs/data-sources/asg_profiles.md | 2 +- docs/data-sources/native_hosts.md | 2 +- docs/resources/asg_profile.md | 2 +- docs/resources/aws_host.md | 2 +- docs/resources/s3_bucket.md | 1 + duplocloud/resource_duplo_s3_bucket.go | 11 +++++++++++ duplosdk/tenant_aws_cloud_resources.go | 2 ++ 7 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/data-sources/asg_profiles.md b/docs/data-sources/asg_profiles.md index 310d94df..2299a37e 100644 --- a/docs/data-sources/asg_profiles.md +++ b/docs/data-sources/asg_profiles.md @@ -52,7 +52,7 @@ Read-Only: - `minion_tags` (List of Object) (see [below for nested schema](#nestedobjatt--asg_profiles--minion_tags)) - `network_interface` (List of Object) (see [below for nested schema](#nestedobjatt--asg_profiles--network_interface)) - `prepend_user_data` (Boolean) -- `puplic_ip_address` (String) +- `public_ip_address` (String) - `tags` (List of Object) (see [below for nested schema](#nestedobjatt--asg_profiles--tags)) - `tenant_id` (String) - `use_spot_instances` (Boolean) diff --git a/docs/data-sources/native_hosts.md b/docs/data-sources/native_hosts.md index b6618417..ff2ede95 100644 --- a/docs/data-sources/native_hosts.md +++ b/docs/data-sources/native_hosts.md @@ -48,7 +48,7 @@ Read-Only: - `network_interface` (List of Object) (see [below for nested schema](#nestedobjatt--hosts--network_interface)) - `prepend_user_data` (Boolean) - `private_ip_address` (String) -- `puplic_ip_address` (String) +- `public_ip_address` (String) - `status` (String) - `tags` (List of Object) (see [below for nested schema](#nestedobjatt--hosts--tags)) - `tenant_id` (String) diff --git a/docs/resources/asg_profile.md b/docs/resources/asg_profile.md index 2f8370ad..e3d95376 100644 --- a/docs/resources/asg_profile.md +++ b/docs/resources/asg_profile.md @@ -86,7 +86,7 @@ resource "duplocloud_asg_profile" "duplo-test-asg" { - `fullname` (String) The full name of the ASG profile. - `id` (String) The ID of this resource. - `initial_base64_user_data` (String) -- `puplic_ip_address` (String) The primary public IP address assigned to the host. +- `public_ip_address` (String) The primary public IP address assigned to the host. ### Nested Schema for `metadata` diff --git a/docs/resources/aws_host.md b/docs/resources/aws_host.md index 7abbe58c..9dd4c6fc 100644 --- a/docs/resources/aws_host.md +++ b/docs/resources/aws_host.md @@ -127,7 +127,7 @@ resource "duplocloud_aws_host" "host" { - `initial_base64_user_data` (String) - `instance_id` (String) The AWS EC2 instance ID of the host. - `private_ip_address` (String) The primary private IP address assigned to the host. -- `puplic_ip_address` (String) The primary public IP address assigned to the host. +- `public_ip_address` (String) The primary public IP address assigned to the host. - `status` (String) The current status of the host. diff --git a/docs/resources/s3_bucket.md b/docs/resources/s3_bucket.md index adcfaf7f..42153ee7 100644 --- a/docs/resources/s3_bucket.md +++ b/docs/resources/s3_bucket.md @@ -72,6 +72,7 @@ resource "duplocloud_s3_bucket" "mydata" { - `default_encryption` (Block List, Max: 1) Default encryption settings for objects uploaded to the bucket. (see [below for nested schema](#nestedblock--default_encryption)) - `enable_access_logs` (Boolean) Whether or not to enable access logs. When enabled, Duplo will send access logs to a centralized S3 bucket per plan. - `enable_versioning` (Boolean) Whether or not to enable versioning. +- `location` (String) The location is to set multi region, applicable for gcp cloud. - `managed_policies` (List of String) Duplo can manage your S3 bucket policy for you, based on simple list of policy keywords: - `"ssl"`: Require SSL / HTTPS when accessing the bucket. diff --git a/duplocloud/resource_duplo_s3_bucket.go b/duplocloud/resource_duplo_s3_bucket.go index 5b8dfd9e..d2d1277c 100644 --- a/duplocloud/resource_duplo_s3_bucket.go +++ b/duplocloud/resource_duplo_s3_bucket.go @@ -114,6 +114,12 @@ func s3BucketSchema() map[string]*schema.Schema { Elem: &schema.Schema{Type: schema.TypeString}, }, "tags": awsTagsKeyValueSchemaComputed(), + "location": { + Description: "The location is to set multi region, applicable for gcp cloud.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, } } @@ -425,6 +431,7 @@ func resourceS3BucketSetData(d *schema.ResourceData, tenantID string, name strin d.Set("managed_policies", duplo.Policies) d.Set("tags", keyValueToState("tags", duplo.Tags)) d.Set("region", duplo.Region) + d.Set("location", duplo.Location) } func fillS3BucketRequest(duploObject *duplosdk.DuploS3BucketSettingsRequest, d *schema.ResourceData) error { @@ -458,6 +465,10 @@ func fillS3BucketRequest(duploObject *duplosdk.DuploS3BucketSettingsRequest, d * duploObject.Region = v.(string) } + if v, ok := d.GetOk("location"); ok && v != nil { + duploObject.Location = v.(string) + } + // Set the managed policies. if v, ok := getAsStringArray(d, "managed_policies"); ok && v != nil { duploObject.Policies = *v diff --git a/duplosdk/tenant_aws_cloud_resources.go b/duplosdk/tenant_aws_cloud_resources.go index 85a860d5..54f819e1 100644 --- a/duplosdk/tenant_aws_cloud_resources.go +++ b/duplosdk/tenant_aws_cloud_resources.go @@ -82,6 +82,7 @@ type DuploS3Bucket struct { Name string `json:"Name,omitempty"` DomainName string `json:"DomainName,omitempty"` Region string `json:"Region,omitempty"` + Location string `json:"Location,omitempty"` Arn string `json:"Arn,omitempty"` MetaData string `json:"MetaData,omitempty"` EnableVersioning bool `json:"EnableVersioning,omitempty"` @@ -219,6 +220,7 @@ type DuploS3BucketRequest struct { type DuploS3BucketSettingsRequest struct { Name string `json:"Name,omitempty"` Region string `json:"Region,omitempty"` + Location string `json:"Location,omitempty"` EnableVersioning bool `json:"EnableVersioning,omitempty"` EnableAccessLogs bool `json:"EnableAccessLogs,omitempty"` AllowPublicAccess bool `json:"AllowPublicAccess,omitempty"` From 24456a33e05f858bd70dc455d8297072b5ef2522 Mon Sep 17 00:00:00 2001 From: duplo-bot Date: Tue, 25 Jun 2024 15:06:33 +0000 Subject: [PATCH 16/35] version bump --- Makefile | 2 +- examples/aws-integration/main.tf | 2 +- examples/ecs/main.tf | 2 +- examples/eks-integration/main.tf | 2 +- examples/emr/files/emr_auto_scaling/main.tf | 2 +- examples/emr/files/emr_spot/main.tf | 2 +- examples/emr/main.tf | 2 +- examples/ephemeral-storage/main.tf | 2 +- examples/infrastructure/main.tf | 2 +- examples/k8/main.tf | 2 +- examples/service/main.tf | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index a5b7b881..fe133ab7 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ NAMESPACE=duplocloud NAME=duplocloud BINARY=terraform-provider-${NAME} -VERSION=0.10.30 +VERSION=0.10.31 #mac #OS_ARCH=darwin_amd64 #OS_ARCH=linux_amd64 diff --git a/examples/aws-integration/main.tf b/examples/aws-integration/main.tf index 7445eb1f..dfe24f23 100644 --- a/examples/aws-integration/main.tf +++ b/examples/aws-integration/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } aws = { diff --git a/examples/ecs/main.tf b/examples/ecs/main.tf index 3cf82030..6cd555aa 100644 --- a/examples/ecs/main.tf +++ b/examples/ecs/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/eks-integration/main.tf b/examples/eks-integration/main.tf index c25b9222..b1d05e71 100644 --- a/examples/eks-integration/main.tf +++ b/examples/eks-integration/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } aws = { diff --git a/examples/emr/files/emr_auto_scaling/main.tf b/examples/emr/files/emr_auto_scaling/main.tf index b4ed7152..0badde80 100644 --- a/examples/emr/files/emr_auto_scaling/main.tf +++ b/examples/emr/files/emr_auto_scaling/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/emr/files/emr_spot/main.tf b/examples/emr/files/emr_spot/main.tf index 99954546..736ed791 100644 --- a/examples/emr/files/emr_spot/main.tf +++ b/examples/emr/files/emr_spot/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/emr/main.tf b/examples/emr/main.tf index 31f56122..86801472 100644 --- a/examples/emr/main.tf +++ b/examples/emr/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/ephemeral-storage/main.tf b/examples/ephemeral-storage/main.tf index 63c76d99..f126ec5b 100644 --- a/examples/ephemeral-storage/main.tf +++ b/examples/ephemeral-storage/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/infrastructure/main.tf b/examples/infrastructure/main.tf index 75e91070..62bb8ba2 100644 --- a/examples/infrastructure/main.tf +++ b/examples/infrastructure/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/k8/main.tf b/examples/k8/main.tf index 2266edd0..77a0d05d 100644 --- a/examples/k8/main.tf +++ b/examples/k8/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } diff --git a/examples/service/main.tf b/examples/service/main.tf index 4df8df4a..e70f24d7 100644 --- a/examples/service/main.tf +++ b/examples/service/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { duplocloud = { - version = "0.10.30" # RELEASE VERSION + version = "0.10.31" # RELEASE VERSION source = "registry.terraform.io/duplocloud/duplocloud" } } From 229a7d489c737a3a1d26fc4a7bf47ed3866e1b59 Mon Sep 17 00:00:00 2001 From: nikhil Date: Wed, 26 Jun 2024 15:39:34 +0530 Subject: [PATCH 17/35] inprogress --- duplocloud/resource_duplo_gcp_s3_bucket.go | 331 +++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 duplocloud/resource_duplo_gcp_s3_bucket.go diff --git a/duplocloud/resource_duplo_gcp_s3_bucket.go b/duplocloud/resource_duplo_gcp_s3_bucket.go new file mode 100644 index 00000000..2cbb9e4f --- /dev/null +++ b/duplocloud/resource_duplo_gcp_s3_bucket.go @@ -0,0 +1,331 @@ +package duplocloud + +import ( + "context" + "fmt" + "log" + "regexp" + "strings" + "terraform-provider-duplocloud/duplosdk" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func gcpS3BucketSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "tenant_id": { + Description: "The GUID of the tenant that the S3 bucket will be created in.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + "name": { + Description: "The short name of the S3 bucket. Duplo will add a prefix to the name. You can retrieve the full name from the `fullname` attribute.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringMatch(regexp.MustCompile(`^[a-z0-9._-]*$`), "Invalid S3 bucket name"), + + // NOTE: some validations are moot, because Duplo provides a prefix and suffix for the name: + // + // - Bucket names must begin and end with a letter or number. + // - Bucket names must not be formatted as an IP address (for example, 192.168.5.4). + // - Bucket names must not start with the prefix xn--. + // - Bucket names must not end with the suffix -s3alias. + // + // Because Duplo automatically prefixes and suffixes bucket names, it is impossible to break any of the rules in the above bulleted list. + ), + }, + "fullname": { + Description: "The full name of the S3 bucket.", + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Description: "The ARN of the S3 bucket.", + Type: schema.TypeString, + Computed: true, + }, + "domain_name": { + Description: "The domain name of the S3 bucket.", + Type: schema.TypeString, + Computed: true, + }, + "enable_versioning": { + Description: "Whether or not to enable versioning.", + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "enable_access_logs": { + Description: "Whether or not to enable access logs. When enabled, Duplo will send access logs to a centralized S3 bucket per plan.", + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "allow_public_access": { + Description: "Whether or not to remove the public access block from the bucket.", + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "default_encryption": { + Description: "Default encryption settings for objects uploaded to the bucket.", + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "method": { + Description: "Default encryption method. Must be one of: `None`, `Sse`, `AwsKms`, `TenantKms`.", + Type: schema.TypeString, + Optional: true, + Default: "Sse", + ValidateFunc: validation.StringInSlice([]string{"None", "Sse", "AwsKms", "TenantKms"}, false), + }, + // RESERVED FOR THE FUTURE + // + //"kms_key_id": { + // Type: schema.TypeString, + // Optional: true, + //}, + }, + }, + }, + "region": { + Description: "The region of the S3 bucket.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "managed_policies": { + Description: "Duplo can manage your S3 bucket policy for you, based on simple list of policy keywords:\n\n" + + " - `\"ssl\"`: Require SSL / HTTPS when accessing the bucket.\n" + + " - `\"ignore\"`: If this key is present, Duplo will not manage your bucket policy.\n", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": awsTagsKeyValueSchemaComputed(), + "location": { + Description: "The location is to set multi region, applicable for gcp cloud.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + } +} + +// Resource for managing an AWS ElasticSearch instance +func resourceGCPS3Bucket() *schema.Resource { + return &schema.Resource{ + ReadContext: resourceGCPS3BucketRead, + CreateContext: resourceGCPS3BucketCreate, + UpdateContext: resourceGCPS3BucketUpdate, + DeleteContext: resourceGCPS3BucketDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), + }, + Schema: gcpS3BucketSchema(), + } +} + +// READ resource +func resourceGCPS3BucketRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[TRACE] resourceS3BucketRead ******** start") + + // Parse the identifying attributes + id := d.Id() + idParts := strings.SplitN(id, "/", 2) + if len(idParts) < 2 { + return diag.Errorf("resourceS3BucketRead: Invalid resource (ID: %s)", id) + } + tenantID, name := idParts[0], idParts[1] + + c := m.(*duplosdk.Client) + + // Figure out the full resource name. + features, _ := c.AdminGetSystemFeatures() + fullName, err := c.GetDuploServicesNameWithAws(tenantID, name) + if err != nil { + return diag.Errorf("resourceS3BucketRead: Unable to retrieve duplo service name (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + } + if features != nil && features.IsTagsBasedResourceMgmtEnabled { + fullName = features.S3BucketNamePrefix + name + } + + // Get the object from Duplo + duplo, err := c.TenantGetV3S3Bucket(tenantID, fullName) + if err != nil && !err.PossibleMissingAPI() { + return diag.Errorf("resourceS3BucketRead: Unable to retrieve s3 bucket (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + } + + // **** fallback on older api **** + if err != nil && err.PossibleMissingAPI() { + duplo, err = c.TenantGetS3BucketSettings(tenantID, name) + if duplo == nil { + d.SetId("") // object missing + return nil + } + if err != nil { + return diag.Errorf("resourceS3BucketRead: Unable to retrieve s3 bucket settings (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + } + } + + // Set simple fields first. + resourceS3BucketSetData(d, tenantID, name, duplo) + + log.Printf("[TRACE] resourceS3BucketRead ******** end") + return nil +} + +// CREATE resource +func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[TRACE] resourceS3BucketCreate ******** start") + name := d.Get("name").(string) + c := m.(*duplosdk.Client) + features, _ := c.AdminGetSystemFeatures() + + s3MaxLength := 63 - MAX_DUPLOSERVICES_AND_SUFFIX_LENGTH + if features != nil && features.IsTagsBasedResourceMgmtEnabled { + s3MaxLength = 63 + } + if len(name) > s3MaxLength { + return diag.Errorf("resourceS3BucketCreate: Invalid s3 bucket name: %s, Length must be in the range: (1 - %d)", name, s3MaxLength) + } + tenantID := d.Get("tenant_id").(string) + + // prefix + name based on settings + fullName, errname := c.GetDuploServicesNameWithAws(tenantID, name) + if features != nil && features.IsTagsBasedResourceMgmtEnabled { + fullName = features.S3BucketNamePrefix + name + } + if errname != nil { + return diag.Errorf("resourceS3BucketCreate: Unable to retrieve duplo service name (name: %s, error: %s)", name, errname.Error()) + } + + // Create the request object. + duploObject := duplosdk.DuploS3BucketSettingsRequest{ + Name: name, + } + errFill := fillS3BucketRequest(&duploObject, d) + if errFill != nil { + return diag.FromErr(errFill) + } + + // Post the object to Duplo + _, err := c.TenantCreateV3S3Bucket(tenantID, duploObject) + if err != nil && !err.PossibleMissingAPI() { + return diag.Errorf("resourceS3BucketCreate: Unable to create s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) + } + + // **** fallback on older api **** + if err != nil && err.PossibleMissingAPI() { + return resourceS3BucketCreateOldApi(ctx, d, m) + } + + // Wait up to 60 seconds for Duplo to be able to return the bucket's details. + id := fmt.Sprintf("%s/%s", tenantID, name) + diags := waitForResourceToBePresentAfterCreate(ctx, d, "S3 bucket", id, func() (interface{}, duplosdk.ClientError) { + return c.TenantGetV3S3Bucket(tenantID, fullName) + }) + if diags != nil { + return diags + } + d.SetId(id) + duploObject.Name = fullName + _, err = c.TenantUpdateV3S3Bucket(tenantID, duploObject) + if err != nil { + return diag.Errorf("%s", err.Error()) + } + duplo, err := c.TenantGetV3S3Bucket(tenantID, fullName) + if duplo == nil { + d.SetId("") // object missing + return nil + } + if err != nil { + return diag.Errorf("resourceS3BucketCreate: Unable to retrieve s3 bucket details using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + } + + // Set simple fields first. + resourceS3BucketSetData(d, tenantID, name, duplo) + log.Printf("[TRACE] resourceS3BucketCreate ******** end") + return diags +} + +// UPDATE resource +func resourceGCPS3BucketUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[TRACE] resourceS3BucketUpdate ******** start") + + fullname := d.Get("fullname").(string) + name := d.Get("name").(string) + + // Create the request object. + duploObject := duplosdk.DuploS3BucketSettingsRequest{ + Name: fullname, + } + + errName := fillS3BucketRequest(&duploObject, d) + if errName != nil { + return diag.FromErr(errName) + } + c := m.(*duplosdk.Client) + tenantID := d.Get("tenant_id").(string) + + // Post the object to Duplo + resource, err := c.TenantUpdateV3S3Bucket(tenantID, duploObject) + if err != nil && !err.PossibleMissingAPI() { + return diag.Errorf("resourceS3BucketUpdate: Unable to update s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) + } + // **** fallback on older api **** + if err != nil && err.PossibleMissingAPI() { + return resourceS3BucketUpdateOldApi(ctx, d, m) + } + + resourceS3BucketSetData(d, tenantID, name, resource) + + log.Printf("[TRACE] resourceS3BucketUpdate ******** end") + return nil +} + +// DELETE resource +func resourceGCPS3BucketDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[TRACE] resourceS3BucketDelete ******** start") + + // Delete the object with Duplo + c := m.(*duplosdk.Client) + id := d.Id() + idParts := strings.SplitN(id, "/", 2) + if len(idParts) < 2 { + return diag.Errorf("resourceS3BucketDelete: Invalid resource (ID: %s)", id) + } + err := c.TenantDeleteS3Bucket(idParts[0], idParts[1]) + if err != nil { + return diag.Errorf("resourceS3BucketDelete: Unable to delete bucket (name:%s, error: %s)", id, err) + } + + // Wait up to 60 seconds for Duplo to delete the bucket. + diag := waitForResourceToBeMissingAfterDelete(ctx, d, "bucket", id, func() (interface{}, duplosdk.ClientError) { + return c.TenantGetS3Bucket(idParts[0], idParts[1]) + }) + if diag != nil { + return diag + } + + // Wait 10 more seconds to deal with consistency issues. + time.Sleep(10 * time.Second) + + log.Printf("[TRACE] resourceS3BucketDelete ******** end") + return nil +} From ef6b360cade31ca8e427185afcfe0a0f353f8c26 Mon Sep 17 00:00:00 2001 From: nikhil Date: Wed, 26 Jun 2024 16:50:08 +0530 Subject: [PATCH 18/35] in progress --- duplocloud/resource_duplo_gcp_s3_bucket.go | 29 ++----------- duplosdk/tenant_aws_cloud_resources.go | 50 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/duplocloud/resource_duplo_gcp_s3_bucket.go b/duplocloud/resource_duplo_gcp_s3_bucket.go index 2cbb9e4f..42e2a64a 100644 --- a/duplocloud/resource_duplo_gcp_s3_bucket.go +++ b/duplocloud/resource_duplo_gcp_s3_bucket.go @@ -195,26 +195,9 @@ func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m in log.Printf("[TRACE] resourceS3BucketCreate ******** start") name := d.Get("name").(string) c := m.(*duplosdk.Client) - features, _ := c.AdminGetSystemFeatures() - s3MaxLength := 63 - MAX_DUPLOSERVICES_AND_SUFFIX_LENGTH - if features != nil && features.IsTagsBasedResourceMgmtEnabled { - s3MaxLength = 63 - } - if len(name) > s3MaxLength { - return diag.Errorf("resourceS3BucketCreate: Invalid s3 bucket name: %s, Length must be in the range: (1 - %d)", name, s3MaxLength) - } tenantID := d.Get("tenant_id").(string) - // prefix + name based on settings - fullName, errname := c.GetDuploServicesNameWithAws(tenantID, name) - if features != nil && features.IsTagsBasedResourceMgmtEnabled { - fullName = features.S3BucketNamePrefix + name - } - if errname != nil { - return diag.Errorf("resourceS3BucketCreate: Unable to retrieve duplo service name (name: %s, error: %s)", name, errname.Error()) - } - // Create the request object. duploObject := duplosdk.DuploS3BucketSettingsRequest{ Name: name, @@ -225,16 +208,12 @@ func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m in } // Post the object to Duplo - _, err := c.TenantCreateV3S3Bucket(tenantID, duploObject) + resp, err := c.GCPTenantCreateV3S3Bucket(tenantID, duploObject) if err != nil && !err.PossibleMissingAPI() { return diag.Errorf("resourceS3BucketCreate: Unable to create s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) } - // **** fallback on older api **** - if err != nil && err.PossibleMissingAPI() { - return resourceS3BucketCreateOldApi(ctx, d, m) - } - + fullName := resp.Name // Wait up to 60 seconds for Duplo to be able to return the bucket's details. id := fmt.Sprintf("%s/%s", tenantID, name) diags := waitForResourceToBePresentAfterCreate(ctx, d, "S3 bucket", id, func() (interface{}, duplosdk.ClientError) { @@ -245,11 +224,11 @@ func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m in } d.SetId(id) duploObject.Name = fullName - _, err = c.TenantUpdateV3S3Bucket(tenantID, duploObject) + _, err = c.GCPTenantUpdateV3S3Bucket(tenantID, duploObject) if err != nil { return diag.Errorf("%s", err.Error()) } - duplo, err := c.TenantGetV3S3Bucket(tenantID, fullName) + duplo, err := c.GCPTenantGetV3S3Bucket(tenantID, fullName) if duplo == nil { d.SetId("") // object missing return nil diff --git a/duplosdk/tenant_aws_cloud_resources.go b/duplosdk/tenant_aws_cloud_resources.go index 54f819e1..47807961 100644 --- a/duplosdk/tenant_aws_cloud_resources.go +++ b/duplosdk/tenant_aws_cloud_resources.go @@ -532,6 +532,24 @@ func (c *Client) TenantCreateV3S3Bucket(tenantID string, duplo DuploS3BucketSett return &resp, nil } +func (c *Client) GCPTenantCreateV3S3Bucket(tenantID string, duplo DuploS3BucketSettingsRequest) (*DuploS3Bucket, ClientError) { + + resp := DuploS3Bucket{} + + // Create the bucket via Duplo. + err := c.postAPI( + fmt.Sprintf("TenantCreateV3S3Bucket(%s, %s)", tenantID, duplo.Name), + // fmt.Sprintf("subscriptions/%s/S3BucketUpdate", tenantID), + fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket", tenantID), + &duplo, + &resp) + + if err != nil || resp.Name == "" { + return nil, err + } + return &resp, nil +} + // TenantDeleteV3S3Bucket deletes an S3 bucket resource via V3 Duplo Api. func (c *Client) TenantDeleteV3S3Bucket(tenantID string, fullname string) ClientError { // Delete the bucket via Duplo. @@ -553,6 +571,17 @@ func (c *Client) TenantGetV3S3Bucket(tenantID string, name string) (*DuploS3Buck return &rp, err } +func (c *Client) GCPTenantGetV3S3Bucket(tenantID string, name string) (*DuploS3Bucket, ClientError) { + rp := DuploS3Bucket{} + err := c.getAPI(fmt.Sprintf("TenantGetV3S3Bucket(%s, %s)", tenantID, name), + fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket/%s", tenantID, name), + &rp) + if err != nil || rp.Arn == "" { + return nil, err + } + return &rp, err +} + // TenantUpdateV3S3Bucket applies settings to an S3 bucket resource V3 Duplo Api. func (c *Client) TenantUpdateV3S3Bucket(tenantID string, duplo DuploS3BucketSettingsRequest) (*DuploS3Bucket, ClientError) { // Apply the settings via Duplo. @@ -575,6 +604,27 @@ func (c *Client) TenantUpdateV3S3Bucket(tenantID string, duplo DuploS3BucketSett return &rp, nil } +func (c *Client) GCPTenantUpdateV3S3Bucket(tenantID string, duplo DuploS3BucketSettingsRequest) (*DuploS3Bucket, ClientError) { + // Apply the settings via Duplo. + apiName := fmt.Sprintf("TenantUpdateV3S3Bucket(%s, %s)", tenantID, duplo.Name) + rp := DuploS3Bucket{} + err := c.putAPI(apiName, fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket/%s", tenantID, duplo.Name), &duplo, &rp) + if err != nil { + return nil, err + } + + // Deal with a missing response. + if rp.Name == "" { + message := fmt.Sprintf("%s: unexpected missing response from backend", apiName) + log.Printf("[TRACE] %s", message) + return nil, newClientError(message) + } + + // Return the response. + rp.TenantID = tenantID + return &rp, nil +} + // TenantCreateS3Bucket creates an S3 bucket resource via Duplo. func (c *Client) TenantCreateS3Bucket(tenantID string, duplo DuploS3BucketRequest) ClientError { duplo.Type = ResourceTypeS3Bucket From 27ecf1402a87f848c084c4761d630e748195dafe Mon Sep 17 00:00:00 2001 From: nikhil Date: Wed, 26 Jun 2024 19:55:29 +0530 Subject: [PATCH 19/35] Implemented gcp based s3 resource duplocloud_gcp_s3_bucket --- docs/resources/gcp_s3_bucket.md | 142 ++++++++++++++++++ docs/resources/s3_bucket.md | 1 - duplocloud/provider.go | 1 + duplocloud/resource_duplo_gcp_s3_bucket.go | 70 ++++----- duplocloud/resource_duplo_s3_bucket.go | 6 - duplosdk/tenant_aws_cloud_resources.go | 19 ++- .../duplocloud_gcp_s3_bucket/import.sh | 5 + .../duplocloud_gcp_s3_bucket/resource.tf | 59 ++++++++ 8 files changed, 251 insertions(+), 52 deletions(-) create mode 100644 docs/resources/gcp_s3_bucket.md create mode 100644 examples/resources/duplocloud_gcp_s3_bucket/import.sh create mode 100644 examples/resources/duplocloud_gcp_s3_bucket/resource.tf diff --git a/docs/resources/gcp_s3_bucket.md b/docs/resources/gcp_s3_bucket.md new file mode 100644 index 00000000..c532d2d3 --- /dev/null +++ b/docs/resources/gcp_s3_bucket.md @@ -0,0 +1,142 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "duplocloud_gcp_s3_bucket Resource - terraform-provider-duplocloud" +subcategory: "" +description: |- + +--- + +# duplocloud_gcp_s3_bucket (Resource) + + + +## Example Usage + +```terraform +resource "duplocloud_tenant" "myapp" { + account_name = "myapp" + plan_id = "default" +} + +# Simple Example 1: Deploy an S3 bucket with hardened security settings. +resource "duplocloud_gcp_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + allow_public_access = false + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" # For even stricter security, use "TenantKms" here. + } +} + +# Simple Example 2: Deploy a hardened S3 bucket suitable for public website hosting. +resource "duplocloud_gcp_s3_bucket" "www" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "website" + + allow_public_access = true + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" + } +} + + +# Simple Example 3: Deploy an S3 bucket to dersired region. +resource "duplocloud_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + # optional, if not provided, tenant region will be used + region = "us-west-2" + +} + +# Simple Example 4: Deploy an S3 bucket with multiple region. + +resource "duplocloud_gcp_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + allow_public_access = true + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" # For even stricter security, use "TenantKms" here. + } + location = "Asia" #pass region value (Asia/EU/US)to location to enable multi region +} +``` + + +## Schema + +### Required + +- `name` (String) The short name of the S3 bucket. Duplo will add a prefix to the name. You can retrieve the full name from the `fullname` attribute. +- `tenant_id` (String) The GUID of the tenant that the S3 bucket will be created in. + +### Optional + +- `allow_public_access` (Boolean) Whether or not to remove the public access block from the bucket. +- `default_encryption` (Block List, Max: 1) Default encryption settings for objects uploaded to the bucket. (see [below for nested schema](#nestedblock--default_encryption)) +- `enable_access_logs` (Boolean) Whether or not to enable access logs. When enabled, Duplo will send access logs to a centralized S3 bucket per plan. +- `enable_versioning` (Boolean) Whether or not to enable versioning. +- `location` (String) The location is to set multi region, applicable for gcp cloud. +- `managed_policies` (List of String) Duplo can manage your S3 bucket policy for you, based on simple list of policy keywords: + + - `"ssl"`: Require SSL / HTTPS when accessing the bucket. + - `"ignore"`: If this key is present, Duplo will not manage your bucket policy. +- `region` (String) The region of the S3 bucket. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `arn` (String) The ARN of the S3 bucket. +- `domain_name` (String) The domain name of the S3 bucket. +- `fullname` (String) The full name of the S3 bucket. +- `id` (String) The ID of this resource. +- `tags` (List of Object) (see [below for nested schema](#nestedatt--tags)) + + +### Nested Schema for `default_encryption` + +Optional: + +- `method` (String) Default encryption method. Must be one of: `None`, `Sse`, `AwsKms`, `TenantKms`. Defaults to `Sse`. + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `delete` (String) + + + +### Nested Schema for `tags` + +Read-Only: + +- `key` (String) +- `value` (String) + +## Import + +Import is supported using the following syntax: + +```shell +# Example: Importing an existing S3 bucket +# - *TENANT_ID* is the tenant GUID +# - *SHORTNAME* is the short name of the S3 bucket (without the duploservices prefix) +# +terraform import duplocloud_gcp_s3_bucket.mybucket *TENANT_ID*/*SHORTNAME* +``` diff --git a/docs/resources/s3_bucket.md b/docs/resources/s3_bucket.md index 42153ee7..adcfaf7f 100644 --- a/docs/resources/s3_bucket.md +++ b/docs/resources/s3_bucket.md @@ -72,7 +72,6 @@ resource "duplocloud_s3_bucket" "mydata" { - `default_encryption` (Block List, Max: 1) Default encryption settings for objects uploaded to the bucket. (see [below for nested schema](#nestedblock--default_encryption)) - `enable_access_logs` (Boolean) Whether or not to enable access logs. When enabled, Duplo will send access logs to a centralized S3 bucket per plan. - `enable_versioning` (Boolean) Whether or not to enable versioning. -- `location` (String) The location is to set multi region, applicable for gcp cloud. - `managed_policies` (List of String) Duplo can manage your S3 bucket policy for you, based on simple list of policy keywords: - `"ssl"`: Require SSL / HTTPS when accessing the bucket. diff --git a/duplocloud/provider.go b/duplocloud/provider.go index 87a910ec..4b31d304 100644 --- a/duplocloud/provider.go +++ b/duplocloud/provider.go @@ -154,6 +154,7 @@ func Provider() *schema.Provider { "duplocloud_plan_waf": resourcePlanWaf(), "duplocloud_plan_kms": resourcePlanKMS(), "duplocloud_aws_apigateway_event": resourceAwsApiGatewayEvent(), + "duplocloud_gcp_s3_bucket": resourceGCPS3Bucket(), }, DataSourcesMap: map[string]*schema.Resource{ "duplocloud_admin_aws_credentials": dataSourceAdminAwsCredentials(), diff --git a/duplocloud/resource_duplo_gcp_s3_bucket.go b/duplocloud/resource_duplo_gcp_s3_bucket.go index 42e2a64a..48596108 100644 --- a/duplocloud/resource_duplo_gcp_s3_bucket.go +++ b/duplocloud/resource_duplo_gcp_s3_bucket.go @@ -119,6 +119,11 @@ func gcpS3BucketSchema() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "Asia", + "EU", + "US", + }, false), }, } } @@ -143,32 +148,25 @@ func resourceGCPS3Bucket() *schema.Resource { // READ resource func resourceGCPS3BucketRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - log.Printf("[TRACE] resourceS3BucketRead ******** start") + log.Printf("[TRACE] resourceGCPS3BucketRead ******** start") // Parse the identifying attributes id := d.Id() idParts := strings.SplitN(id, "/", 2) if len(idParts) < 2 { - return diag.Errorf("resourceS3BucketRead: Invalid resource (ID: %s)", id) + return diag.Errorf("resourceGCPS3BucketRead: Invalid resource (ID: %s)", id) } tenantID, name := idParts[0], idParts[1] c := m.(*duplosdk.Client) // Figure out the full resource name. - features, _ := c.AdminGetSystemFeatures() - fullName, err := c.GetDuploServicesNameWithAws(tenantID, name) - if err != nil { - return diag.Errorf("resourceS3BucketRead: Unable to retrieve duplo service name (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) - } - if features != nil && features.IsTagsBasedResourceMgmtEnabled { - fullName = features.S3BucketNamePrefix + name - } + fullName := d.Get("fullname").(string) // Get the object from Duplo - duplo, err := c.TenantGetV3S3Bucket(tenantID, fullName) + duplo, err := c.GCPTenantGetV3S3Bucket(tenantID, fullName) if err != nil && !err.PossibleMissingAPI() { - return diag.Errorf("resourceS3BucketRead: Unable to retrieve s3 bucket (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + return diag.Errorf("resourceGCPS3BucketRead: Unable to retrieve s3 bucket (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) } // **** fallback on older api **** @@ -179,20 +177,20 @@ func resourceGCPS3BucketRead(ctx context.Context, d *schema.ResourceData, m inte return nil } if err != nil { - return diag.Errorf("resourceS3BucketRead: Unable to retrieve s3 bucket settings (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + return diag.Errorf("resourceGCPS3BucketRead: Unable to retrieve s3 bucket settings (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) } } // Set simple fields first. resourceS3BucketSetData(d, tenantID, name, duplo) - log.Printf("[TRACE] resourceS3BucketRead ******** end") + log.Printf("[TRACE] resourceGCPS3BucketRead ******** end") return nil } // CREATE resource func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - log.Printf("[TRACE] resourceS3BucketCreate ******** start") + log.Printf("[TRACE] resourceGCPS3BucketCreate ******** start") name := d.Get("name").(string) c := m.(*duplosdk.Client) @@ -210,42 +208,37 @@ func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m in // Post the object to Duplo resp, err := c.GCPTenantCreateV3S3Bucket(tenantID, duploObject) if err != nil && !err.PossibleMissingAPI() { - return diag.Errorf("resourceS3BucketCreate: Unable to create s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) + return diag.Errorf("resourceGCPS3BucketCreate: Unable to create s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) } fullName := resp.Name // Wait up to 60 seconds for Duplo to be able to return the bucket's details. id := fmt.Sprintf("%s/%s", tenantID, name) diags := waitForResourceToBePresentAfterCreate(ctx, d, "S3 bucket", id, func() (interface{}, duplosdk.ClientError) { - return c.TenantGetV3S3Bucket(tenantID, fullName) + return c.GCPTenantGetV3S3Bucket(tenantID, fullName) }) if diags != nil { return diags } - d.SetId(id) - duploObject.Name = fullName - _, err = c.GCPTenantUpdateV3S3Bucket(tenantID, duploObject) - if err != nil { - return diag.Errorf("%s", err.Error()) - } duplo, err := c.GCPTenantGetV3S3Bucket(tenantID, fullName) if duplo == nil { d.SetId("") // object missing return nil } if err != nil { - return diag.Errorf("resourceS3BucketCreate: Unable to retrieve s3 bucket details using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) + return diag.Errorf("resourceGCPS3BucketCreate: Unable to retrieve s3 bucket details using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, name, err) } + d.SetId(id) // Set simple fields first. resourceS3BucketSetData(d, tenantID, name, duplo) - log.Printf("[TRACE] resourceS3BucketCreate ******** end") - return diags + log.Printf("[TRACE] resourceGCPS3BucketCreate ******** end") + return nil } // UPDATE resource func resourceGCPS3BucketUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - log.Printf("[TRACE] resourceS3BucketUpdate ******** start") + log.Printf("[TRACE] resourceGCPS3BucketUpdate ******** start") fullname := d.Get("fullname").(string) name := d.Get("name").(string) @@ -263,40 +256,37 @@ func resourceGCPS3BucketUpdate(ctx context.Context, d *schema.ResourceData, m in tenantID := d.Get("tenant_id").(string) // Post the object to Duplo - resource, err := c.TenantUpdateV3S3Bucket(tenantID, duploObject) + resource, err := c.GCPTenantUpdateV3S3Bucket(tenantID, duploObject) if err != nil && !err.PossibleMissingAPI() { - return diag.Errorf("resourceS3BucketUpdate: Unable to update s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) - } - // **** fallback on older api **** - if err != nil && err.PossibleMissingAPI() { - return resourceS3BucketUpdateOldApi(ctx, d, m) + return diag.Errorf("resourceGCPS3BucketUpdate: Unable to update s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) } resourceS3BucketSetData(d, tenantID, name, resource) - log.Printf("[TRACE] resourceS3BucketUpdate ******** end") + log.Printf("[TRACE] resourceGCPS3BucketUpdate ******** end") return nil } // DELETE resource func resourceGCPS3BucketDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - log.Printf("[TRACE] resourceS3BucketDelete ******** start") + log.Printf("[TRACE] resourceGCPS3BucketDelete ******** start") // Delete the object with Duplo c := m.(*duplosdk.Client) id := d.Id() idParts := strings.SplitN(id, "/", 2) if len(idParts) < 2 { - return diag.Errorf("resourceS3BucketDelete: Invalid resource (ID: %s)", id) + return diag.Errorf("resourceGCPS3BucketDelete: Invalid resource (ID: %s)", id) } - err := c.TenantDeleteS3Bucket(idParts[0], idParts[1]) + fullName := d.Get("fullname").(string) + err := c.GCPTenantDeleteS3Bucket(idParts[0], idParts[1], fullName) if err != nil { - return diag.Errorf("resourceS3BucketDelete: Unable to delete bucket (name:%s, error: %s)", id, err) + return diag.Errorf("resourceGCPS3BucketDelete: Unable to delete bucket (name:%s, error: %s)", id, err) } // Wait up to 60 seconds for Duplo to delete the bucket. diag := waitForResourceToBeMissingAfterDelete(ctx, d, "bucket", id, func() (interface{}, duplosdk.ClientError) { - return c.TenantGetS3Bucket(idParts[0], idParts[1]) + return c.GCPTenantGetV3S3Bucket(idParts[0], fullName) }) if diag != nil { return diag @@ -305,6 +295,6 @@ func resourceGCPS3BucketDelete(ctx context.Context, d *schema.ResourceData, m in // Wait 10 more seconds to deal with consistency issues. time.Sleep(10 * time.Second) - log.Printf("[TRACE] resourceS3BucketDelete ******** end") + log.Printf("[TRACE] resourceGCPS3BucketDelete ******** end") return nil } diff --git a/duplocloud/resource_duplo_s3_bucket.go b/duplocloud/resource_duplo_s3_bucket.go index d2d1277c..dee677e8 100644 --- a/duplocloud/resource_duplo_s3_bucket.go +++ b/duplocloud/resource_duplo_s3_bucket.go @@ -114,12 +114,6 @@ func s3BucketSchema() map[string]*schema.Schema { Elem: &schema.Schema{Type: schema.TypeString}, }, "tags": awsTagsKeyValueSchemaComputed(), - "location": { - Description: "The location is to set multi region, applicable for gcp cloud.", - Type: schema.TypeString, - Optional: true, - Computed: true, - }, } } diff --git a/duplosdk/tenant_aws_cloud_resources.go b/duplosdk/tenant_aws_cloud_resources.go index 47807961..2dc240f4 100644 --- a/duplosdk/tenant_aws_cloud_resources.go +++ b/duplosdk/tenant_aws_cloud_resources.go @@ -540,7 +540,7 @@ func (c *Client) GCPTenantCreateV3S3Bucket(tenantID string, duplo DuploS3BucketS err := c.postAPI( fmt.Sprintf("TenantCreateV3S3Bucket(%s, %s)", tenantID, duplo.Name), // fmt.Sprintf("subscriptions/%s/S3BucketUpdate", tenantID), - fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket", tenantID), + fmt.Sprintf("v3/subscriptions/%s/google/bucket", tenantID), &duplo, &resp) @@ -573,10 +573,10 @@ func (c *Client) TenantGetV3S3Bucket(tenantID string, name string) (*DuploS3Buck func (c *Client) GCPTenantGetV3S3Bucket(tenantID string, name string) (*DuploS3Bucket, ClientError) { rp := DuploS3Bucket{} - err := c.getAPI(fmt.Sprintf("TenantGetV3S3Bucket(%s, %s)", tenantID, name), - fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket/%s", tenantID, name), + err := c.getAPI(fmt.Sprintf("GCPTenantGetV3S3Bucket(%s, %s)", tenantID, name), + fmt.Sprintf("v3/subscriptions/%s/google/bucket/%s", tenantID, name), &rp) - if err != nil || rp.Arn == "" { + if err != nil { //|| rp.Arn == "" { return nil, err } return &rp, err @@ -608,7 +608,7 @@ func (c *Client) GCPTenantUpdateV3S3Bucket(tenantID string, duplo DuploS3BucketS // Apply the settings via Duplo. apiName := fmt.Sprintf("TenantUpdateV3S3Bucket(%s, %s)", tenantID, duplo.Name) rp := DuploS3Bucket{} - err := c.putAPI(apiName, fmt.Sprintf("v3/subscriptions/%s/google/s3Bucket/%s", tenantID, duplo.Name), &duplo, &rp) + err := c.putAPI(apiName, fmt.Sprintf("v3/subscriptions/%s/google/bucket/%s", tenantID, duplo.Name), &duplo, &rp) if err != nil { return nil, err } @@ -658,6 +658,15 @@ func (c *Client) TenantDeleteS3Bucket(tenantID string, name string) ClientError nil) } +func (c *Client) GCPTenantDeleteS3Bucket(tenantID string, name, fullName string) ClientError { + + // Delete the bucket via Duplo. + return c.deleteAPI(fmt.Sprintf("NativeHostDelete(%s, %s)", tenantID, name), + fmt.Sprintf("v3/subscriptions/%s/google/bucket/%s", tenantID, fullName), + nil) + +} + // TenantGetS3BucketSettings gets a non-cached view of the S3 buckets's settings via Duplo. func (c *Client) TenantGetS3BucketSettings(tenantID string, name string) (*DuploS3Bucket, ClientError) { rp := DuploS3Bucket{} diff --git a/examples/resources/duplocloud_gcp_s3_bucket/import.sh b/examples/resources/duplocloud_gcp_s3_bucket/import.sh new file mode 100644 index 00000000..9ce092f4 --- /dev/null +++ b/examples/resources/duplocloud_gcp_s3_bucket/import.sh @@ -0,0 +1,5 @@ +# Example: Importing an existing S3 bucket +# - *TENANT_ID* is the tenant GUID +# - *SHORTNAME* is the short name of the S3 bucket (without the duploservices prefix) +# +terraform import duplocloud_gcp_s3_bucket.mybucket *TENANT_ID*/*SHORTNAME* diff --git a/examples/resources/duplocloud_gcp_s3_bucket/resource.tf b/examples/resources/duplocloud_gcp_s3_bucket/resource.tf new file mode 100644 index 00000000..bc258f4c --- /dev/null +++ b/examples/resources/duplocloud_gcp_s3_bucket/resource.tf @@ -0,0 +1,59 @@ +resource "duplocloud_tenant" "myapp" { + account_name = "myapp" + plan_id = "default" +} + +# Simple Example 1: Deploy an S3 bucket with hardened security settings. +resource "duplocloud_gcp_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + allow_public_access = false + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" # For even stricter security, use "TenantKms" here. + } +} + +# Simple Example 2: Deploy a hardened S3 bucket suitable for public website hosting. +resource "duplocloud_gcp_s3_bucket" "www" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "website" + + allow_public_access = true + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" + } +} + + +# Simple Example 3: Deploy an S3 bucket to dersired region. +resource "duplocloud_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + # optional, if not provided, tenant region will be used + region = "us-west-2" + +} + +# Simple Example 4: Deploy an S3 bucket with multiple region. + +resource "duplocloud_gcp_s3_bucket" "mydata" { + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mydata" + + allow_public_access = true + enable_access_logs = true + enable_versioning = true + managed_policies = ["ssl"] + default_encryption { + method = "Sse" # For even stricter security, use "TenantKms" here. + } + location = "Asia" #pass region value (Asia/EU/US)to location to enable multi region +} From 6601c6bc47f90ef7d6705f3ab0fb84f34164cd14 Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 27 Jun 2024 18:43:05 +0530 Subject: [PATCH 20/35] allowing opensearch creation without storage --- duplocloud/resource_duplo_aws_elasticsearch.go | 16 +++++++--------- duplosdk/aws_elasticsearch.go | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/duplocloud/resource_duplo_aws_elasticsearch.go b/duplocloud/resource_duplo_aws_elasticsearch.go index 1e7219da..ab2f0915 100644 --- a/duplocloud/resource_duplo_aws_elasticsearch.go +++ b/duplocloud/resource_duplo_aws_elasticsearch.go @@ -96,7 +96,6 @@ func awsElasticSearchSchema() map[string]*schema.Schema { Type: schema.TypeInt, Optional: true, ForceNew: true, - Default: 20, }, "ebs_options": { Type: schema.TypeList, @@ -392,12 +391,14 @@ func resourceDuploAwsElasticSearchCreate(ctx context.Context, d *schema.Resource RequireSSL: d.Get("require_ssl").(bool), UseLatestTLSCipher: d.Get("use_latest_tls_cipher").(bool), EnableNodeToNodeEncryption: d.Get("enable_node_to_node_encryption").(bool), - EBSOptions: duplosdk.DuploElasticSearchDomainEBSOptions{ - VolumeSize: d.Get("storage_size").(int), - }, + VPCOptions: duploVPCOptions, } - + if size, ok := d.GetOk("storage_size"); ok { + duploObject.EBSOptions = &duplosdk.DuploElasticSearchDomainEBSOptions{ + VolumeSize: size.(int), + } + } c := m.(*duplosdk.Client) tenantID := d.Get("tenant_id").(string) id := fmt.Sprintf("%s/%s", tenantID, duploObject.Name) @@ -513,10 +514,7 @@ func resourceDuploAwsElasticSearchUpdate(ctx context.Context, d *schema.Resource RequireSSL: d.Get("require_ssl").(bool), UseLatestTLSCipher: d.Get("use_latest_tls_cipher").(bool), EnableNodeToNodeEncryption: d.Get("enable_node_to_node_encryption").(bool), - EBSOptions: duplosdk.DuploElasticSearchDomainEBSOptions{ - VolumeSize: d.Get("storage_size").(int), - }, - VPCOptions: duploVPCOptions, + VPCOptions: duploVPCOptions, } c := m.(*duplosdk.Client) diff --git a/duplosdk/aws_elasticsearch.go b/duplosdk/aws_elasticsearch.go index 4b2b19a3..fe80af8f 100644 --- a/duplosdk/aws_elasticsearch.go +++ b/duplosdk/aws_elasticsearch.go @@ -92,7 +92,7 @@ type DuploElasticSearchDomainRequest struct { Version string `json:"Version,omitempty"` KmsKeyID string `json:"KmsKeyId,omitempty"` ClusterConfig DuploElasticSearchDomainClusterConfig `json:"ClusterConfig,omitempty"` - EBSOptions DuploElasticSearchDomainEBSOptions `json:"EbsOptions,omitempty"` + EBSOptions *DuploElasticSearchDomainEBSOptions `json:"EbsOptions,omitempty"` VPCOptions DuploElasticSearchDomainVPCOptions `json:"VPCOptions,omitempty"` EnableNodeToNodeEncryption bool `json:"EnableNodeToNodeEncryption,omitempty"` RequireSSL bool `json:"RequireSSL,omitempty"` From 1efe66fbbf0c4b8c19fd4309ad01905a4e90550e Mon Sep 17 00:00:00 2001 From: Tahir Tamboli Date: Sun, 30 Jun 2024 20:22:50 +0530 Subject: [PATCH 21/35] New attribute api_key_required added. --- .../resource_duplo_aws_apigateway_event.go | 25 +++++++++++++------ duplosdk/aws_apigateway_events.go | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/duplocloud/resource_duplo_aws_apigateway_event.go b/duplocloud/resource_duplo_aws_apigateway_event.go index 5468afe6..0e1a35dd 100644 --- a/duplocloud/resource_duplo_aws_apigateway_event.go +++ b/duplocloud/resource_duplo_aws_apigateway_event.go @@ -55,6 +55,12 @@ func AwsApiGatewayEventSchema() map[string]*schema.Schema { Optional: true, Computed: true, }, + "api_key_required": { + Description: "Specify if the method requires an API key.", + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, "authorizer_id": { Description: "Authorizer id to be used when the authorization is `CUSTOM` or `COGNITO_USER_POOLS`.", Type: schema.TypeString, @@ -155,6 +161,7 @@ func resourceAwsApiGatewayEventRead(ctx context.Context, d *schema.ResourceData, d.Set("method", duplo.Method) d.Set("path", duplo.Path) d.Set("cors", duplo.Cors) + d.Set("api_key_required", duplo.ApiKeyRequired) d.Set("authorizer_id", duplo.AuthorizerId) d.Set("authorization_type", duplo.AuthorizationType) if duplo.Integration != nil { @@ -183,10 +190,11 @@ func resourceAwsApiGatewayEventCreate(ctx context.Context, d *schema.ResourceDat // Create the request object. rq := duplosdk.DuploApiGatewayEvent{ - APIGatewayID: apigatewayid, - Method: method, - Path: path, - Cors: d.Get("cors").(bool), + APIGatewayID: apigatewayid, + Method: method, + Path: path, + Cors: d.Get("cors").(bool), + ApiKeyRequired: d.Get("api_key_required").(bool), } if v, ok := d.GetOk("authorizer_id"); ok && v != nil { rq.AuthorizerId = v.(string) @@ -234,10 +242,11 @@ func resourceAwsApiGatewayEventUpdate(ctx context.Context, d *schema.ResourceDat // Create the request object. rq := duplosdk.DuploApiGatewayEvent{ - APIGatewayID: apigatewayid, - Method: method, - Path: path, - Cors: d.Get("cors").(bool), + APIGatewayID: apigatewayid, + Method: method, + Path: path, + Cors: d.Get("cors").(bool), + ApiKeyRequired: d.Get("api_key_required").(bool), } if v, ok := d.GetOk("authorizer_id"); ok && v != nil { diff --git a/duplosdk/aws_apigateway_events.go b/duplosdk/aws_apigateway_events.go index c32d871d..c3a78e3d 100644 --- a/duplosdk/aws_apigateway_events.go +++ b/duplosdk/aws_apigateway_events.go @@ -9,6 +9,7 @@ type DuploApiGatewayEvent struct { Path string `json:"Path,omitempty"` Method string `json:"Method,omitempty"` Cors bool `json:"Cors,omitempty"` + ApiKeyRequired bool `json:"ApiKeyRequired,omitempty"` AuthorizerId string `json:"AuthorizerId,omitempty"` AuthorizationType string `json:"AuthorizationType,omitempty"` Integration *DuploApiGatewayEventIntegration `json:"Integration"` From eadafdfa80f3776e2a7fb58cc96d3cd9d107a692 Mon Sep 17 00:00:00 2001 From: nikhil Date: Mon, 1 Jul 2024 11:36:15 +0530 Subject: [PATCH 22/35] fixed diff issue at metadata. used diffSuppressWhenExisting since duplocloud_aws_host doesnt have update context --- duplocloud/resource_duplo_aws_host.go | 11 ++++++----- examples/resources/duplocloud_aws_host/import.sh | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/duplocloud/resource_duplo_aws_host.go b/duplocloud/resource_duplo_aws_host.go index e1bea18f..40fc7188 100644 --- a/duplocloud/resource_duplo_aws_host.go +++ b/duplocloud/resource_duplo_aws_host.go @@ -151,11 +151,12 @@ func nativeHostSchema() map[string]*schema.Schema { Computed: true, }, "metadata": { - Description: "Configuration metadata used when creating the host.", - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: KeyValueSchema(), + Description: "Configuration metadata used when creating the host.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: KeyValueSchema(), + DiffSuppressFunc: diffSuppressWhenExisting, }, "tags": { Type: schema.TypeList, diff --git a/examples/resources/duplocloud_aws_host/import.sh b/examples/resources/duplocloud_aws_host/import.sh index 7f119b05..95609036 100644 --- a/examples/resources/duplocloud_aws_host/import.sh +++ b/examples/resources/duplocloud_aws_host/import.sh @@ -4,3 +4,4 @@ # terraform import duplocloud_aws_host.myhost v2/subscriptions/*TENANT_ID*/NativeHostV2/*INSTANCE_ID* + From 7fcd55769b5cf9bdc2ae3a7b44aea0e7d3c3e465 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 2 Jul 2024 12:12:15 +0530 Subject: [PATCH 23/35] document update --- docs/resources/aws_apigateway_event.md | 1 + docs/resources/aws_dynamodb_table_v2.md | 69 ++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/resources/aws_apigateway_event.md b/docs/resources/aws_apigateway_event.md index 4dae8eec..c915136e 100644 --- a/docs/resources/aws_apigateway_event.md +++ b/docs/resources/aws_apigateway_event.md @@ -48,6 +48,7 @@ resource "duplocloud_aws_apigateway_event" "apigateway_event" { ### Optional +- `api_key_required` (Boolean) Specify if the method requires an API key. - `authorization_type` (String) Type of authorization used for the method. (`NONE`, `CUSTOM`, `AWS_IAM`, `COGNITO_USER_POOLS`) - `authorizer_id` (String) Authorizer id to be used when the authorization is `CUSTOM` or `COGNITO_USER_POOLS`. - `cors` (Boolean) Enable handling of preflight requests. diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index 1e51081d..fa6d4c3d 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -72,7 +72,6 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { } - resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { tenant_id = duplocloud_tenant.myapp.tenant_id @@ -148,6 +147,74 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { } } + + +resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { + + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "mytable" + read_capacity = 80 + write_capacity = 40 + billing_mode = "PROVISIONED" + is_point_in_time_recovery = false + deletion_protection_enabled = false + + tag { + key = "school" + value = "admission" + } + attribute { + name = "ForumName" + type = "S" + } + attribute { + name = "Subject" + type = "S" + } + attribute { + name = "LastPostDateTime" + type = "S" + } + attribute { + name = "PostMonth" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } + + attribute { + name = "TopScore" + type = "N" + } + + key_schema { + attribute_name = "UserId" + key_type = "HASH" + } + + key_schema { + attribute_name = "GameTitle" + key_type = "RANGE" + } + + global_secondary_index { + name = "GameTitleIndex" + hash_key = "GameTitle" + range_key = "TopScore" + write_capacity = 10 + read_capacity = 10 + projection_type = "INCLUDE" + non_key_attributes = ["UserId"] + } + + ttl { + attribute_name = "TimeToExist" + enabled = true + } +} ``` From 029309764f3e3a6dd38a7c94a33c25f38397387a Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 2 Jul 2024 13:31:42 +0530 Subject: [PATCH 24/35] changed gsi action implementation for update. removed log line. updated doc. tested after merge --- docs/resources/aws_dynamodb_table_v2.md | 1 - .../resource_duplo_aws_dynamodb_table_v2.go | 85 ++++++++++++------- duplosdk/client.go | 1 - 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index fa6d4c3d..0ee01045 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -282,7 +282,6 @@ Required: Optional: -- `delete_index` (Boolean) Set it to true when global index need to be deleted during update Defaults to `false`. - `non_key_attributes` (Set of String) Only required with `INCLUDE` as a projection type; a list of attributes to project into the index. These do not need to be defined as attributes on the table. - `range_key` (String) The name of the range key; must be defined. - `read_capacity` (Number) The number of read units for this index. Must be set if `billing_mode` is set to `PROVISIONED`. diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index f4928ce3..5eb4eb5b 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -167,12 +167,6 @@ func awsDynamoDBTableSchemaV2() map[string]*schema.Schema { Type: schema.TypeInt, Optional: true, }, - "delete_index": { - Description: "Set it to true when global index need to be deleted during update", - Type: schema.TypeBool, - Optional: true, - Default: false, - }, }, }, }, @@ -1084,11 +1078,46 @@ func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoD tenantID, name string, d *schema.ResourceData) diag.Diagnostics { existingIndex := make(map[string]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndexResponse) + rqIndex := make(map[string]duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex) + gsiu := []duplosdk.GlobalSecondaryIndexUpdates{} if existing != nil && d.HasChange("global_secondary_index") { for _, e := range *existing.GlobalSecondaryIndexes { existingIndex[e.IndexName] = e } + for _, ne := range *request.GlobalSecondaryIndexes { + rqIndex[ne.IndexName] = ne + } + //remove non staged gsi + for _, r := range *existing.GlobalSecondaryIndexes { + if _, ok := rqIndex[r.IndexName]; !ok { + del := duplosdk.GlobalSecondaryIndexUpdates{ + Delete: &duplosdk.Delete{ + IndexName: r.IndexName, + }, + } + gsiu = append(gsiu, del) + } + } + if len(gsiu) > 0 { + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } + + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + gsictx := context.Background() + er := dynamodbWaitUntilReady(gsictx, c, tenantID, name, d.Timeout("update")) + if er != nil { + return diag.FromErr(err) + } + gsiu = nil + } for _, r := range *request.GlobalSecondaryIndexes { if ev, ok := existingIndex[r.IndexName]; !ok { t := duplosdk.DuploDynamoDBTableV2GlobalSecondaryIndex{ @@ -1103,37 +1132,29 @@ func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoD gsiu = append(gsiu, cr) } else { - if r.DeleteIndex { - del := duplosdk.GlobalSecondaryIndexUpdates{ - Delete: &duplosdk.Delete{ - IndexName: r.IndexName, + ev = existingIndex[r.IndexName] + if (ev.ProvisionedThroughput.ReadCapacityUnits != r.ProvisionedThroughput.ReadCapacityUnits) || (ev.ProvisionedThroughput.WriteCapacityUnits != r.ProvisionedThroughput.WriteCapacityUnits) { + up := duplosdk.GlobalSecondaryIndexUpdates{ + Update: &duplosdk.Update{ + IndexName: r.IndexName, + ProvisionedThroughput: *r.ProvisionedThroughput, }, } - gsiu = append(gsiu, del) - - } else { - ev = existingIndex[r.IndexName] - if (ev.ProvisionedThroughput.ReadCapacityUnits != r.ProvisionedThroughput.ReadCapacityUnits) || (ev.ProvisionedThroughput.WriteCapacityUnits != r.ProvisionedThroughput.WriteCapacityUnits) { - up := duplosdk.GlobalSecondaryIndexUpdates{ - Update: &duplosdk.Update{ - IndexName: r.IndexName, - ProvisionedThroughput: *r.ProvisionedThroughput, - }, - } - gsiu = append(gsiu, up) - } + gsiu = append(gsiu, up) } } } - req := &duplosdk.ModifyGSI{ - TableName: name, - GlobalSecondaryIndexUpdates: gsiu, - AttributeDefinitions: *request.AttributeDefinitions, - } - _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) - if err != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, name, err) + if len(gsiu) > 0 { + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } } } diff --git a/duplosdk/client.go b/duplosdk/client.go index 30794c73..1c20cab8 100644 --- a/duplosdk/client.go +++ b/duplosdk/client.go @@ -221,7 +221,6 @@ func (c *Client) doAPI(verb string, apiName string, apiPath string, rp interface func (c *Client) doAPIWithRequestBody(verb string, apiName string, apiPath string, rq interface{}, rp interface{}) ClientError { apiName = fmt.Sprintf("%sAPI %s", strings.ToLower(verb), apiName) url := fmt.Sprintf("%s/%s", c.HostURL, apiPath) - log.Printf("REQUEST NODY BEFORE SERIALIZE %+v", rq) // Build the request rqBody, err := json.Marshal(rq) if err != nil { From 55ca8f8a471263cbce6f7eb947ca643f879a9b9d Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 2 Jul 2024 14:41:22 +0530 Subject: [PATCH 25/35] removed unwanted commented line --- docs/resources/aws_dynamodb_table_v2.md | 1 - examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/resources/aws_dynamodb_table_v2.md b/docs/resources/aws_dynamodb_table_v2.md index 0ee01045..44c6e967 100644 --- a/docs/resources/aws_dynamodb_table_v2.md +++ b/docs/resources/aws_dynamodb_table_v2.md @@ -134,7 +134,6 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { write_capacity = 5 read_capacity = 5 projection_type = "ALL" - #delete_index = true #To remove an global secondary index from associated table } server_side_encryption { enabled = false diff --git a/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf b/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf index 7035c81a..cc2389f8 100644 --- a/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf +++ b/examples/resources/duplocloud_aws_dynamodb_table_v2/resource.tf @@ -119,7 +119,6 @@ resource "duplocloud_aws_dynamodb_table_v2" "tst-dynamodb-table" { write_capacity = 5 read_capacity = 5 projection_type = "ALL" - #delete_index = true #To remove an global secondary index from associated table } server_side_encryption { enabled = false From 61f28b42b1305f842dc7e27de59256f4ac57d6c5 Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 2 Jul 2024 16:52:01 +0530 Subject: [PATCH 26/35] fixed creation and deletion of multi gsi in update context --- .../resource_duplo_aws_dynamodb_table_v2.go | 122 ++++++++++++++---- duplosdk/aws_dynamo_db.go | 2 +- 2 files changed, 96 insertions(+), 28 deletions(-) diff --git a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go index 5eb4eb5b..fe0841d1 100644 --- a/duplocloud/resource_duplo_aws_dynamodb_table_v2.go +++ b/duplocloud/resource_duplo_aws_dynamodb_table_v2.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "reflect" "strings" "terraform-provider-duplocloud/duplosdk" "time" @@ -531,22 +532,15 @@ func resourceAwsDynamoDBTableUpdateV2(ctx context.Context, d *schema.ResourceDat tagsToUpdate := []duplosdk.DuploKeyStringValue{} //tagsToDelete := []string{} for _, v := range *rq.Tags { - // if v.DeleteTag { - // tagsToDelete = append(tagsToDelete, v.Key) //waiting for backend change - // } else { tagsToUpdate = append(tagsToUpdate, duplosdk.DuploKeyStringValue{ Key: v.Key, Value: v.Value, }) - // } } tagReq := &duplosdk.DuploDynamoDBTagResourceRequest{ ResourceArn: existing.TableArn, Tags: &tagsToUpdate, } - //if len(tagsToDelete) > 0 { - // tagReq.TagKeys = &tagsToDelete - //} _, err = tagDynamoDBtTableV2(tenantID, tagReq, m) //taging and untaging dynamodb if err != nil { return diag.FromErr(err) @@ -818,7 +812,6 @@ func expandGlobalSecondaryIndex(data map[string]interface{}, billingMode string) KeySchema: expandKeySchema(data), Projection: expandProjection(data), ProvisionedThroughput: expandProvisionedThroughput(data, billingMode), - DeleteIndex: data["delete_index"].(bool), } } @@ -1073,6 +1066,38 @@ func dynamodbWaitUntilReady(ctx context.Context, c *duplosdk.Client, tenantID st return err } +func dynamodbWaitUntilGSIReady(ctx context.Context, c *duplosdk.Client, tenantID string, name string, timeout time.Duration, indexName string) error { + stateConf := &retry.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"ready"}, + Refresh: func() (interface{}, string, error) { + rp, err := c.DynamoDBTableGetV2(tenantID, name) + // log.Printf("[TRACE] Dynamodb status is (%s).", rp.TableStatus.Value) + status := "pending" + for _, gsi := range *rp.GlobalSecondaryIndexes { + if err == nil { + if gsi.IndexName == indexName { + if gsi.IndexStatus.Value == "ACTIVE" { + status = "ready" + } else { + status = "pending" + } + break + } + + } + } + return rp, status, err + }, + // MinTimeout will be 10 sec freq, if times-out forces 30 sec anyway + PollInterval: 30 * time.Second, + Timeout: timeout, + } + log.Printf("[DEBUG] dynamodbWaitUntilReady(%s, %s)", tenantID, name) + _, err := stateConf.WaitForStateContext(ctx) + return err +} + func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoDBTableV2Response, request *duplosdk.DuploDynamoDBTableRequestV2, tenantID, name string, d *schema.ResourceData) diag.Diagnostics { @@ -1090,33 +1115,59 @@ func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoD } //remove non staged gsi for _, r := range *existing.GlobalSecondaryIndexes { - if _, ok := rqIndex[r.IndexName]; !ok { + if rs, ok := rqIndex[r.IndexName]; !ok { del := duplosdk.GlobalSecondaryIndexUpdates{ Delete: &duplosdk.Delete{ IndexName: r.IndexName, }, } gsiu = append(gsiu, del) - } - } - if len(gsiu) > 0 { - req := &duplosdk.ModifyGSI{ - TableName: name, - GlobalSecondaryIndexUpdates: gsiu, - AttributeDefinitions: *request.AttributeDefinitions, - } + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } + //only one gsi can be deleted per update request + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + gsictx := context.Background() + er := dynamodbWaitUntilReady(gsictx, c, tenantID, name, d.Timeout("update")) + if er != nil { + return diag.FromErr(err) + } + gsiu = nil + } else if (r.Projection.ProjectionType.Value != rs.Projection.ProjectionType) || + (!reflect.DeepEqual(r.KeySchema, rs.KeySchema)) || + (!reflect.DeepEqual(r.Projection.NonKeyAttributes, rs.Projection.NonKeyAttributes)) { + del := duplosdk.GlobalSecondaryIndexUpdates{ + Delete: &duplosdk.Delete{ + IndexName: r.IndexName, + }, + } + gsiu = append(gsiu, del) + delete(existingIndex, r.IndexName) + //removing gsi for attribute change which is not supported for updation and provisioning for recreation. + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } - _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) - if err != nil { - e := "Error updating tenant %s DynamoDB table '%s': %s" - return diag.Errorf(e, tenantID, name, err) - } - gsictx := context.Background() - er := dynamodbWaitUntilReady(gsictx, c, tenantID, name, d.Timeout("update")) - if er != nil { - return diag.FromErr(err) + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + gsictx := context.Background() + er := dynamodbWaitUntilReady(gsictx, c, tenantID, name, d.Timeout("update")) + if er != nil { + return diag.FromErr(err) + } + gsiu = nil } - gsiu = nil } for _, r := range *request.GlobalSecondaryIndexes { if ev, ok := existingIndex[r.IndexName]; !ok { @@ -1130,7 +1181,24 @@ func globalIndexUpdateAction(c *duplosdk.Client, existing *duplosdk.DuploDynamoD Create: &t, } gsiu = append(gsiu, cr) + req := &duplosdk.ModifyGSI{ + TableName: name, + GlobalSecondaryIndexUpdates: gsiu, + AttributeDefinitions: *request.AttributeDefinitions, + } + //only one gsi can be created per update request + _, err := c.DynamoDBTableUpdateGSIV2(tenantID, req) + if err != nil { + e := "Error updating tenant %s DynamoDB table '%s': %s" + return diag.Errorf(e, tenantID, name, err) + } + gsictx := context.Background() + er := dynamodbWaitUntilGSIReady(gsictx, c, tenantID, name, d.Timeout("update"), r.IndexName) + if er != nil { + return diag.FromErr(err) + } + gsiu = nil } else { ev = existingIndex[r.IndexName] if (ev.ProvisionedThroughput.ReadCapacityUnits != r.ProvisionedThroughput.ReadCapacityUnits) || (ev.ProvisionedThroughput.WriteCapacityUnits != r.ProvisionedThroughput.WriteCapacityUnits) { diff --git a/duplosdk/aws_dynamo_db.go b/duplosdk/aws_dynamo_db.go index 6110d3a4..6d8774e9 100644 --- a/duplosdk/aws_dynamo_db.go +++ b/duplosdk/aws_dynamo_db.go @@ -181,7 +181,6 @@ type DuploDynamoDBTableV2GlobalSecondaryIndex struct { Projection *DuploDynamoDBTableV2Projection `json:"Projection,omitempty"` KeySchema *[]DuploDynamoDBKeySchema `json:"KeySchema,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` - DeleteIndex bool `json:"-"` } type DuploDynamoDBTableV2GlobalSecondaryIndexResponse struct { @@ -189,6 +188,7 @@ type DuploDynamoDBTableV2GlobalSecondaryIndexResponse struct { Projection *DuploDynamoDBTableV2ProjectionResponse `json:"Projection,omitempty"` KeySchema *[]DuploDynamoDBKeySchemaResponse `json:"KeySchema,omitempty"` ProvisionedThroughput *DuploDynamoDBProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` + IndexStatus *DuploStringValue `json:"IndexStatus,omitempty"` } type UpdateGSIReq struct { UpdateGSI UpdateGSI `json:"Update"` From 2e0a52c94279805e6d7d2df4445e0358d287dbca Mon Sep 17 00:00:00 2001 From: nikhil Date: Tue, 2 Jul 2024 20:02:12 +0530 Subject: [PATCH 27/35] fixed masking bug --- duplocloud/data_source_duplo_k8_secret.go | 1 - 1 file changed, 1 deletion(-) diff --git a/duplocloud/data_source_duplo_k8_secret.go b/duplocloud/data_source_duplo_k8_secret.go index 53b94b6e..c910fde8 100644 --- a/duplocloud/data_source_duplo_k8_secret.go +++ b/duplocloud/data_source_duplo_k8_secret.go @@ -66,7 +66,6 @@ func dataSourceK8SecretRead(ctx context.Context, d *schema.ResourceData, m inter break } } - usrResp.IsReadOnly = true } rp, err := c.K8SecretGet(tenantID, name) if err != nil { From ba2a7582d6aecc354e21494e0cfc0715ed62f8de Mon Sep 17 00:00:00 2001 From: Matheus Bafutto Date: Tue, 2 Jul 2024 11:56:16 -0400 Subject: [PATCH 28/35] DUPLO-18156 feat: add support for fifo processing for sns topics --- duplocloud/resource_duplo_aws_sns_topic.go | 32 +++++++++- duplosdk/aws_sns.go | 61 +++++++++++++++++-- .../duplocloud_aws_sns_topic/resource.tf | 3 +- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/duplocloud/resource_duplo_aws_sns_topic.go b/duplocloud/resource_duplo_aws_sns_topic.go index 3d5c253a..ea32d9f7 100644 --- a/duplocloud/resource_duplo_aws_sns_topic.go +++ b/duplocloud/resource_duplo_aws_sns_topic.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "reflect" "strings" "terraform-provider-duplocloud/duplosdk" "time" @@ -34,6 +35,13 @@ func duploAwsSnsTopicSchema() map[string]*schema.Schema { Optional: true, Computed: true, }, + "fifo_topic": { + Description: "Whether the topic processes messages as fifo or not", + Type: schema.TypeBool, + ForceNew: true, + Optional: true, + Default: false, + }, "arn": { Description: "The ARN of the SNS topic.", Type: schema.TypeString, @@ -94,6 +102,9 @@ func resourceAwsSnsTopicRead(ctx context.Context, d *schema.ResourceData, m inte return diag.Errorf("Unable to retrieve tenant %s sns topic %s : %s", tenantID, arn, clientErr) } + attributes, _ := c.TenantGetSnsTopicAttributes(tenantID, topic.Name) + d.Set("fifo_topic", attributes.FifoTopic) + prefix, err := c.GetDuploServicesPrefix(tenantID) if err != nil { return diag.FromErr(err) @@ -172,9 +183,26 @@ func resourceAwsSnsTopicDelete(ctx context.Context, d *schema.ResourceData, m in } func expandAwsSnsTopic(d *schema.ResourceData) *duplosdk.DuploSnsTopic { + var extraAttributes = duplosdk.DuploSnsTopicAttributesCreate{} + + addIfDefined(&extraAttributes, "FifoTopic", d.Get("fifo_topic")) return &duplosdk.DuploSnsTopic{ - Name: d.Get("name").(string), - KmsKeyId: d.Get("kms_key_id").(string), + Name: d.Get("name").(string), + KmsKeyId: d.Get("kms_key_id").(string), + ExtraTopicAttributes: extraAttributes, + } +} + +func addIfDefined(target interface{}, resourceName string, targetValue interface{}) { + v := reflect.ValueOf(target).Elem() + field := v.FieldByName(resourceName) + if field.IsValid() && field.CanSet() && targetValue != nil { + + val := reflect.ValueOf(targetValue) + + if val.Type().AssignableTo(field.Type()) { + field.Set(val) + } } } diff --git a/duplosdk/aws_sns.go b/duplosdk/aws_sns.go index cadef3e1..cd5333a4 100644 --- a/duplosdk/aws_sns.go +++ b/duplosdk/aws_sns.go @@ -2,16 +2,46 @@ package duplosdk import ( "fmt" + "strings" + "time" ) type DuploSnsTopic struct { - Name string `json:"Name"` - KmsKeyId string `json:"KmsKeyId,omitempty"` + Name string `json:"Name"` + KmsKeyId string `json:"KmsKeyId,omitempty"` + ExtraTopicAttributes DuploSnsTopicAttributesCreate `json:"ExtraTopicAttributes,omitempty"` } type DuploSnsTopicResource struct { - Name string `json:"Name"` - ResourceType int `json:"ResourceType,omitempty"` + Name string `json:"Name"` + ResourceType int `json:"ResourceType,omitempty"` + ExtraTopicAttributes DuploSnsTopicAttributes `json:"ExtraTopicAttributes,omitempty"` +} + +type DuploSnsTopicAttributesCreate struct { + DeliveryPolicy string `json:"DeliveryPolicy,omitempty"` + DisplayName string `json:"DisplayName,omitempty"` + FifoTopic bool `json:"FifoTopic,omitempty"` + Policy string `json:"Policy,omitempty"` + SignatureVersion string `json:"SignatureVersion,omitempty"` + TracingConfig string `json:"TracingConfig,omitempty"` + KmsMasterKeyId string `json:"KmsMasterKeyId,omitempty"` + ArchivePolicy string `json:"ArchivePolicy,omitempty"` + BeginningArchiveTime string `json:"BeginningArchiveTime,omitempty"` + ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` +} + +type DuploSnsTopicAttributes struct { + Policy string `json:"Policy,omitempty"` + Owner string `json:"Owner,omitempty"` + SubscriptionsPending int `json:"SubscriptionsPending,omitempty"` + TopicArn string `json:"TopicArn,omitempty"` + EffectiveDeliveryPolicy string `json:"EffectiveDeliveryPolicy,omitempty"` + SubscriptionsConfirmed int `json:"SubscriptionsConfirmed,omitempty"` + FifoTopic string `json:"FifoTopic,omitempty"` + DisplayName string `json:"DisplayName,omitempty"` + ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` + SubscriptionsDeleted int `json:"SubscriptionsDeleted,omitempty"` } func (c *Client) DuploSnsTopicCreate(tenantID string, rq *DuploSnsTopic) (*DuploSnsTopicResource, ClientError) { @@ -43,6 +73,29 @@ func (c *Client) TenantListSnsTopic(tenantID string) (*[]DuploSnsTopicResource, return &rp, err } +func (c *Client) TenantGetSnsTopicAttributes(tenantID string, topicArn string) (*DuploSnsTopicAttributes, ClientError) { + rp := DuploSnsTopicAttributes{} + _, err := RetryWithExponentialBackoff(func() (interface{}, ClientError) { + err := c.getAPI( + fmt.Sprintf("TenantListSnsTopic(%s)", tenantID), + fmt.Sprintf("v3/subscriptions/%s/aws/snsTopic/%s/attributes", tenantID, topicArn), + &rp, + ) + return &rp, err + }, + RetryConfig{ + MinDelay: 1 * time.Second, + MaxDelay: 5 * time.Second, + MaxJitter: 2000, + Timeout: 60 * time.Second, + IsRetryable: func(error ClientError) bool { + return error.Status() == 400 || strings.Contains(error.Error(), "context deadline exceeded") + }, + }) + + return &rp, err +} + func (c *Client) TenantGetSnsTopic(tenantID string, arn string) (*DuploSnsTopicResource, ClientError) { list, err := c.TenantListSnsTopic(tenantID) if err != nil { diff --git a/examples/resources/duplocloud_aws_sns_topic/resource.tf b/examples/resources/duplocloud_aws_sns_topic/resource.tf index 0a2ca111..187b787d 100644 --- a/examples/resources/duplocloud_aws_sns_topic/resource.tf +++ b/examples/resources/duplocloud_aws_sns_topic/resource.tf @@ -3,10 +3,11 @@ resource "duplocloud_tenant" "myapp" { plan_id = "default" } -# Without KMS Key +# Without KMS Key running as fifo resource "duplocloud_aws_sns_topic" "sns_topic" { tenant_id = duplocloud_tenant.myapp.tenant_id name = "duplo_topic" + fifo_topic = true } # With Tenant KMS Key From 89e425e2e7223f6d2dfb20cd740a474b2f27e3b5 Mon Sep 17 00:00:00 2001 From: Matheus Bafutto Date: Tue, 2 Jul 2024 12:00:13 -0400 Subject: [PATCH 29/35] DUPLO-18156 docs: update documentation --- docs/resources/aws_apigateway_event.md | 1 + docs/resources/aws_sns_topic.md | 8 +++++--- examples/resources/duplocloud_aws_sns_topic/resource.tf | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/resources/aws_apigateway_event.md b/docs/resources/aws_apigateway_event.md index 4dae8eec..c915136e 100644 --- a/docs/resources/aws_apigateway_event.md +++ b/docs/resources/aws_apigateway_event.md @@ -48,6 +48,7 @@ resource "duplocloud_aws_apigateway_event" "apigateway_event" { ### Optional +- `api_key_required` (Boolean) Specify if the method requires an API key. - `authorization_type` (String) Type of authorization used for the method. (`NONE`, `CUSTOM`, `AWS_IAM`, `COGNITO_USER_POOLS`) - `authorizer_id` (String) Authorizer id to be used when the authorization is `CUSTOM` or `COGNITO_USER_POOLS`. - `cors` (Boolean) Enable handling of preflight requests. diff --git a/docs/resources/aws_sns_topic.md b/docs/resources/aws_sns_topic.md index 8dc4d398..c64bdac8 100644 --- a/docs/resources/aws_sns_topic.md +++ b/docs/resources/aws_sns_topic.md @@ -18,10 +18,11 @@ resource "duplocloud_tenant" "myapp" { plan_id = "default" } -# Without KMS Key +# Without KMS Key running as fifo resource "duplocloud_aws_sns_topic" "sns_topic" { - tenant_id = duplocloud_tenant.myapp.tenant_id - name = "duplo_topic" + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics + fifo_topic = true } # With Tenant KMS Key @@ -46,6 +47,7 @@ resource "duplocloud_aws_sns_topic" "sns_topic" { ### Optional +- `fifo_topic` (Boolean) Whether the topic processes messages as fifo or not Defaults to `false`. - `kms_key_id` (String) The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) diff --git a/examples/resources/duplocloud_aws_sns_topic/resource.tf b/examples/resources/duplocloud_aws_sns_topic/resource.tf index 187b787d..5aee0754 100644 --- a/examples/resources/duplocloud_aws_sns_topic/resource.tf +++ b/examples/resources/duplocloud_aws_sns_topic/resource.tf @@ -5,8 +5,8 @@ resource "duplocloud_tenant" "myapp" { # Without KMS Key running as fifo resource "duplocloud_aws_sns_topic" "sns_topic" { - tenant_id = duplocloud_tenant.myapp.tenant_id - name = "duplo_topic" + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics fifo_topic = true } From dbd405c1c86530e522d94bbf355d2546f1c41847 Mon Sep 17 00:00:00 2001 From: Matheus Bafutto Date: Wed, 3 Jul 2024 11:46:53 -0400 Subject: [PATCH 30/35] DUPLO-18156 chore: apply PR feedback --- duplocloud/resource_duplo_aws_sns_topic.go | 20 +++++--------------- duplocloud/utils.go | 13 +++++++++++++ duplosdk/aws_sns.go | 11 +---------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/duplocloud/resource_duplo_aws_sns_topic.go b/duplocloud/resource_duplo_aws_sns_topic.go index ea32d9f7..fbbf4a03 100644 --- a/duplocloud/resource_duplo_aws_sns_topic.go +++ b/duplocloud/resource_duplo_aws_sns_topic.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "reflect" "strings" "terraform-provider-duplocloud/duplosdk" "time" @@ -102,7 +101,11 @@ func resourceAwsSnsTopicRead(ctx context.Context, d *schema.ResourceData, m inte return diag.Errorf("Unable to retrieve tenant %s sns topic %s : %s", tenantID, arn, clientErr) } - attributes, _ := c.TenantGetSnsTopicAttributes(tenantID, topic.Name) + attributes, err := c.TenantGetSnsTopicAttributes(tenantID, topic.Name) + if err != nil { + return diag.Errorf("Failed to retrieve attributes from sns topic %s in tenant %s : %s", tenantID, arn, err) + } + d.Set("fifo_topic", attributes.FifoTopic) prefix, err := c.GetDuploServicesPrefix(tenantID) @@ -193,19 +196,6 @@ func expandAwsSnsTopic(d *schema.ResourceData) *duplosdk.DuploSnsTopic { } } -func addIfDefined(target interface{}, resourceName string, targetValue interface{}) { - v := reflect.ValueOf(target).Elem() - field := v.FieldByName(resourceName) - if field.IsValid() && field.CanSet() && targetValue != nil { - - val := reflect.ValueOf(targetValue) - - if val.Type().AssignableTo(field.Type()) { - field.Set(val) - } - } -} - func parseAwsSnsTopicIdParts(id string) (tenantID, arn string, err error) { idParts := strings.SplitN(id, "/", 2) if len(idParts) == 2 { diff --git a/duplocloud/utils.go b/duplocloud/utils.go index e19b127e..fa3667ff 100644 --- a/duplocloud/utils.go +++ b/duplocloud/utils.go @@ -945,3 +945,16 @@ func diffSuppressSpecifiedMetadata(k, old, new string, d *schema.ResourceData) b func diffSuppressStringCase(k, old, new string, d *schema.ResourceData) bool { return strings.EqualFold(old, new) } + +func addIfDefined(target interface{}, resourceName string, targetValue interface{}) { + v := reflect.ValueOf(target).Elem() + field := v.FieldByName(resourceName) + if field.IsValid() && field.CanSet() && targetValue != nil { + + val := reflect.ValueOf(targetValue) + + if val.Type().AssignableTo(field.Type()) { + field.Set(val) + } + } +} diff --git a/duplosdk/aws_sns.go b/duplosdk/aws_sns.go index cd5333a4..1668bf13 100644 --- a/duplosdk/aws_sns.go +++ b/duplosdk/aws_sns.go @@ -32,16 +32,7 @@ type DuploSnsTopicAttributesCreate struct { } type DuploSnsTopicAttributes struct { - Policy string `json:"Policy,omitempty"` - Owner string `json:"Owner,omitempty"` - SubscriptionsPending int `json:"SubscriptionsPending,omitempty"` - TopicArn string `json:"TopicArn,omitempty"` - EffectiveDeliveryPolicy string `json:"EffectiveDeliveryPolicy,omitempty"` - SubscriptionsConfirmed int `json:"SubscriptionsConfirmed,omitempty"` - FifoTopic string `json:"FifoTopic,omitempty"` - DisplayName string `json:"DisplayName,omitempty"` - ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` - SubscriptionsDeleted int `json:"SubscriptionsDeleted,omitempty"` + FifoTopic string `json:"FifoTopic,omitempty"` } func (c *Client) DuploSnsTopicCreate(tenantID string, rq *DuploSnsTopic) (*DuploSnsTopicResource, ClientError) { From 0cd1b4d1a103e00da7198b11293ce7eb52b5eee3 Mon Sep 17 00:00:00 2001 From: Matheus Bafutto Date: Wed, 3 Jul 2024 11:50:38 -0400 Subject: [PATCH 31/35] DUPLO-18156 chore: unprune sns topic attributes --- duplosdk/aws_sns.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/duplosdk/aws_sns.go b/duplosdk/aws_sns.go index 1668bf13..191efefe 100644 --- a/duplosdk/aws_sns.go +++ b/duplosdk/aws_sns.go @@ -32,7 +32,17 @@ type DuploSnsTopicAttributesCreate struct { } type DuploSnsTopicAttributes struct { - FifoTopic string `json:"FifoTopic,omitempty"` + Policy string `json:"Policy,omitempty"` + Owner string `json:"Owner,omitempty"` + SubscriptionsPending int `json:"SubscriptionsPending,omitempty"` + TopicArn string `json:"TopicArn,omitempty"` + EffectiveDeliveryPolicy string `json:"EffectiveDeliveryPolicy,omitempty"` + SubscriptionsConfirmed int `json:"SubscriptionsConfirmed,omitempty"` + FifoTopic string `json:"FifoTopic,omitempty"` + KmsMasterKeyId string `json:"KmsMasterKeyId,omitempty"` + DisplayName string `json:"DisplayName,omitempty"` + ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` + SubscriptionsDeleted int `json:"SubscriptionsDeleted,omitempty"` } func (c *Client) DuploSnsTopicCreate(tenantID string, rq *DuploSnsTopic) (*DuploSnsTopicResource, ClientError) { From ee84d6dd4a780519a385d311d5588b7b4263617a Mon Sep 17 00:00:00 2001 From: nikhil Date: Thu, 4 Jul 2024 10:17:52 +0530 Subject: [PATCH 32/35] created new function to flaten gcp_s3 data for duplocloud_gcp_s3_bucket resource --- duplocloud/resource_duplo_gcp_s3_bucket.go | 24 +++++++++++++++++++--- duplocloud/resource_duplo_s3_bucket.go | 1 - 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/duplocloud/resource_duplo_gcp_s3_bucket.go b/duplocloud/resource_duplo_gcp_s3_bucket.go index 48596108..24210bcb 100644 --- a/duplocloud/resource_duplo_gcp_s3_bucket.go +++ b/duplocloud/resource_duplo_gcp_s3_bucket.go @@ -182,7 +182,7 @@ func resourceGCPS3BucketRead(ctx context.Context, d *schema.ResourceData, m inte } // Set simple fields first. - resourceS3BucketSetData(d, tenantID, name, duplo) + resourceGcpS3BucketSetData(d, tenantID, name, duplo) log.Printf("[TRACE] resourceGCPS3BucketRead ******** end") return nil @@ -231,7 +231,7 @@ func resourceGCPS3BucketCreate(ctx context.Context, d *schema.ResourceData, m in d.SetId(id) // Set simple fields first. - resourceS3BucketSetData(d, tenantID, name, duplo) + resourceGcpS3BucketSetData(d, tenantID, name, duplo) log.Printf("[TRACE] resourceGCPS3BucketCreate ******** end") return nil } @@ -261,7 +261,7 @@ func resourceGCPS3BucketUpdate(ctx context.Context, d *schema.ResourceData, m in return diag.Errorf("resourceGCPS3BucketUpdate: Unable to update s3 bucket using v3 api (tenant: %s, bucket: %s: error: %s)", tenantID, duploObject.Name, err) } - resourceS3BucketSetData(d, tenantID, name, resource) + resourceGcpS3BucketSetData(d, tenantID, name, resource) log.Printf("[TRACE] resourceGCPS3BucketUpdate ******** end") return nil @@ -298,3 +298,21 @@ func resourceGCPS3BucketDelete(ctx context.Context, d *schema.ResourceData, m in log.Printf("[TRACE] resourceGCPS3BucketDelete ******** end") return nil } + +func resourceGcpS3BucketSetData(d *schema.ResourceData, tenantID string, name string, duplo *duplosdk.DuploS3Bucket) { + d.Set("tenant_id", tenantID) + d.Set("name", name) + d.Set("fullname", duplo.Name) + d.Set("domain_name", duplo.DomainName) + d.Set("arn", duplo.Arn) + d.Set("enable_versioning", duplo.EnableVersioning) + d.Set("enable_access_logs", duplo.EnableAccessLogs) + d.Set("allow_public_access", duplo.AllowPublicAccess) + d.Set("default_encryption", []map[string]interface{}{{ + "method": duplo.DefaultEncryption, + }}) + d.Set("managed_policies", duplo.Policies) + d.Set("tags", keyValueToState("tags", duplo.Tags)) + d.Set("region", duplo.Region) + d.Set("location", duplo.Location) +} diff --git a/duplocloud/resource_duplo_s3_bucket.go b/duplocloud/resource_duplo_s3_bucket.go index dee677e8..9cde061f 100644 --- a/duplocloud/resource_duplo_s3_bucket.go +++ b/duplocloud/resource_duplo_s3_bucket.go @@ -425,7 +425,6 @@ func resourceS3BucketSetData(d *schema.ResourceData, tenantID string, name strin d.Set("managed_policies", duplo.Policies) d.Set("tags", keyValueToState("tags", duplo.Tags)) d.Set("region", duplo.Region) - d.Set("location", duplo.Location) } func fillS3BucketRequest(duploObject *duplosdk.DuploS3BucketSettingsRequest, d *schema.ResourceData) error { From 6e5358bdfa313071305399141f98cfa3deb92f38 Mon Sep 17 00:00:00 2001 From: Matheus Bafutto Date: Fri, 5 Jul 2024 15:19:49 -0400 Subject: [PATCH 33/35] DUPLO-18156 feat: add support for content based deduplication on fifo SNS topics --- docs/resources/aws_sns_topic.md | 8 +++++--- duplocloud/resource_duplo_aws_sns_topic.go | 9 +++++++++ duplosdk/aws_sns.go | 11 ++++++----- .../resources/duplocloud_aws_sns_topic/resource.tf | 7 ++++--- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/resources/aws_sns_topic.md b/docs/resources/aws_sns_topic.md index c64bdac8..0c6d15f6 100644 --- a/docs/resources/aws_sns_topic.md +++ b/docs/resources/aws_sns_topic.md @@ -20,9 +20,10 @@ resource "duplocloud_tenant" "myapp" { # Without KMS Key running as fifo resource "duplocloud_aws_sns_topic" "sns_topic" { - tenant_id = duplocloud_tenant.myapp.tenant_id - name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics - fifo_topic = true + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics + fifo_topic = true + fifo_content_based_deduplication = true } # With Tenant KMS Key @@ -47,6 +48,7 @@ resource "duplocloud_aws_sns_topic" "sns_topic" { ### Optional +- `fifo_content_based_deduplication` (Boolean) Whether to enable content based deduplication for fifo type SNS topics Defaults to `false`. - `fifo_topic` (Boolean) Whether the topic processes messages as fifo or not Defaults to `false`. - `kms_key_id` (String) The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) diff --git a/duplocloud/resource_duplo_aws_sns_topic.go b/duplocloud/resource_duplo_aws_sns_topic.go index fbbf4a03..e6395f88 100644 --- a/duplocloud/resource_duplo_aws_sns_topic.go +++ b/duplocloud/resource_duplo_aws_sns_topic.go @@ -41,6 +41,13 @@ func duploAwsSnsTopicSchema() map[string]*schema.Schema { Optional: true, Default: false, }, + "fifo_content_based_deduplication": { + Description: "Whether to enable content based deduplication for fifo type SNS topics", + Type: schema.TypeBool, + ForceNew: true, + Optional: true, + Default: false, + }, "arn": { Description: "The ARN of the SNS topic.", Type: schema.TypeString, @@ -107,6 +114,7 @@ func resourceAwsSnsTopicRead(ctx context.Context, d *schema.ResourceData, m inte } d.Set("fifo_topic", attributes.FifoTopic) + d.Set("fifo_content_based_deduplication", attributes.ContentBasedDeduplication) prefix, err := c.GetDuploServicesPrefix(tenantID) if err != nil { @@ -189,6 +197,7 @@ func expandAwsSnsTopic(d *schema.ResourceData) *duplosdk.DuploSnsTopic { var extraAttributes = duplosdk.DuploSnsTopicAttributesCreate{} addIfDefined(&extraAttributes, "FifoTopic", d.Get("fifo_topic")) + addIfDefined(&extraAttributes, "ContentBasedDeduplication", d.Get("fifo_content_based_deduplication")) return &duplosdk.DuploSnsTopic{ Name: d.Get("name").(string), KmsKeyId: d.Get("kms_key_id").(string), diff --git a/duplosdk/aws_sns.go b/duplosdk/aws_sns.go index 191efefe..51f0d3f5 100644 --- a/duplosdk/aws_sns.go +++ b/duplosdk/aws_sns.go @@ -28,21 +28,22 @@ type DuploSnsTopicAttributesCreate struct { KmsMasterKeyId string `json:"KmsMasterKeyId,omitempty"` ArchivePolicy string `json:"ArchivePolicy,omitempty"` BeginningArchiveTime string `json:"BeginningArchiveTime,omitempty"` - ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` + ContentBasedDeduplication bool `json:"ContentBasedDeduplication,omitempty"` } type DuploSnsTopicAttributes struct { Policy string `json:"Policy,omitempty"` Owner string `json:"Owner,omitempty"` - SubscriptionsPending int `json:"SubscriptionsPending,omitempty"` + SubscriptionsPending string `json:"SubscriptionsPending,omitempty"` TopicArn string `json:"TopicArn,omitempty"` EffectiveDeliveryPolicy string `json:"EffectiveDeliveryPolicy,omitempty"` - SubscriptionsConfirmed int `json:"SubscriptionsConfirmed,omitempty"` + SubscriptionsConfirmed string `json:"SubscriptionsConfirmed,omitempty"` FifoTopic string `json:"FifoTopic,omitempty"` KmsMasterKeyId string `json:"KmsMasterKeyId,omitempty"` DisplayName string `json:"DisplayName,omitempty"` + ArchivePolicy string `json:"ArchivePolicy,omitempty"` ContentBasedDeduplication string `json:"ContentBasedDeduplication,omitempty"` - SubscriptionsDeleted int `json:"SubscriptionsDeleted,omitempty"` + SubscriptionsDeleted string `json:"SubscriptionsDeleted,omitempty"` } func (c *Client) DuploSnsTopicCreate(tenantID string, rq *DuploSnsTopic) (*DuploSnsTopicResource, ClientError) { @@ -78,7 +79,7 @@ func (c *Client) TenantGetSnsTopicAttributes(tenantID string, topicArn string) ( rp := DuploSnsTopicAttributes{} _, err := RetryWithExponentialBackoff(func() (interface{}, ClientError) { err := c.getAPI( - fmt.Sprintf("TenantListSnsTopic(%s)", tenantID), + fmt.Sprintf("TenantListSnsTopicAttributes(%s)", tenantID), fmt.Sprintf("v3/subscriptions/%s/aws/snsTopic/%s/attributes", tenantID, topicArn), &rp, ) diff --git a/examples/resources/duplocloud_aws_sns_topic/resource.tf b/examples/resources/duplocloud_aws_sns_topic/resource.tf index 5aee0754..547eba7b 100644 --- a/examples/resources/duplocloud_aws_sns_topic/resource.tf +++ b/examples/resources/duplocloud_aws_sns_topic/resource.tf @@ -5,9 +5,10 @@ resource "duplocloud_tenant" "myapp" { # Without KMS Key running as fifo resource "duplocloud_aws_sns_topic" "sns_topic" { - tenant_id = duplocloud_tenant.myapp.tenant_id - name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics - fifo_topic = true + tenant_id = duplocloud_tenant.myapp.tenant_id + name = "duplo_topic.fifo" # AWS requires the ".fifo" extension for fifo sns topics + fifo_topic = true + fifo_content_based_deduplication = true } # With Tenant KMS Key From b2527820bf580376fd54fdba156de67c0c0b101c Mon Sep 17 00:00:00 2001 From: Tahir Tamboli Date: Mon, 8 Jul 2024 23:23:43 +0530 Subject: [PATCH 34/35] Patch method added in validation. --- duplocloud/resource_duplo_aws_apigateway_event.go | 1 + 1 file changed, 1 insertion(+) diff --git a/duplocloud/resource_duplo_aws_apigateway_event.go b/duplocloud/resource_duplo_aws_apigateway_event.go index 0e1a35dd..5a565a0c 100644 --- a/duplocloud/resource_duplo_aws_apigateway_event.go +++ b/duplocloud/resource_duplo_aws_apigateway_event.go @@ -40,6 +40,7 @@ func AwsApiGatewayEventSchema() map[string]*schema.Schema { "DELETE", "HEAD", "OPTIONS", + "PATCH", "ANY", }, false), }, From 55f62655b2b5b65fd07f1a2f22f60c3ff5b5808a Mon Sep 17 00:00:00 2001 From: duplo-bot Date: Tue, 9 Jul 2024 07:12:09 +0000 Subject: [PATCH 35/35] [release] version bump --- docs/resources/aws_elasticsearch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/aws_elasticsearch.md b/docs/resources/aws_elasticsearch.md index dc5b301a..aff368af 100644 --- a/docs/resources/aws_elasticsearch.md +++ b/docs/resources/aws_elasticsearch.md @@ -50,7 +50,7 @@ resource "duplocloud_aws_elasticsearch" "es-doc" { - `encrypt_at_rest` (Block List, Max: 1) The storage encryption settings for the ElasticSearch instance. (see [below for nested schema](#nestedblock--encrypt_at_rest)) - `require_ssl` (Boolean) Whether or not to require SSL for accessing this ElasticSearch instance. - `selected_zone` (Number) The numerical index of the zone to launch this ElasticSearch instance in. -- `storage_size` (Number) The storage volume size, in GB, for the ElasticSearch instance. Defaults to `20`. +- `storage_size` (Number) The storage volume size, in GB, for the ElasticSearch instance. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) - `use_latest_tls_cipher` (Boolean) Whether or not to use the latest TLS cipher for this ElasticSearch instance. - `vpc_options` (Block List) (see [below for nested schema](#nestedblock--vpc_options))