diff --git a/.changelog/39180.txt b/.changelog/39180.txt new file mode 100644 index 00000000000..a01b0938240 --- /dev/null +++ b/.changelog/39180.txt @@ -0,0 +1,6 @@ +```release-note:new-data-source +aws_synthetics_runtime_version +``` +```release-note:new-data-source +aws_synthetics_runtime_versions +``` \ No newline at end of file diff --git a/internal/framework/validators/bool_equals.go b/internal/framework/validators/bool_equals.go new file mode 100644 index 00000000000..fb601629be3 --- /dev/null +++ b/internal/framework/validators/bool_equals.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ validator.Bool = boolEqualsValidator{} + +type boolEqualsValidator struct { + value types.Bool +} + +func (v boolEqualsValidator) Description(ctx context.Context) string { + return fmt.Sprintf("Value must be %q", v.value) +} + +func (v boolEqualsValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v boolEqualsValidator) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + configValue := req.ConfigValue + + if !configValue.Equal(v.value) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + req.Path, + v.Description(ctx), + configValue.String(), + )) + } +} + +// BoolEquals checks that the Bool held in the attribute matches the +// given `value` +func BoolEquals(value bool) validator.Bool { + return boolEqualsValidator{ + value: types.BoolValue(value), + } +} diff --git a/internal/framework/validators/bool_equals_test.go b/internal/framework/validators/bool_equals_test.go new file mode 100644 index 00000000000..86360415fca --- /dev/null +++ b/internal/framework/validators/bool_equals_test.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" +) + +func TestBoolEqualsValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + in types.Bool + validator validator.Bool + expErrors int + } + + testCases := map[string]testCase{ + "simple-match": { + in: types.BoolValue(true), + validator: fwvalidators.BoolEquals(true), + expErrors: 0, + }, + "simple-mismatch": { + in: types.BoolValue(false), + validator: fwvalidators.BoolEquals(true), + expErrors: 1, + }, + "skip-validation-on-null": { + in: types.BoolNull(), + validator: fwvalidators.BoolEquals(true), + expErrors: 0, + }, + "skip-validation-on-unknown": { + in: types.BoolUnknown(), + validator: fwvalidators.BoolEquals(true), + expErrors: 0, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + req := validator.BoolRequest{ + ConfigValue: test.in, + } + res := validator.BoolResponse{} + test.validator.ValidateBool(context.TODO(), req, &res) + + if test.expErrors > 0 && !res.Diagnostics.HasError() { + t.Fatalf("expected %d error(s), got none", test.expErrors) + } + + if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() { + t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics) + } + + if test.expErrors == 0 && res.Diagnostics.HasError() { + t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics) + } + }) + } +} diff --git a/internal/service/synthetics/canary_test.go b/internal/service/synthetics/canary_test.go index c6410a6006d..d7d57e6ac32 100644 --- a/internal/service/synthetics/canary_test.go +++ b/internal/service/synthetics/canary_test.go @@ -25,6 +25,7 @@ func TestAccSyntheticsCanary_basic(t *testing.T) { var conf1, conf2 awstypes.Canary rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(8)) resourceName := "aws_synthetics_canary.test" + runtimeVersionDataSourceName := "data.aws_synthetics_runtime_version.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -38,7 +39,7 @@ func TestAccSyntheticsCanary_basic(t *testing.T) { testAccCheckCanaryExists(ctx, resourceName, &conf1), acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "synthetics", regexache.MustCompile(`canary:.+`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, "runtime_version", "syn-nodejs-puppeteer-9.0"), + resource.TestCheckResourceAttrPair(resourceName, "runtime_version", runtimeVersionDataSourceName, "version_name"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "run_config.0.memory_in_mb", "1000"), resource.TestCheckResourceAttr(resourceName, "run_config.0.timeout_in_seconds", "840"), @@ -70,7 +71,7 @@ func TestAccSyntheticsCanary_basic(t *testing.T) { testAccCheckCanaryExists(ctx, resourceName, &conf2), acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "synthetics", regexache.MustCompile(`canary:.+`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, "runtime_version", "syn-nodejs-puppeteer-9.0"), + resource.TestCheckResourceAttrPair(resourceName, "runtime_version", runtimeVersionDataSourceName, "version_name"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "run_config.0.memory_in_mb", "1000"), resource.TestCheckResourceAttr(resourceName, "run_config.0.timeout_in_seconds", "840"), @@ -313,6 +314,7 @@ func TestAccSyntheticsCanary_s3(t *testing.T) { var conf awstypes.Canary rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(8)) resourceName := "aws_synthetics_canary.test" + runtimeVersionDataSourceName := "data.aws_synthetics_runtime_version.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -326,7 +328,7 @@ func TestAccSyntheticsCanary_s3(t *testing.T) { testAccCheckCanaryExists(ctx, resourceName, &conf), acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "synthetics", regexache.MustCompile(`canary:.+`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, "runtime_version", "syn-nodejs-puppeteer-9.0"), + resource.TestCheckResourceAttrPair(resourceName, "runtime_version", runtimeVersionDataSourceName, "version_name"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "run_config.0.memory_in_mb", "1000"), resource.TestCheckResourceAttr(resourceName, "run_config.0.timeout_in_seconds", "840"), @@ -782,6 +784,11 @@ resource "aws_iam_role_policy" "test" { } EOF } + +data "aws_synthetics_runtime_version" "test" { + prefix = "syn-nodejs-puppeteer" + latest = true +} `, rName) } @@ -793,7 +800,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -817,7 +824,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -842,7 +849,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -867,7 +874,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -886,14 +893,15 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_runEnvVariables2(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -913,7 +921,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { # Must have bucket versioning enabled first depends_on = [aws_s3_bucket_versioning.test, aws_iam_role.test, aws_iam_role_policy.test] @@ -923,7 +933,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -934,7 +944,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_rate(rName string, rate string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { # Must have bucket versioning enabled first depends_on = [aws_s3_bucket_versioning.test, aws_iam_role.test, aws_iam_role_policy.test] @@ -944,7 +956,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true run_config { @@ -960,14 +972,16 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_artifactEncryption(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true artifact_config { @@ -986,7 +1000,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_artifactEncryptionKMS(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_kms_key" "test" { description = %[1]q deletion_window_in_days = 7 @@ -998,7 +1014,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true artifact_config { @@ -1018,7 +1034,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_runtimeVersion(rName, version string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" @@ -1038,14 +1056,16 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_zipUpdated(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/test/" execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest_modified.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1058,7 +1078,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_start(rName string, state bool) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" @@ -1066,7 +1088,7 @@ resource "aws_synthetics_canary" "test" { handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" start_canary = %[2]t - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1079,7 +1101,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_startZipUpdated(rName string, state bool) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" @@ -1087,7 +1111,7 @@ resource "aws_synthetics_canary" "test" { handler = "exports.handler" zip_file = "test-fixtures/lambdatest_modified.zip" start_canary = %[2]t - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1100,7 +1124,9 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_basicS3Code(rName string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" @@ -1109,7 +1135,7 @@ resource "aws_synthetics_canary" "test" { s3_bucket = aws_s3_object.test.bucket s3_key = aws_s3_object.test.key s3_version = aws_s3_object.test.version_id - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1128,12 +1154,11 @@ resource "aws_s3_object" "test" { source = "test-fixtures/lambdatest.zip" etag = filemd5("test-fixtures/lambdatest.zip") } - `, rName)) } func testAccCanarySecurityGroupBaseConfig(rName string, count int) string { - return acctest.ConfigCompose(fmt.Sprintf(` + return fmt.Sprintf(` resource "aws_security_group" "test" { count = %[2]d vpc_id = aws_vpc.test.id @@ -1147,7 +1172,7 @@ resource "aws_iam_role_policy_attachment" "test" { policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" role = aws_iam_role.test.name } -`, rName, count)) +`, rName, count) } func testAccCanaryConfig_vpc1(rName string) string { @@ -1162,7 +1187,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1191,7 +1216,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1220,7 +1245,7 @@ resource "aws_synthetics_canary" "test" { execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1238,14 +1263,16 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_tags1(rName, tagKey1, tagValue1 string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { @@ -1260,14 +1287,16 @@ resource "aws_synthetics_canary" "test" { } func testAccCanaryConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose(testAccCanaryConfig_base(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccCanaryConfig_base(rName), + fmt.Sprintf(` resource "aws_synthetics_canary" "test" { name = %[1]q artifact_s3_location = "s3://${aws_s3_bucket.test.bucket}/" execution_role_arn = aws_iam_role.test.arn handler = "exports.handler" zip_file = "test-fixtures/lambdatest.zip" - runtime_version = "syn-nodejs-puppeteer-9.0" + runtime_version = data.aws_synthetics_runtime_version.test.version_name delete_lambda = true schedule { diff --git a/internal/service/synthetics/find.go b/internal/service/synthetics/find.go index 3d8619e7a22..5f4fbb3d24c 100644 --- a/internal/service/synthetics/find.go +++ b/internal/service/synthetics/find.go @@ -102,3 +102,18 @@ func FindAssociatedGroup(ctx context.Context, conn *synthetics.Client, canaryArn return &group, nil } + +func findRuntimeVersions(ctx context.Context, conn *synthetics.Client) ([]awstypes.RuntimeVersion, error) { + input := &synthetics.DescribeRuntimeVersionsInput{} + output, err := conn.DescribeRuntimeVersions(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil || output.RuntimeVersions == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.RuntimeVersions, nil +} diff --git a/internal/service/synthetics/runtime_version_data_source.go b/internal/service/synthetics/runtime_version_data_source.go new file mode 100644 index 00000000000..0c3f14dbe77 --- /dev/null +++ b/internal/service/synthetics/runtime_version_data_source.go @@ -0,0 +1,162 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package synthetics + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + awstypes "github.com/aws/aws-sdk-go-v2/service/synthetics/types" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkDataSource(name="Runtime Version") +func newDataSourceRuntimeVersion(context.Context) (datasource.DataSourceWithConfigure, error) { + return &dataSourceRuntimeVersion{}, nil +} + +const ( + DSNameRuntimeVersion = "Runtime Version Data Source" +) + +type dataSourceRuntimeVersion struct { + framework.DataSourceWithConfigure +} + +func (d *dataSourceRuntimeVersion) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { // nosemgrep:ci.meta-in-func-name + resp.TypeName = "aws_synthetics_runtime_version" +} + +func (d *dataSourceRuntimeVersion) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "deprecation_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + }, + names.AttrDescription: schema.StringAttribute{ + Computed: true, + }, + names.AttrID: framework.IDAttribute(), + "release_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + }, + "latest": schema.BoolAttribute{ + Optional: true, + Validators: []validator.Bool{ + fwvalidators.BoolEquals(true), + boolvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("latest"), + path.MatchRoot(names.AttrVersion), + }...), + }, + }, + names.AttrPrefix: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`^.*[^-]$`), "must not end with a hyphen"), + }, + }, + names.AttrVersion: schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`^\d.*$`), "must start with a digit"), + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("latest"), + path.MatchRoot(names.AttrVersion), + }...), + }, + }, + "version_name": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *dataSourceRuntimeVersion) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + conn := d.Meta().SyntheticsClient(ctx) + + var data dataSourceRuntimeVersionModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + latest := data.Latest.ValueBool() + prefix := data.Prefix.ValueString() + version := data.Version.ValueString() + + out, err := findRuntimeVersions(ctx, conn) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Synthetics, create.ErrActionReading, DSNameRuntimeVersion, "", err), + err.Error(), + ) + return + } + + var runtimeVersion *awstypes.RuntimeVersion + var latestReleaseDate *time.Time + + for _, v := range out { + if strings.HasPrefix(aws.ToString(v.VersionName), prefix) { + if latest { + if latestReleaseDate == nil || aws.ToTime(v.ReleaseDate).After(*latestReleaseDate) { + latestReleaseDate = v.ReleaseDate + runtimeVersion = &v + } + } else { + if aws.ToString(v.VersionName) == fmt.Sprintf("%s-%s", prefix, version) { + runtimeVersion = &v + break + } + } + } + } + + if runtimeVersion == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Synthetics, create.ErrActionReading, DSNameRuntimeVersion, "", err), + "Query returned no results.", + ) + return + } + + data.ID = flex.StringToFramework(ctx, runtimeVersion.VersionName) + resp.Diagnostics.Append(flex.Flatten(ctx, runtimeVersion, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +type dataSourceRuntimeVersionModel struct { + DeprecationDate timetypes.RFC3339 `tfsdk:"deprecation_date"` + Description types.String `tfsdk:"description"` + ID types.String `tfsdk:"id"` + Latest types.Bool `tfsdk:"latest"` + Prefix types.String `tfsdk:"prefix"` + ReleaseDate timetypes.RFC3339 `tfsdk:"release_date"` + Version types.String `tfsdk:"version"` + VersionName types.String `tfsdk:"version_name"` +} diff --git a/internal/service/synthetics/runtime_version_data_source_test.go b/internal/service/synthetics/runtime_version_data_source_test.go new file mode 100644 index 00000000000..c51dbdcb20c --- /dev/null +++ b/internal/service/synthetics/runtime_version_data_source_test.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package synthetics_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSyntheticsRuntimeVersionDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + dataSourceName := "data.aws_synthetics_runtime_version.test" + prefix := "syn-nodejs-puppeteer" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SyntheticsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccRuntimeVersionDataSourceConfig_basic(prefix), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrHasPrefix(dataSourceName, "version_name", prefix), + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrID, dataSourceName, "version_name"), + resource.TestCheckNoResourceAttr(dataSourceName, "deprecation_date"), + resource.TestCheckResourceAttrSet(dataSourceName, names.AttrDescription), + resource.TestCheckResourceAttrSet(dataSourceName, "release_date"), + ), + }, + }, + }) +} + +func TestAccSyntheticsRuntimeVersionDataSource_deprecatedVersion(t *testing.T) { + ctx := acctest.Context(t) + dataSourceName := "data.aws_synthetics_runtime_version.test" + prefix := "syn-nodejs-puppeteer" + version := "3.0" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SyntheticsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccRuntimeVersionDataSourceConfig_version(prefix, version), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrHasPrefix(dataSourceName, "version_name", fmt.Sprintf("%s-%s", prefix, version)), + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrID, dataSourceName, "version_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "deprecation_date"), + resource.TestCheckResourceAttrSet(dataSourceName, names.AttrDescription), + resource.TestCheckResourceAttrSet(dataSourceName, "release_date"), + ), + }, + }, + }) +} + +func testAccRuntimeVersionDataSourceConfig_basic(prefix string) string { + return fmt.Sprintf(` +data "aws_synthetics_runtime_version" "test" { + prefix = %[1]q + latest = true +} +`, prefix) +} + +func testAccRuntimeVersionDataSourceConfig_version(prefix, version string) string { + return fmt.Sprintf(` +data "aws_synthetics_runtime_version" "test" { + prefix = %[1]q + version = %[2]q +} +`, prefix, version) +} diff --git a/internal/service/synthetics/runtime_versions_data_source.go b/internal/service/synthetics/runtime_versions_data_source.go new file mode 100644 index 00000000000..3de433a5c74 --- /dev/null +++ b/internal/service/synthetics/runtime_versions_data_source.go @@ -0,0 +1,101 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package synthetics + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkDataSource(name="Runtime Versions") +func newDataSourceRuntimeVersions(context.Context) (datasource.DataSourceWithConfigure, error) { + return &dataSourceRuntimeVersions{}, nil +} + +const ( + DSNameRuntimeVersions = "Runtime Versions Data Source" +) + +type dataSourceRuntimeVersions struct { + framework.DataSourceWithConfigure +} + +func (d *dataSourceRuntimeVersions) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { // nosemgrep:ci.meta-in-func-name + resp.TypeName = "aws_synthetics_runtime_versions" +} + +func (d *dataSourceRuntimeVersions) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttribute(), + }, + Blocks: map[string]schema.Block{ + "runtime_versions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[runtimeVersionModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "deprecation_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + }, + names.AttrDescription: schema.StringAttribute{ + Computed: true, + }, + "release_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + }, + "version_name": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *dataSourceRuntimeVersions) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + conn := d.Meta().SyntheticsClient(ctx) + + var data dataSourceRuntimeVersionsModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findRuntimeVersions(ctx, conn) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Synthetics, create.ErrActionReading, DSNameRuntimeVersions, "", err), + err.Error(), + ) + return + } + + data.ID = flex.StringValueToFramework(ctx, d.Meta().Region) + resp.Diagnostics.Append(flex.Flatten(ctx, out, &data.RuntimeVersions)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +type dataSourceRuntimeVersionsModel struct { + ID types.String `tfsdk:"id"` + RuntimeVersions fwtypes.ListNestedObjectValueOf[runtimeVersionModel] `tfsdk:"runtime_versions"` +} + +type runtimeVersionModel struct { + DeprecationDate timetypes.RFC3339 `tfsdk:"deprecation_date"` + Description types.String `tfsdk:"description"` + ReleaseDate timetypes.RFC3339 `tfsdk:"release_date"` + VersionName types.String `tfsdk:"version_name"` +} diff --git a/internal/service/synthetics/runtime_versions_data_source_test.go b/internal/service/synthetics/runtime_versions_data_source_test.go new file mode 100644 index 00000000000..489be4e84af --- /dev/null +++ b/internal/service/synthetics/runtime_versions_data_source_test.go @@ -0,0 +1,40 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package synthetics_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSyntheticsRuntimeVersionsDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + dataSourceName := "data.aws_synthetics_runtime_versions.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SyntheticsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccRuntimeVersionsDataSourceConfig_basic(), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "runtime_versions.#", 0), + resource.TestCheckTypeSetElemNestedAttrs(dataSourceName, "runtime_versions.*", map[string]string{ + "version_name": "syn-1.0", + }), + ), + }, + }, + }) +} + +func testAccRuntimeVersionsDataSourceConfig_basic() string { + return ` +data "aws_synthetics_runtime_versions" "test" {} +` +} diff --git a/internal/service/synthetics/service_package_gen.go b/internal/service/synthetics/service_package_gen.go index 4c7cc2c2e56..7dfe81a556c 100644 --- a/internal/service/synthetics/service_package_gen.go +++ b/internal/service/synthetics/service_package_gen.go @@ -15,7 +15,16 @@ import ( type servicePackage struct{} func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource { - return []*types.ServicePackageFrameworkDataSource{} + return []*types.ServicePackageFrameworkDataSource{ + { + Factory: newDataSourceRuntimeVersion, + Name: "Runtime Version", + }, + { + Factory: newDataSourceRuntimeVersions, + Name: "Runtime Versions", + }, + } } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { diff --git a/website/docs/d/synthetics_runtime_version.html.markdown b/website/docs/d/synthetics_runtime_version.html.markdown new file mode 100644 index 00000000000..1c1ff79b746 --- /dev/null +++ b/website/docs/d/synthetics_runtime_version.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "CloudWatch Synthetics" +layout: "aws" +page_title: "AWS: aws_synthetics_runtime_version" +description: |- + Terraform data source for managing an AWS CloudWatch Synthetics Runtime Version. +--- +# Data Source: aws_synthetics_runtime_version + +Terraform data source for managing an AWS CloudWatch Synthetics Runtime Version. + +## Example Usage + +### Latest Runtime Version + +```terraform +data "aws_synthetics_runtime_version" "example" { + prefix = "syn-nodejs-puppeteer" + latest = true +} +``` + +### Specific Runtime Version + +```terraform +data "aws_synthetics_runtime_version" "example" { + prefix = "syn-nodejs-puppeteer" + version = "9.0" +} +``` + +## Argument Reference + +The following arguments are required: + +* `prefix` - (Required) Name prefix of the runtime version (for example, `syn-nodejs-puppeteer`). + +The following arguments are optional: + +* `latest` - (Optional) Whether the latest version of the runtime should be fetched. Conflicts with `version`. Valid values: `true`. +* `version` - (Optional) Version of the runtime to be fetched (for example, `9.0`). Conflicts with `latest`. + +## Attribute Reference + +This data source exports the following attributes in addition to the arguments above: + +* `deprecation_date` - Date of deprecation if the runtme version is deprecated. +* `description` - Description of the runtime version, created by Amazon. +* `id` - Name of the runtime version. For a list of valid runtime versions, see [Canary Runtime Versions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html). +* `release_date` - Date that the runtime version was released. +* `version_name` - Name of the runtime version. For a list of valid runtime versions, see [Canary Runtime Versions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html). diff --git a/website/docs/d/synthetics_runtime_versions.html.markdown b/website/docs/d/synthetics_runtime_versions.html.markdown new file mode 100644 index 00000000000..921e592e5d3 --- /dev/null +++ b/website/docs/d/synthetics_runtime_versions.html.markdown @@ -0,0 +1,38 @@ +--- +subcategory: "CloudWatch Synthetics" +layout: "aws" +page_title: "AWS: aws_synthetics_runtime_versions" +description: |- + Terraform data source for managing an AWS CloudWatch Synthetics Runtime Versions. +--- + +# Data Source: aws_synthetics_runtime_versions + +Terraform data source for managing an AWS CloudWatch Synthetics Runtime Versions. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_synthetics_runtime_versions" "example" {} +``` + +## Argument Reference + +There are no arguments available for this data source. + +## Attribute Reference + +This data source exports the following attributes in addition to the arguments above: + +* `id` - Name of the AWS region from which runtime versions are fetched. +* `runtime_versions` - List of runtime versions. See [`runtime_versions` attribute reference](#runtime_versions-attribute-reference). + +### `runtime_versions` Attribute Reference + +* `deprecation_date` - Date of deprecation if the runtme version is deprecated. +* `description` - Description of the runtime version, created by Amazon. +* `release_date` - Date that the runtime version was released. +* `version_name` - Name of the runtime version. +For a list of valid runtime versions, see [Canary Runtime Versions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html).