Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/rds v1.91.0 // indirect
github.com/aws/aws-sdk-go-v2/service/redshift v1.65.4 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 // indirect
github.com/aws/aws-sdk-go-v2/service/s3tables v1.18.4 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 h1:wmt05tPp/CaRZpPV5B4SaJ5T
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2/go.mod h1:d+K9HESMpGb1EU9/UmmpInbGIUcAkwmcY6ZO/A3zZsw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2 h1:U3ygWUhCpiSPYSHOrRhb3gOl9T5Y3kB8k5Vjs//57bE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/s3tables v1.18.4 h1:Dw488RJo3tscyq5pzpT/BIGZcfZ7GoIE+S06AG4q8ik=
github.com/aws/aws-sdk-go-v2/service/s3tables v1.18.4/go.mod h1:7PsCRtQnxct6wWtIRr6glZNE8rDpqV+UraHwmZJMK1c=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 h1:1KDMKvOKNrpD667ORbZ/+4OgvUoaok1gg/MLzrHF9fw=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6/go.mod h1:DmtyfCfONhOyVAJ6ZMTrDSFIeyCBlEO93Qkfhxwbxu0=
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.42.0 h1:2UqEBrJPyfw9guK/JuInuQkWp+3Z6Hw2eySe//LQk50=
Expand Down
4 changes: 4 additions & 0 deletions integ/aws/storage/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ bucket-notifications: ## Test S3 Bucket with EventBridge Notifications
backup.plan: ## Test Backup Plan/Vault/Selection L2s (live plan over a DynamoDB table)
go test -v -count 1 -timeout 30m ./... -run ^TestBackupPlan$
.PHONY: backup.plan

s3tables.table: ## Test S3 Tables TableBucket/Namespace/Table L2s (live ICEBERG table)
go test -v -count 1 -timeout 30m ./... -run ^TestS3TablesTable$
.PHONY: s3tables.table
90 changes: 90 additions & 0 deletions integ/aws/storage/apps/s3tables.table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Live test for the storage.s3tables L2s (alpha port): a real S3 Tables
// TableBucket + Namespace + ICEBERG Table with a compaction-only maintenance
// configuration — the one-sided maintenance_configuration shape (absent
// snapshot_management member rendered null-filled) proven against live AWS,
// including the post-apply drift oracle.
import { App, LocalBackend, TerraformOutput } from "cdktn";
import { aws } from "../../../../src";

const environmentName = process.env.ENVIRONMENT_NAME ?? "test";
const region = process.env.AWS_REGION ?? "us-east-1";
const outdir = process.env.OUT_DIR ?? "cdktf.out";
const stackName = process.env.STACK_NAME ?? "s3tables.table";

const app = new App({
outdir,
});

const stack = new aws.AwsStack(app, stackName, {
gridUUID: "geeeeeeee-eeee",
environmentName,
providerConfig: {
region,
},
});
new LocalBackend(stack, {
path: `${stackName}.tfstate`,
});

const bucket = new aws.storage.s3tables.TableBucket(stack, "Bucket", {
tableBucketName: "tcons-s3tables-integ",
// Terraform-native destroy of a non-empty table bucket.
forceDestroy: true,
});

const namespace = new aws.storage.s3tables.Namespace(stack, "Namespace", {
namespaceName: "integ_ns",
tableBucket: bucket,
});

const table = new aws.storage.s3tables.Table(stack, "Table", {
tableName: "integ_table",
namespace,
openTableFormat: aws.storage.s3tables.OpenTableFormat.ICEBERG,
// Compaction WITHOUT snapshotManagement: the one-sided maintenance shape
// (absent side rendered with AWS's documented server-side defaults).
compaction: {
status: aws.storage.s3tables.Status.ENABLED,
targetFileSizeMb: 128,
},
});

// The mirror one-sided shape: snapshotManagement WITHOUT compaction, proving
// the AWS-defaults fill for the compaction side too.
const snapshotTable = new aws.storage.s3tables.Table(stack, "SnapshotTable", {
tableName: "integ_snapshot_table",
namespace,
openTableFormat: aws.storage.s3tables.OpenTableFormat.ICEBERG,
snapshotManagement: {
status: aws.storage.s3tables.Status.ENABLED,
maxSnapshotAgeHours: 48,
minSnapshotsToKeep: 3,
},
});

new TerraformOutput(stack, "table_bucket_arn", {
value: bucket.tableBucketArn,
staticId: true,
});
new TerraformOutput(stack, "table_bucket_name", {
value: bucket.tableBucketName,
staticId: true,
});
new TerraformOutput(stack, "namespace_name", {
value: namespace.namespaceName,
staticId: true,
});
new TerraformOutput(stack, "table_name", {
value: table.tableName,
staticId: true,
});
new TerraformOutput(stack, "table_arn", {
value: table.tableArn,
staticId: true,
});
new TerraformOutput(stack, "snapshot_table_name", {
value: snapshotTable.tableName,
staticId: true,
});

app.synth();
107 changes: 107 additions & 0 deletions integ/aws/storage/s3tables_table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package test

import (
"context"
"testing"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3tables"
s3tablestypes "github.com/aws/aws-sdk-go-v2/service/s3tables/types"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/require"
)

// Run the apps/s3tables.table.ts integration test: a real S3 Tables
// TableBucket + Namespace + ICEBERG Table through the storage.s3tables L2s
// (alpha port). Validates bucket/namespace/table read-back, the compaction-only
// maintenance_configuration shape (null-filled absent member), and the
// post-apply drift oracle.
func TestS3TablesTable(t *testing.T) {
runStorageIntegrationTest(t, "s3tables.table", "us-east-1", validateS3TablesTable)
}

func validateS3TablesTable(t *testing.T, tfWorkingDir string, awsRegion string) {
terraformOptions := test_structure.LoadTerraformOptions(t, tfWorkingDir)
outputs := terraform.OutputAll(t, terraformOptions)

bucketArn := outputs["table_bucket_arn"].(string)
bucketName := outputs["table_bucket_name"].(string)
namespaceName := outputs["namespace_name"].(string)
tableName := outputs["table_name"].(string)
tableArn := outputs["table_arn"].(string)

ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
require.NoError(t, err)
client := s3tables.NewFromConfig(cfg)

// --- 1. Table bucket read-back. ---
gb, err := client.GetTableBucket(ctx, &s3tables.GetTableBucketInput{
TableBucketARN: &bucketArn,
})
require.NoError(t, err)
require.Equal(t, bucketName, *gb.Name)
t.Logf("s3tables: table bucket %s exists (%s)", bucketName, bucketArn)

// --- 2. Namespace read-back. ---
gn, err := client.GetNamespace(ctx, &s3tables.GetNamespaceInput{
TableBucketARN: &bucketArn,
Namespace: &namespaceName,
})
require.NoError(t, err)
require.Contains(t, gn.Namespace, namespaceName)
t.Logf("s3tables: namespace %s exists", namespaceName)

// --- 3. Table read-back: ICEBERG format + ARN. ---
gt, err := client.GetTable(ctx, &s3tables.GetTableInput{
TableBucketARN: &bucketArn,
Namespace: &namespaceName,
Name: &tableName,
})
require.NoError(t, err)
require.Equal(t, tableArn, *gt.TableARN)
require.Equal(t, s3tablestypes.OpenTableFormatIceberg, gt.Format)
t.Logf("s3tables: table %s exists (ICEBERG, %s)", tableName, tableArn)

// --- 4. Maintenance read-back: compaction enabled at 128MB (the one-sided
// maintenance_configuration shape with a null-filled absent member). ---
gm, err := client.GetTableMaintenanceConfiguration(ctx, &s3tables.GetTableMaintenanceConfigurationInput{
TableBucketARN: &bucketArn,
Namespace: &namespaceName,
Name: &tableName,
})
require.NoError(t, err)
compaction, ok := gm.Configuration[string(s3tablestypes.TableMaintenanceTypeIcebergCompaction)]
require.True(t, ok, "compaction maintenance configuration must exist")
require.Equal(t, s3tablestypes.MaintenanceStatusEnabled, compaction.Status)
settings, ok := compaction.Settings.(*s3tablestypes.TableMaintenanceSettingsMemberIcebergCompaction)
require.True(t, ok, "compaction settings must be the iceberg compaction member")
require.Equal(t, int32(128), *settings.Value.TargetFileSizeMB)
t.Logf("s3tables: compaction enabled at %dMB target file size", *settings.Value.TargetFileSizeMB)

// --- 4b. The mirror one-sided shape: snapshot-only table, compaction side
// filled with AWS defaults (enabled/512MB). ---
snapshotTableName := outputs["snapshot_table_name"].(string)
gm2, err := client.GetTableMaintenanceConfiguration(ctx, &s3tables.GetTableMaintenanceConfigurationInput{
TableBucketARN: &bucketArn,
Namespace: &namespaceName,
Name: &snapshotTableName,
})
require.NoError(t, err)
snap, ok := gm2.Configuration[string(s3tablestypes.TableMaintenanceTypeIcebergSnapshotManagement)]
require.True(t, ok, "snapshot management maintenance configuration must exist")
require.Equal(t, s3tablestypes.MaintenanceStatusEnabled, snap.Status)
snapSettings, ok := snap.Settings.(*s3tablestypes.TableMaintenanceSettingsMemberIcebergSnapshotManagement)
require.True(t, ok, "snapshot settings must be the iceberg snapshot management member")
require.Equal(t, int32(48), *snapSettings.Value.MaxSnapshotAgeHours)
require.Equal(t, int32(3), *snapSettings.Value.MinSnapshotsToKeep)
t.Logf("s3tables: snapshot-only table %s reads back 48h/3 snapshots", snapshotTableName)

// --- Drift oracle: re-planning the already-applied stack must show zero
// changes. Proves the null-filled absent maintenance member reads back
// without perpetual diff. ---
planExitCode := terraform.PlanExitCode(t, terraformOptions)
require.Equal(t, terraform.DefaultSuccessExitCode, planExitCode,
"expected `tofu plan -detailed-exitcode` to report no drift after apply (got exit code %d)", planExitCode)
}
3 changes: 3 additions & 0 deletions src/aws/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ export * as redshift from "./redshift";

// aws-backup
export * as backup from "./backup";

// aws-s3tables-alpha
export * as s3tables from "./s3tables";
25 changes: 25 additions & 0 deletions src/aws/storage/s3tables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/@aws-cdk/aws-s3tables-alpha/lib/index.ts
//
// TODO(alpha-tracker): ported from @aws-cdk/aws-s3tables-alpha@2.263.0-alpha.0 (stability:
// experimental). Re-diff against upstream on every reference-tag bump — alpha surfaces churn
// without deprecation cycles.
//
// The index.ts files contains a list of files we want to include as part of the public API of
// this module. In general, all files including L2 classes will be listed here, while all files
// including only utility functions (`./permissions.ts`, `./util.ts`) are omitted, matching
// upstream's own convention verbatim.

export * from "./table-bucket";
export * from "./table-bucket-policy";
export * from "./namespace";
export * from "./table";
export * from "./table-policy";

// TODO: omitted — upstream also re-exports the generated CFN L1 (`./s3tables.generated`, i.e.
// `CfnTableBucket`/`CfnTableBucketPolicy`/`CfnNamespace`/`CfnTable`/`CfnTablePolicy`). This repo
// has no CloudFormation-generated L1 layer to re-export (Terraform L1s come from
// `@cdktn/provider-aws` instead, already consumed directly by
// `./table-bucket.ts`/`./table-bucket-policy.ts`/`./namespace.ts`/`./table.ts`/`./table-policy.ts`)
// — identical omission to every other ported module in this repo (e.g. `../docdb/index.ts`,
// `../neptune/index.ts`, `../redshift/index.ts`) —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/@aws-cdk/aws-s3tables-alpha/lib/index.ts
Loading
Loading