Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

r/aws_dynamodb_table_export - support DynamoDB ExportTableToPointInTime API. #30399

Merged
merged 10 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/30399.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_dynamodb_table_export
```
1 change: 1 addition & 0 deletions .github/workflows/semgrep-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
env:
SEMGREP_SEND_METRICS: "off"
SEMGREP_ENABLE_VERSION_CHECK: false
SEMGREP_TIMEOUT: 300
COMMON_PARAMS: --error --quiet

jobs:
Expand Down
28 changes: 25 additions & 3 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ cleantidy: prereq-go ## Clean up tidy
@echo "make: Go mods tidied"

clean: cleango cleantidy build tools ## Clean up Go cache, tidy and re-install tools
@echo "make: clean complete"
@echo "make: clean complete"

copyright: ## Run copywrite (generate source code headers)
@copywrite headers
Expand Down Expand Up @@ -317,7 +317,28 @@ sanity: prereq-go ## Run sanity checks with failures allowed

semall: semgrep-validate ## Run semgrep on all files
@echo "make: running Semgrep checks locally (must have semgrep installed)..."
@semgrep --error --metrics=off \
@SEMGREP_TIMEOUT=300 semgrep --error --metrics=off \
$(if $(filter-out $(origin PKG), undefined),--include $(PKG_NAME),) \
--config .ci/.semgrep.yml \
--config .ci/.semgrep-caps-aws-ec2.yml \
--config .ci/.semgrep-configs.yml \
--config .ci/.semgrep-service-name0.yml \
--config .ci/.semgrep-service-name1.yml \
--config .ci/.semgrep-service-name2.yml \
--config .ci/.semgrep-service-name3.yml \
--config .ci/semgrep/ \
--config 'r/dgryski.semgrep-go.badnilguard' \
--config 'r/dgryski.semgrep-go.errnilcheck' \
--config 'r/dgryski.semgrep-go.marshaljson' \
--config 'r/dgryski.semgrep-go.nilerr' \
--config 'r/dgryski.semgrep-go.oddifsequence' \
--config 'r/dgryski.semgrep-go.oserrors'

semfix: semgrep-validate ## Run semgrep on all files
@echo "make: running Semgrep checks locally (must have semgrep installed)..."
@echo "make: applying fixes with --autofix"
@echo "make: WARNING: This will not fix rules that don't have autofixes"
@SEMGREP_TIMEOUT=300 semgrep --error --metrics=off --autofix \
$(if $(filter-out $(origin PKG), undefined),--include $(PKG_NAME),) \
--config .ci/.semgrep.yml \
--config .ci/.semgrep-caps-aws-ec2.yml \
Expand All @@ -335,7 +356,7 @@ semall: semgrep-validate ## Run semgrep on all files
--config 'r/dgryski.semgrep-go.oserrors'

semgrep-validate: ## Validate semgrep configuration files
@semgrep --error --validate \
@SEMGREP_TIMEOUT=300 semgrep --error --validate \
--config .ci/.semgrep.yml \
--config .ci/.semgrep-caps-aws-ec2.yml \
--config .ci/.semgrep-configs.yml \
Expand Down Expand Up @@ -485,6 +506,7 @@ yamllint: ## Lint YAML files (via yamllint)
sane \
sanity \
semall \
semfix \
semgrep \
semgrep-validate \
skaff \
Expand Down
21 changes: 21 additions & 0 deletions internal/service/dynamodb/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,24 @@ func FindContributorInsights(ctx context.Context, conn *dynamodb.DynamoDB, table

return output, nil
}

func FindTableExportByID(ctx context.Context, conn *dynamodb.DynamoDB, id string) (*dynamodb.DescribeExportOutput, error) {
input := &dynamodb.DescribeExportInput{
ExportArn: aws.String(id),
}

out, err := conn.DescribeExportWithContext(ctx, input)

if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if out == nil || out.ExportDescription == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return out, nil
}
4 changes: 4 additions & 0 deletions internal/service/dynamodb/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions internal/service/dynamodb/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,22 @@ func statusContributorInsights(ctx context.Context, conn *dynamodb.DynamoDB, tab
return insight, aws.StringValue(insight.ContributorInsightsStatus), nil
}
}

func statusTableExport(ctx context.Context, conn *dynamodb.DynamoDB, id string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
out, err := FindTableExportByID(ctx, conn, id)
if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

if out.ExportDescription == nil {
return nil, "", nil
}

return out, aws.StringValue(out.ExportDescription.ExportStatus), nil
}
}
224 changes: 224 additions & 0 deletions internal/service/dynamodb/table_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamodb

import (
"context"
"errors"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"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"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @SDKResource("aws_dynamodb_table_export")
func ResourceTableExport() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceTableExportCreate,
ReadWithoutTimeout: resourceTableExportRead,
DeleteWithoutTimeout: schema.NoopContext,

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(60 * time.Minute),
Delete: schema.DefaultTimeout(60 * time.Minute),
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"billed_size_in_bytes": {
Type: schema.TypeInt,
Computed: true,
},
"end_time": {
Type: schema.TypeString,
Computed: true,
},
"export_format": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(dynamodb.ExportFormat_Values(), false),
ForceNew: true,
Default: dynamodb.ExportFormatDynamodbJson,
},
"export_status": {
Type: schema.TypeString,
Computed: true,
},
"export_time": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: verify.ValidUTCTimestamp,
ForceNew: true,
},
"item_count": {
Type: schema.TypeInt,
Computed: true,
},
"manifest_files_s3_key": {
Type: schema.TypeString,
Computed: true,
},
"s3_bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"s3_bucket_owner": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: verify.ValidAccountID,
ForceNew: true,
},
"s3_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
ForceNew: true,
},
"s3_sse_algorithm": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice(dynamodb.S3SseAlgorithm_Values(), false),
ForceNew: true,
},
"s3_sse_kms_key_id": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 2048),
ForceNew: true,
},
"start_time": {
Type: schema.TypeString,
Computed: true,
},
"table_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

const (
ResNameTableExport = "Table Export"
)

func resourceTableExportCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).DynamoDBConn(ctx)

s3Bucket := d.Get("s3_bucket").(string)
tableArn := d.Get("table_arn").(string)

in := &dynamodb.ExportTableToPointInTimeInput{
S3Bucket: aws.String(s3Bucket),
TableArn: aws.String(tableArn),
}

if v, ok := d.GetOk("export_format"); ok {
in.ExportFormat = aws.String(v.(string))
}
if v, ok := d.GetOk("export_time"); ok {
v, _ := time.Parse(time.RFC3339, v.(string))
in.ExportTime = aws.Time(v)
}

if v, ok := d.GetOk("s3_bucket_owner"); ok {
in.S3BucketOwner = aws.String(v.(string))
}

if v, ok := d.GetOk("s3_sse_algorithm"); ok {
in.S3SseAlgorithm = aws.String(v.(string))
}

if v, ok := d.GetOk("s3_prefix"); ok {
in.S3Prefix = aws.String(v.(string))
}

if v, ok := d.GetOk("s3_sse_kms_key_id"); ok {
in.S3SseKmsKeyId = aws.String(v.(string))
}

log.Printf("Creating export table: %s", in)

out, err := conn.ExportTableToPointInTimeWithContext(ctx, in)
if err != nil {
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionCreating, ResNameTableExport, d.Get("table_arn").(string), err)
}

if out == nil || out.ExportDescription == nil {
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionCreating, ResNameTableExport, d.Get("table_arn").(string), errors.New("empty output"))
}

d.SetId(aws.StringValue(out.ExportDescription.ExportArn))

if _, err := waitTableExportCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionWaitingForCreation, ResNameTableExport, d.Id(), err)
}

return append(diags, resourceTableExportRead(ctx, d, meta)...)
}

func resourceTableExportRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).DynamoDBConn(ctx)

out, err := FindTableExportByID(ctx, conn, d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] DynamoDB TableExport (%s) not found, removing from state", d.Id())
d.SetId("")
return diags
}

if err != nil {
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionReading, ResNameTableExport, d.Id(), err)
}
desc := out.ExportDescription

d.Set("arn", desc.ExportArn)
d.Set("billed_size_in_bytes", desc.BilledSizeBytes)
d.Set("item_count", desc.ItemCount)
d.Set("manifest_files_s3_key", desc.ExportManifest)
d.Set("table_arn", desc.TableArn)
d.Set("s3_bucket", desc.S3Bucket)
d.Set("s3_bucket_owner", desc.S3BucketOwner)
d.Set("export_format", desc.ExportFormat)
d.Set("s3_prefix", desc.S3Prefix)
d.Set("s3_sse_algorithm", desc.S3SseAlgorithm)
d.Set("s3_sse_kms_key_id", desc.S3SseKmsKeyId)
d.Set("export_status", desc.ExportStatus)
if desc.EndTime != nil {
d.Set("end_time", aws.TimeValue(desc.EndTime).Format(time.RFC3339))
}
if desc.StartTime != nil {
d.Set("start_time", aws.TimeValue(desc.StartTime).Format(time.RFC3339))
}
if desc.ExportTime != nil {
d.Set("export_time", aws.TimeValue(desc.ExportTime).Format(time.RFC3339))
}

return diags
}
Loading
Loading