From da6bd9ea14173e8ebdf3f600298549712e5877e1 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Fri, 25 Aug 2023 10:03:44 -0400 Subject: [PATCH 01/20] adding CEV support --- .../service/rds/custom_db_engine_version.go | 583 ++++++++++++++++++ .../rds/custom_db_engine_version_test.go | 327 ++++++++++ ...rds_custom_db_engine_version.html.markdown | 71 +++ 3 files changed, 981 insertions(+) create mode 100644 internal/service/rds/custom_db_engine_version.go create mode 100644 internal/service/rds/custom_db_engine_version_test.go create mode 100644 website/docs/r/rds_custom_db_engine_version.html.markdown diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go new file mode 100644 index 00000000000..7acd2d4c0a9 --- /dev/null +++ b/internal/service/rds/custom_db_engine_version.go @@ -0,0 +1,583 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/rds" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "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/errs" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" + "github.com/mitchellh/go-homedir" +) + +const cevMutexKey = `aws_rds_custom_engine_version` + +// @SDKResource("aws_rds_custom_db_engine_version", name="Custom DB Engine Version") +// @Tags(identifierAttribute="arn") +func ResourceCustomDBEngineVersion() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomDBEngineVersionCreate, + ReadWithoutTimeout: resourceCustomDBEngineVersionRead, + UpdateWithoutTimeout: resourceCustomDBEngineVersionUpdate, + DeleteWithoutTimeout: resourceCustomDBEngineVersionDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(60 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + }, + "database_installation_files_s3_bucket_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(3, 63), + }, + "database_installation_files_s3_prefix": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "db_parameter_group_family": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "engine": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringMatch(regexp.MustCompile(fmt.Sprintf(`^%s.*$`, InstanceEngineCustomPrefix)), fmt.Sprintf("must begin with %s", InstanceEngineCustomPrefix)), + validation.StringLenBetween(1, 35), + ), + }, + "engine_version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 60), + }, + "filename": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"manifest"}, + }, + "image_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "major_engine_version": { + Type: schema.TypeString, + Computed: true, + }, + "manifest": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.All( + validation.StringIsJSON, + validation.StringLenBetween(1, 100000), + ), + ConflictsWith: []string{"filename"}, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, + "manifest_hash": { + Type: schema.TypeString, + Optional: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrTags: tftags.TagsSchema(), // TIP: Many, but not all, resources have `tags` and `tags_all` attributes. + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameCustomDBEngineVersion = "Custom DB Engine Version" +) + +func resourceCustomDBEngineVersionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSConn(ctx) + + input := rds.CreateCustomDBEngineVersionInput{ + Engine: aws.String(d.Get("engine").(string)), + EngineVersion: aws.String(d.Get("engine_version").(string)), + Tags: getTagsIn(ctx), + } + + if v, ok := d.GetOk("database_installation_files_s3_bucket_name"); ok { + input.DatabaseInstallationFilesS3BucketName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("database_installation_files_s3_prefix"); ok { + input.DatabaseInstallationFilesS3Prefix = aws.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_id"); ok { + input.ImageId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("kms_key_id"); ok { + input.KMSKeyId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("filename"); ok { + filename := v.(string) + // Grab an exclusive lock so that we're only reading one contact flow into + // memory at a time. + // See https://github.com/hashicorp/terraform/issues/9364 + conns.GlobalMutexKV.Lock(cevMutexKey) + defer conns.GlobalMutexKV.Unlock(cevMutexKey) + file, err := resourceCustomDBEngineVersionLoadFileContent(filename) + if err != nil { + return diag.Errorf("unable to load %q: %s", filename, err) + } + input.Manifest = aws.String(file) + } else if v, ok := d.GetOk("manifest"); ok { + input.Manifest = aws.String(v.(string)) + } + + output, err := conn.CreateCustomDBEngineVersionWithContext(ctx, &input) + if err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionCreating, ResNameCustomDBEngineVersion, fmt.Sprintf("%s:%s", aws.StringValue(output.Engine), aws.StringValue(output.EngineVersion)), err)...) + } + + if output == nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionCreating, ResNameCustomDBEngineVersion, fmt.Sprintf("%s:%s", aws.StringValue(output.Engine), aws.StringValue(output.EngineVersion)), errors.New("empty output"))...) + } + + d.SetId(fmt.Sprintf("%s:%s", aws.StringValue(output.Engine), aws.StringValue(output.EngineVersion))) + + if _, err := waitCustomDBEngineVersionCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionWaitingForCreation, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + return append(diags, resourceCustomDBEngineVersionRead(ctx, d, meta)...) +} + +func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSConn(ctx) + + out, err := findCustomDBEngineVersionByID(ctx, conn, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] RDS CustomDBEngineVersion (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionReading, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + d.Set("arn", out.DBEngineVersionArn) + d.Set("create_time", out.CreateTime) + d.Set("database_installation_files_s3_bucket_name", out.DatabaseInstallationFilesS3BucketName) + d.Set("database_installation_files_s3_prefix", out.DatabaseInstallationFilesS3Prefix) + d.Set("db_parameter_group_family", out.DBParameterGroupFamily) + d.Set("description", out.DBEngineVersionDescription) + d.Set("engine", out.Engine) + d.Set("engine_version", out.EngineVersion) + d.Set("image_id", out.Image.ImageId) + d.Set("kms_key_id", out.KMSKeyId) + d.Set("major_engine_version", out.MajorEngineVersion) + d.Set("manifest", out.CustomDBEngineVersionManifest) + d.Set("status", out.Status) + + setTagsOut(ctx, out.TagList) + + return diags +} + +func resourceCustomDBEngineVersionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + update := false + + input := &rds.UpdateCustomDBEngineVersionInput{ + Id: aws.String(d.Id()), + } + + if d.HasChanges("an_argument") { + input.AnArgument = aws.String(d.Get("an_argument").(string)) + update = true + } + + if !update { + // TIP: If update doesn't do anything at all, which is rare, you can + // return diags. Otherwise, return a read call, as below. + return diags + } + + // TIP: -- 3. Call the AWS modify/update function + log.Printf("[DEBUG] Updating RDS CustomDBEngineVersion (%s): %#v", d.Id(), in) + out, err := conn.UpdateCustomDBEngineVersion(ctx, put) + if err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + // TIP: -- 4. Use a waiter to wait for update to complete + if _, err := waitCustomDBEngineVersionUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionWaitingForUpdate, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + // TIP: -- 5. Call the Read function in the Update return + return append(diags, resourceCustomDBEngineVersionRead(ctx, d, meta)...) +} + +func resourceCustomDBEngineVersionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + // TIP: ==== RESOURCE DELETE ==== + // Most resources have Delete functions. There are rare situations + // where you might not need a delete: + // a. The AWS API does not provide a way to delete the resource + // b. The point of your resource is to perform an action (e.g., reboot a + // server) and deleting serves no purpose. + // + // The Delete function should do the following things. Make sure there + // is a good reason if you don't do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Populate a delete input structure + // 3. Call the AWS delete function + // 4. Use a waiter to wait for delete to complete + // 5. Return diags + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + // TIP: -- 2. Populate a delete input structure + log.Printf("[INFO] Deleting RDS CustomDBEngineVersion %s", d.Id()) + + // TIP: -- 3. Call the AWS delete function + _, err := conn.DeleteCustomDBEngineVersion(ctx, &rds.DeleteCustomDBEngineVersionInput{ + Id: aws.String(d.Id()), + }) + + // TIP: On rare occassions, the API returns a not found error after deleting a + // resource. If that happens, we don't want it to show up as an error. + if errs.IsA[*types.ResourceNotFoundException](err) { + return diags + } + if err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionDeleting, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + // TIP: -- 4. Use a waiter to wait for delete to complete + if _, err := waitCustomDBEngineVersionDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionWaitingForDeletion, ResNameCustomDBEngineVersion, d.Id(), err)...) + } + + // TIP: -- 5. Return diags + return diags +} + +const ( + statusAvailable = "available" + statusCreating = "creating" + statusDeleting = "deleting" + statusDeprecated = "deprecated" + statusFailed = "failed" +) + +func waitCustomDBEngineVersionCreated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusCreating}, + Target: []string{statusAvailable}, + Refresh: statusCustomDBEngineVersion(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*rds.DBEngineVersion); ok { + return out, err + } + + return nil, err +} + +// TIP: It is easier to determine whether a resource is updated for some +// resources than others. The best case is a status flag that tells you when +// the update has been fully realized. Other times, you can check to see if a +// key resource argument is updated to a new value or not. + +func waitCustomDBEngineVersionUpdated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusChangePending}, + Target: []string{statusUpdated}, + Refresh: statusCustomDBEngineVersion(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*rds.DBEngineVersion); ok { + return out, err + } + + return nil, err +} + +// TIP: A deleted waiter is almost like a backwards created waiter. There may +// be additional pending states, however. + +func waitCustomDBEngineVersionDeleted(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusDeleting, statusNormal}, + Target: []string{}, + Refresh: statusCustomDBEngineVersion(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*rds.DBEngineVersion); ok { + return out, err + } + + return nil, err +} + +func statusCustomDBEngineVersion(ctx context.Context, conn *rds.RDS, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findCustomDBEngineVersionByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, aws.StringValue(out.Status), nil + } +} + +func findCustomDBEngineVersionByID(ctx context.Context, conn *rds.RDS, id string) (*rds.DBEngineVersion, error) { + engine, engineVersion, err := customEngineVersionParseID(id) + input := &rds.DescribeDBEngineVersionsInput{ + Engine: aws.String(engine), + EngineVersion: aws.String(engineVersion), + IncludeAll: aws.Bool(true), // Required to return CEVs that are in `creating` state + } + + output, err := conn.DescribeDBEngineVersionsWithContext(ctx, input) + if tfawserr.ErrCodeEquals(err, rds.ErrCodeCustomDBEngineVersionNotFoundFault) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { + return nil, err + } + if output == nil || len(output.DBEngineVersions) == 0 { + return nil, &retry.NotFoundError{ + LastRequest: input, + } + } + + return output.DBEngineVersions[0], nil +} + +// TIP: ==== FLEX ==== +// Flatteners and expanders ("flex" functions) help handle complex data +// types. Flatteners take an API data type and return something you can use in +// a d.Set() call. In other words, flatteners translate from AWS -> Terraform. +// +// On the other hand, expanders take a Terraform data structure and return +// something that you can send to the AWS API. In other words, expanders +// translate from Terraform -> AWS. +// +// See more: +// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ +func flattenComplexArgument(apiObject *rds.ComplexArgument) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SubFieldOne; v != nil { + m["sub_field_one"] = aws.ToString(v) + } + + if v := apiObject.SubFieldTwo; v != nil { + m["sub_field_two"] = aws.ToString(v) + } + + return m +} + +// TIP: Often the AWS API will return a slice of structures in response to a +// request for information. Sometimes you will have set criteria (e.g., the ID) +// that means you'll get back a one-length slice. This plural function works +// brilliantly for that situation too. +func flattenComplexArguments(apiObjects []*rds.ComplexArgument) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + l = append(l, flattenComplexArgument(apiObject)) + } + + return l +} + +// TIP: Remember, as mentioned above, expanders take a Terraform data structure +// and return something that you can send to the AWS API. In other words, +// expanders translate from Terraform -> AWS. +// +// See more: +// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ +func expandComplexArgument(tfMap map[string]interface{}) *rds.ComplexArgument { + if tfMap == nil { + return nil + } + + a := &rds.ComplexArgument{} + + if v, ok := tfMap["sub_field_one"].(string); ok && v != "" { + a.SubFieldOne = aws.String(v) + } + + if v, ok := tfMap["sub_field_two"].(string); ok && v != "" { + a.SubFieldTwo = aws.String(v) + } + + return a +} + +// TIP: Even when you have a list with max length of 1, this plural function +// works brilliantly. However, if the AWS API takes a structure rather than a +// slice of structures, you will not need it. +func expandComplexArguments(tfList []interface{}) []*rds.ComplexArgument { + // TIP: The AWS API can be picky about whether you send a nil or zero- + // length for an argument that should be cleared. For example, in some + // cases, if you send a nil value, the AWS API interprets that as "make no + // changes" when what you want to say is "remove everything." Sometimes + // using a zero-length list will cause an error. + // + // As a result, here are two options. Usually, option 1, nil, will work as + // expected, clearing the field. But, test going from something to nothing + // to make sure it works. If not, try the second option. + + // TIP: Option 1: Returning nil for zero-length list + if len(tfList) == 0 { + return nil + } + + var s []*rds.ComplexArgument + + // TIP: Option 2: Return zero-length list for zero-length list. If option 1 does + // not work, after testing going from something to nothing (if that is + // possible), uncomment out the next line and remove option 1. + // + // s := make([]*rds.ComplexArgument, 0) + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandComplexArgument(m) + + if a == nil { + continue + } + + s = append(s, a) + } + + return s +} + +func customEngineVersionParseID(id string) (string, string, error) { + parts := strings.SplitN(id, ":", 2) + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected engine:engineversion", id) + } + + return parts[0], parts[1], nil +} + +func resourceCustomDBEngineVersionLoadFileContent(filename string) (string, error) { + filename, err := homedir.Expand(filename) + if err != nil { + return "", err + } + fileContent, err := os.ReadFile(filename) + if err != nil { + return "", err + } + return string(fileContent), nil +} diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go new file mode 100644 index 00000000000..aa5102f9003 --- /dev/null +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -0,0 +1,327 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds_test + +// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** +// +// TIP: ==== INTRODUCTION ==== +// Thank you for trying the skaff tool! +// +// You have opted to include these helpful comments. They all include "TIP:" +// to help you find and remove them when you're done with them. +// +// While some aspects of this file are customized to your input, the +// scaffold tool does *not* look at the AWS API and ensure it has correct +// function, structure, and variable names. It makes guesses based on +// commonalities. You will need to make significant adjustments. +// +// In other words, as generated, this is a rough outline of the work you will +// need to do. If something doesn't make sense for your situation, get rid of +// it. + +import ( + // TIP: ==== IMPORTS ==== + // This is a common set of imports but not customized to your code since + // your code hasn't been written yet. Make sure you, your IDE, or + // goimports -w fixes these imports. + // + // The provider linter wants your imports to be in two groups: first, + // standard library (i.e., "fmt" or "strings"), second, everything else. + // + // Also, AWS Go SDK v2 may handle nested structures differently than v1, + // using the services/rds/types package. If so, you'll + // need to import types and reference the nested types, e.g., as + // types.. + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/names" + + // TIP: You will often need to import the package that this test file lives + // in. Since it is in the "test" context, it must import the package to use + // any normal context constants, variables, or functions. + tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" +) + +// TIP: File Structure. The basic outline for all test files should be as +// follows. Improve this resource's maintainability by following this +// outline. +// +// 1. Package declaration (add "_test" since this is a test file) +// 2. Imports +// 3. Unit tests +// 4. Basic test +// 5. Disappears test +// 6. All the other tests +// 7. Helper functions (exists, destroy, check, etc.) +// 8. Functions that return Terraform configurations + +// TIP: ==== UNIT TESTS ==== +// This is an example of a unit test. Its name is not prefixed with +// "TestAcc" like an acceptance test. +// +// Unlike acceptance tests, unit tests do not access AWS and are focused on a +// function (or method). Because of this, they are quick and cheap to run. +// +// In designing a resource's implementation, isolate complex bits from AWS bits +// so that they can be tested through a unit test. We encourage more unit tests +// in the provider. +// +// Cut and dry functions using well-used patterns, like typical flatteners and +// expanders, don't need unit testing. However, if they are complex or +// intricate, they should be unit tested. +func TestCustomDBEngineVersionExampleUnitTest(t *testing.T) { + t.Parallel() + + testCases := []struct { + TestName string + Input string + Expected string + Error bool + }{ + { + TestName: "empty", + Input: "", + Expected: "", + Error: true, + }, + { + TestName: "descriptive name", + Input: "some input", + Expected: "some output", + Error: false, + }, + { + TestName: "another descriptive name", + Input: "more input", + Expected: "more output", + Error: false, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.TestName, func(t *testing.T) { + t.Parallel() + got, err := tfrds.FunctionFromResource(testCase.Input) + + if err != nil && !testCase.Error { + t.Errorf("got error (%s), expected no error", err) + } + + if err == nil && testCase.Error { + t.Errorf("got (%s) and no error, expected error", got) + } + + if got != testCase.Expected { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +// TIP: ==== ACCEPTANCE TESTS ==== +// This is an example of a basic acceptance test. This should test as much of +// standard functionality of the resource as possible, and test importing, if +// applicable. We prefix its name with "TestAcc", the service, and the +// resource name. +// +// Acceptance test access AWS and cost money to run. +func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { + ctx := acctest.Context(t) + // TIP: This is a long-running test guard for tests that run longer than + // 300s (5 min) generally. + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customdbengineversion rds.DescribeCustomDBEngineVersionResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_custom_db_engine_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.RDSEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"), + resource.TestCheckResourceAttrSet(resourceName, "maintenance_window_start_time.0.day_of_week"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "user.*", map[string]string{ + "console_access": "false", + "groups.#": "0", + "username": "Test", + "password": "TestTest1234", + }), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customdbengineversion rds.DescribeCustomDBEngineVersionResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_custom_db_engine_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.RDSEndpointID) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_basic(rName, testAccCustomDBEngineVersionVersionNewer), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceCustomDBEngineVersion(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCustomDBEngineVersionDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_rds_custom_db_engine_version" { + continue + } + + input := &rds.DescribeCustomDBEngineVersionInput{ + CustomDBEngineVersionId: aws.String(rs.Primary.ID), + } + _, err := conn.DescribeCustomDBEngineVersion(ctx, &rds.DescribeCustomDBEngineVersionInput{ + CustomDBEngineVersionId: aws.String(rs.Primary.ID), + }) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return nil + } + + return create.Error(names.RDS, create.ErrActionCheckingDestroyed, tfrds.ResNameCustomDBEngineVersion, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckCustomDBEngineVersionExists(ctx context.Context, name string, customdbengineversion *rds.DescribeCustomDBEngineVersionResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.RDS, create.ErrActionCheckingExistence, tfrds.ResNameCustomDBEngineVersion, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.RDS, create.ErrActionCheckingExistence, tfrds.ResNameCustomDBEngineVersion, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + resp, err := conn.DescribeCustomDBEngineVersion(ctx, &rds.DescribeCustomDBEngineVersionInput{ + CustomDBEngineVersionId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.RDS, create.ErrActionCheckingExistence, tfrds.ResNameCustomDBEngineVersion, rs.Primary.ID, err) + } + + *customdbengineversion = *resp + + return nil + } +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + input := &rds.ListCustomDBEngineVersionsInput{} + _, err := conn.ListCustomDBEngineVersions(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccCheckCustomDBEngineVersionNotRecreated(before, after *rds.DescribeCustomDBEngineVersionResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.ToString(before.CustomDBEngineVersionId), aws.ToString(after.CustomDBEngineVersionId); before != after { + return create.Error(names.RDS, create.ErrActionCheckingNotRecreated, tfrds.ResNameCustomDBEngineVersion, aws.ToString(before.CustomDBEngineVersionId), errors.New("recreated")) + } + + return nil + } +} + +func testAccCustomDBEngineVersionConfig_basic(rName, version string) string { + return fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q +} + +resource "aws_rds_custom_db_engine_version" "test" { + custom_db_engine_version_name = %[1]q + engine_type = "ActiveRDS" + engine_version = %[2]q + host_instance_type = "rds.t2.micro" + security_groups = [aws_security_group.test.id] + authentication_strategy = "simple" + storage_type = "efs" + + logs { + general = true + } + + user { + username = "Test" + password = "TestTest1234" + } +} +`, rName, version) +} diff --git a/website/docs/r/rds_custom_db_engine_version.html.markdown b/website/docs/r/rds_custom_db_engine_version.html.markdown new file mode 100644 index 00000000000..ce86478689c --- /dev/null +++ b/website/docs/r/rds_custom_db_engine_version.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "RDS (Relational Database)" +layout: "aws" +page_title: "AWS: aws_rds_custom_db_engine_version" +description: |- + Terraform resource for managing an AWS RDS (Relational Database) Custom DB Engine Version. +--- +` +# Resource: aws_rds_custom_db_engine_version + +Terraform resource for managing an AWS RDS (Relational Database) Custom DB Engine Version. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_rds_custom_db_engine_version" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +The following arguments are optional: + +* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `tags` - (Optional) A map of tags assigned to the WorkSpaces Connection Alias. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Custom DB Engine Version. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import RDS (Relational Database) Custom DB Engine Version using the `example_id_arg`. For example: + +```terraform +import { + to = aws_rds_custom_db_engine_version.example + id = "custom_db_engine_version-id-12345678" +} +``` + +Using `terraform import`, import RDS (Relational Database) Custom DB Engine Version using the `example_id_arg`. For example: + +```console +% terraform import aws_rds_custom_db_engine_version.example custom_db_engine_version-id-12345678 +``` From 78aa3fbdc7d208a8a2e7445cecfc784c828970b2 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Mon, 28 Aug 2023 11:26:27 -0400 Subject: [PATCH 02/20] Added Create and Read --- .../service/rds/custom_db_engine_version.go | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index 7acd2d4c0a9..4fe3c555b1e 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -100,6 +100,7 @@ func ResourceCustomDBEngineVersion() *schema.Resource { "image_id": { Type: schema.TypeString, Optional: true, + ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 255), }, "kms_key_id": { @@ -117,6 +118,7 @@ func ResourceCustomDBEngineVersion() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateFunc: validation.All( validation.StringIsJSON, validation.StringLenBetween(1, 100000), @@ -133,10 +135,12 @@ func ResourceCustomDBEngineVersion() *schema.Resource { Optional: true, }, "status": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(rds.CustomEngineVersionStatus_Values(), false), }, - names.AttrTags: tftags.TagsSchema(), // TIP: Many, but not all, resources have `tags` and `tags_all` attributes. + names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -247,75 +251,64 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa func resourceCustomDBEngineVersionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSClient(ctx) + conn := meta.(*conns.AWSClient).RDSConn(ctx) update := false - - input := &rds.UpdateCustomDBEngineVersionInput{ - Id: aws.String(d.Id()), + engine, engineVersion, e := customEngineVersionParseID(d.Id()) + if e != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), e)...) + return sdkdiag.AppendErrorf(diags, "deleting RDS DB Proxy (%s): %s", d.Id(), err) + } + input := &rds.ModifyCustomDBEngineVersionInput{ + Engine: aws.String(engine), + EngineVersion: aws.String(engineVersion), } - if d.HasChanges("an_argument") { - input.AnArgument = aws.String(d.Get("an_argument").(string)) + if d.HasChanges("description") { + input.Description = aws.String(d.Get("description").(string)) + update = true + } + if d.HasChanges("status") { + input.Status = aws.String(d.Get("status").(string)) update = true } if !update { - // TIP: If update doesn't do anything at all, which is rare, you can - // return diags. Otherwise, return a read call, as below. return diags } - // TIP: -- 3. Call the AWS modify/update function - log.Printf("[DEBUG] Updating RDS CustomDBEngineVersion (%s): %#v", d.Id(), in) - out, err := conn.UpdateCustomDBEngineVersion(ctx, put) + log.Printf("[DEBUG] Updating RDS CustomDBEngineVersion (%s): %#v", d.Id(), input) + output, err := conn.ModifyCustomDBEngineVersionWithContext(ctx, input) if err != nil { return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), err)...) } + if output == nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), errors.New("empty output"))...) + } - // TIP: -- 4. Use a waiter to wait for update to complete - if _, err := waitCustomDBEngineVersionUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { + if _, err := waitCustomDBEngineVersionUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return append(diags, create.DiagError(names.RDS, create.ErrActionWaitingForUpdate, ResNameCustomDBEngineVersion, d.Id(), err)...) } - // TIP: -- 5. Call the Read function in the Update return return append(diags, resourceCustomDBEngineVersionRead(ctx, d, meta)...) } func resourceCustomDBEngineVersionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - // TIP: ==== RESOURCE DELETE ==== - // Most resources have Delete functions. There are rare situations - // where you might not need a delete: - // a. The AWS API does not provide a way to delete the resource - // b. The point of your resource is to perform an action (e.g., reboot a - // server) and deleting serves no purpose. - // - // The Delete function should do the following things. Make sure there - // is a good reason if you don't do one of these. - // - // 1. Get a client connection to the relevant service - // 2. Populate a delete input structure - // 3. Call the AWS delete function - // 4. Use a waiter to wait for delete to complete - // 5. Return diags - - // TIP: -- 1. Get a client connection to the relevant service - conn := meta.(*conns.AWSClient).RDSClient(ctx) + conn := meta.(*conns.AWSClient).RDSConn(ctx) - // TIP: -- 2. Populate a delete input structure log.Printf("[INFO] Deleting RDS CustomDBEngineVersion %s", d.Id()) - // TIP: -- 3. Call the AWS delete function - _, err := conn.DeleteCustomDBEngineVersion(ctx, &rds.DeleteCustomDBEngineVersionInput{ - Id: aws.String(d.Id()), + engine, engineVersion, e := customEngineVersionParseID(d.Id()) + output, err := conn.DeleteCustomDBEngineVersionWithContext(ctx, &rds.DeleteCustomDBEngineVersionInput{ + Engine: aws.String(engine), + EngineVersion: aws.String(engineVersion), }) - // TIP: On rare occassions, the API returns a not found error after deleting a - // resource. If that happens, we don't want it to show up as an error. - if errs.IsA[*types.ResourceNotFoundException](err) { + if tfawserr.ErrCodeEquals(err, rds.ErrCodeCustomDBEngineVersionNotFoundFault) { return diags } + if err != nil { return append(diags, create.DiagError(names.RDS, create.ErrActionDeleting, ResNameCustomDBEngineVersion, d.Id(), err)...) } From 3e349df8bdf6990db9b9552dede65c219cc4abeb Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Mon, 28 Aug 2023 13:11:29 -0400 Subject: [PATCH 03/20] updated waiters --- .../service/rds/custom_db_engine_version.go | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index 4fe3c555b1e..bd3725f6928 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -257,7 +257,6 @@ func resourceCustomDBEngineVersionUpdate(ctx context.Context, d *schema.Resource engine, engineVersion, e := customEngineVersionParseID(d.Id()) if e != nil { return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), e)...) - return sdkdiag.AppendErrorf(diags, "deleting RDS DB Proxy (%s): %s", d.Id(), err) } input := &rds.ModifyCustomDBEngineVersionInput{ Engine: aws.String(engine), @@ -348,15 +347,10 @@ func waitCustomDBEngineVersionCreated(ctx context.Context, conn *rds.RDS, id str return nil, err } -// TIP: It is easier to determine whether a resource is updated for some -// resources than others. The best case is a status flag that tells you when -// the update has been fully realized. Other times, you can check to see if a -// key resource argument is updated to a new value or not. - func waitCustomDBEngineVersionUpdated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated}, + Pending: []string{statusAvailable}, + Target: []string{statusAvailable}, Refresh: statusCustomDBEngineVersion(ctx, conn, id), Timeout: timeout, NotFoundChecks: 20, @@ -371,12 +365,9 @@ func waitCustomDBEngineVersionUpdated(ctx context.Context, conn *rds.RDS, id str return nil, err } -// TIP: A deleted waiter is almost like a backwards created waiter. There may -// be additional pending states, however. - func waitCustomDBEngineVersionDeleted(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, + Pending: []string{statusDeleting}, Target: []string{}, Refresh: statusCustomDBEngineVersion(ctx, conn, id), Timeout: timeout, @@ -406,7 +397,10 @@ func statusCustomDBEngineVersion(ctx context.Context, conn *rds.RDS, id string) } func findCustomDBEngineVersionByID(ctx context.Context, conn *rds.RDS, id string) (*rds.DBEngineVersion, error) { - engine, engineVersion, err := customEngineVersionParseID(id) + engine, engineVersion, e := customEngineVersionParseID(id) + if e != nil { + return nil, e + } input := &rds.DescribeDBEngineVersionsInput{ Engine: aws.String(engine), EngineVersion: aws.String(engineVersion), From 8ee6c5f2083ddca705a59c30e817ab841a8a47c9 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 29 Aug 2023 13:30:25 -0400 Subject: [PATCH 04/20] added CEVs --- .../service/rds/custom_db_engine_version.go | 136 +-------- .../rds/custom_db_engine_version_test.go | 261 +++++++----------- .../test-fixtures/custom-oracle-manifest.json | 18 ++ 3 files changed, 131 insertions(+), 284 deletions(-) create mode 100644 internal/service/rds/test-fixtures/custom-oracle-manifest.json diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index bd3725f6928..13795ae3399 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -23,7 +23,6 @@ import ( "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/errs" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -219,7 +218,7 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RDSConn(ctx) - out, err := findCustomDBEngineVersionByID(ctx, conn, d.Id()) + out, err := FindCustomDBEngineVersionByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] RDS CustomDBEngineVersion (%s) not found, removing from state", d.Id()) d.SetId("") @@ -299,7 +298,10 @@ func resourceCustomDBEngineVersionDelete(ctx context.Context, d *schema.Resource log.Printf("[INFO] Deleting RDS CustomDBEngineVersion %s", d.Id()) engine, engineVersion, e := customEngineVersionParseID(d.Id()) - output, err := conn.DeleteCustomDBEngineVersionWithContext(ctx, &rds.DeleteCustomDBEngineVersionInput{ + if e != nil { + return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), e)...) + } + _, err := conn.DeleteCustomDBEngineVersionWithContext(ctx, &rds.DeleteCustomDBEngineVersionInput{ Engine: aws.String(engine), EngineVersion: aws.String(engineVersion), }) @@ -312,12 +314,10 @@ func resourceCustomDBEngineVersionDelete(ctx context.Context, d *schema.Resource return append(diags, create.DiagError(names.RDS, create.ErrActionDeleting, ResNameCustomDBEngineVersion, d.Id(), err)...) } - // TIP: -- 4. Use a waiter to wait for delete to complete if _, err := waitCustomDBEngineVersionDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return append(diags, create.DiagError(names.RDS, create.ErrActionWaitingForDeletion, ResNameCustomDBEngineVersion, d.Id(), err)...) } - // TIP: -- 5. Return diags return diags } @@ -383,7 +383,7 @@ func waitCustomDBEngineVersionDeleted(ctx context.Context, conn *rds.RDS, id str func statusCustomDBEngineVersion(ctx context.Context, conn *rds.RDS, id string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - out, err := findCustomDBEngineVersionByID(ctx, conn, id) + out, err := FindCustomDBEngineVersionByID(ctx, conn, id) if tfresource.NotFound(err) { return nil, "", nil } @@ -396,7 +396,7 @@ func statusCustomDBEngineVersion(ctx context.Context, conn *rds.RDS, id string) } } -func findCustomDBEngineVersionByID(ctx context.Context, conn *rds.RDS, id string) (*rds.DBEngineVersion, error) { +func FindCustomDBEngineVersionByID(ctx context.Context, conn *rds.RDS, id string) (*rds.DBEngineVersion, error) { engine, engineVersion, e := customEngineVersionParseID(id) if e != nil { return nil, e @@ -426,130 +426,8 @@ func findCustomDBEngineVersionByID(ctx context.Context, conn *rds.RDS, id string return output.DBEngineVersions[0], nil } -// TIP: ==== FLEX ==== -// Flatteners and expanders ("flex" functions) help handle complex data -// types. Flatteners take an API data type and return something you can use in -// a d.Set() call. In other words, flatteners translate from AWS -> Terraform. -// -// On the other hand, expanders take a Terraform data structure and return -// something that you can send to the AWS API. In other words, expanders -// translate from Terraform -> AWS. -// -// See more: -// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ -func flattenComplexArgument(apiObject *rds.ComplexArgument) map[string]interface{} { - if apiObject == nil { - return nil - } - - m := map[string]interface{}{} - - if v := apiObject.SubFieldOne; v != nil { - m["sub_field_one"] = aws.ToString(v) - } - - if v := apiObject.SubFieldTwo; v != nil { - m["sub_field_two"] = aws.ToString(v) - } - - return m -} - -// TIP: Often the AWS API will return a slice of structures in response to a -// request for information. Sometimes you will have set criteria (e.g., the ID) -// that means you'll get back a one-length slice. This plural function works -// brilliantly for that situation too. -func flattenComplexArguments(apiObjects []*rds.ComplexArgument) []interface{} { - if len(apiObjects) == 0 { - return nil - } - - var l []interface{} - - for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - - l = append(l, flattenComplexArgument(apiObject)) - } - - return l -} - -// TIP: Remember, as mentioned above, expanders take a Terraform data structure -// and return something that you can send to the AWS API. In other words, -// expanders translate from Terraform -> AWS. -// -// See more: -// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ -func expandComplexArgument(tfMap map[string]interface{}) *rds.ComplexArgument { - if tfMap == nil { - return nil - } - - a := &rds.ComplexArgument{} - - if v, ok := tfMap["sub_field_one"].(string); ok && v != "" { - a.SubFieldOne = aws.String(v) - } - - if v, ok := tfMap["sub_field_two"].(string); ok && v != "" { - a.SubFieldTwo = aws.String(v) - } - - return a -} - -// TIP: Even when you have a list with max length of 1, this plural function -// works brilliantly. However, if the AWS API takes a structure rather than a -// slice of structures, you will not need it. -func expandComplexArguments(tfList []interface{}) []*rds.ComplexArgument { - // TIP: The AWS API can be picky about whether you send a nil or zero- - // length for an argument that should be cleared. For example, in some - // cases, if you send a nil value, the AWS API interprets that as "make no - // changes" when what you want to say is "remove everything." Sometimes - // using a zero-length list will cause an error. - // - // As a result, here are two options. Usually, option 1, nil, will work as - // expected, clearing the field. But, test going from something to nothing - // to make sure it works. If not, try the second option. - - // TIP: Option 1: Returning nil for zero-length list - if len(tfList) == 0 { - return nil - } - - var s []*rds.ComplexArgument - - // TIP: Option 2: Return zero-length list for zero-length list. If option 1 does - // not work, after testing going from something to nothing (if that is - // possible), uncomment out the next line and remove option 1. - // - // s := make([]*rds.ComplexArgument, 0) - - for _, r := range tfList { - m, ok := r.(map[string]interface{}) - - if !ok { - continue - } - - a := expandComplexArgument(m) - - if a == nil { - continue - } - - s = append(s, a) - } - - return s -} - func customEngineVersionParseID(id string) (string, string, error) { parts := strings.SplitN(id, ":", 2) - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return "", "", fmt.Errorf("unexpected format of ID (%s), expected engine:engineversion", id) } diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index aa5102f9003..58dd23f5df6 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -3,52 +3,21 @@ package rds_test -// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** -// -// TIP: ==== INTRODUCTION ==== -// Thank you for trying the skaff tool! -// -// You have opted to include these helpful comments. They all include "TIP:" -// to help you find and remove them when you're done with them. -// -// While some aspects of this file are customized to your input, the -// scaffold tool does *not* look at the AWS API and ensure it has correct -// function, structure, and variable names. It makes guesses based on -// commonalities. You will need to make significant adjustments. -// -// In other words, as generated, this is a rough outline of the work you will -// need to do. If something doesn't make sense for your situation, get rid of -// it. - import ( - // TIP: ==== IMPORTS ==== - // This is a common set of imports but not customized to your code since - // your code hasn't been written yet. Make sure you, your IDE, or - // goimports -w fixes these imports. - // - // The provider linter wants your imports to be in two groups: first, - // standard library (i.e., "fmt" or "strings"), second, everything else. - // - // Also, AWS Go SDK v2 may handle nested structures differently than v1, - // using the services/rds/types package. If so, you'll - // need to import types and reference the nested types, e.g., as - // types.. "context" "errors" "fmt" "regexp" "testing" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/rds" - "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/aws/aws-sdk-go/service/rds" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" // TIP: You will often need to import the package that this test file lives @@ -84,82 +53,73 @@ import ( // Cut and dry functions using well-used patterns, like typical flatteners and // expanders, don't need unit testing. However, if they are complex or // intricate, they should be unit tested. -func TestCustomDBEngineVersionExampleUnitTest(t *testing.T) { - t.Parallel() - - testCases := []struct { - TestName string - Input string - Expected string - Error bool - }{ - { - TestName: "empty", - Input: "", - Expected: "", - Error: true, - }, - { - TestName: "descriptive name", - Input: "some input", - Expected: "some output", - Error: false, - }, - { - TestName: "another descriptive name", - Input: "more input", - Expected: "more output", - Error: false, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.TestName, func(t *testing.T) { - t.Parallel() - got, err := tfrds.FunctionFromResource(testCase.Input) +// func TestCustomDBEngineVersionExampleUnitTest(t *testing.T) { +// t.Parallel() + +// testCases := []struct { +// TestName string +// Input string +// Expected string +// Error bool +// }{ +// { +// TestName: "empty", +// Input: "", +// Expected: "", +// Error: true, +// }, +// { +// TestName: "descriptive name", +// Input: "some input", +// Expected: "some output", +// Error: false, +// }, +// { +// TestName: "another descriptive name", +// Input: "more input", +// Expected: "more output", +// Error: false, +// }, +// } + +// for _, testCase := range testCases { +// testCase := testCase +// t.Run(testCase.TestName, func(t *testing.T) { +// t.Parallel() +// got, err := tfrds.FunctionFromResource(testCase.Input) + +// if err != nil && !testCase.Error { +// t.Errorf("got error (%s), expected no error", err) +// } + +// if err == nil && testCase.Error { +// t.Errorf("got (%s) and no error, expected error", got) +// } + +// if got != testCase.Expected { +// t.Errorf("got %s, expected %s", got, testCase.Expected) +// } +// }) +// } +// } - if err != nil && !testCase.Error { - t.Errorf("got error (%s), expected no error", err) - } - - if err == nil && testCase.Error { - t.Errorf("got (%s) and no error, expected error", got) - } - - if got != testCase.Expected { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - -// TIP: ==== ACCEPTANCE TESTS ==== -// This is an example of a basic acceptance test. This should test as much of -// standard functionality of the resource as possible, and test importing, if -// applicable. We prefix its name with "TestAcc", the service, and the -// resource name. -// -// Acceptance test access AWS and cost money to run. func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { ctx := acctest.Context(t) - // TIP: This is a long-running test guard for tests that run longer than - // 300s (5 min) generally. if testing.Short() { t.Skip("skipping long-running test in short mode") } - var customdbengineversion rds.DescribeCustomDBEngineVersionResponse + var customdbengineversion rds.DBEngineVersion rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.RDSEndpointID) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) testAccPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.RDSEndpointID), + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ @@ -167,14 +127,8 @@ func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { Config: testAccCustomDBEngineVersionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), - resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"), - resource.TestCheckResourceAttrSet(resourceName, "maintenance_window_start_time.0.day_of_week"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "user.*", map[string]string{ - "console_access": "false", - "groups.#": "0", - "username": "Test", - "password": "TestTest1234", - }), + resource.TestCheckResourceAttr(resourceName, "engine_version", rName), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), ), }, @@ -182,7 +136,7 @@ func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, }, }, }) @@ -194,22 +148,22 @@ func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var customdbengineversion rds.DescribeCustomDBEngineVersionResponse + var customdbengineversion rds.DBEngineVersion rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.RDSEndpointID) - testAccPreCheck(t) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) + testAccPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.RDSEndpointID), + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCustomDBEngineVersionConfig_basic(rName, testAccCustomDBEngineVersionVersionNewer), + Config: testAccCustomDBEngineVersionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceCustomDBEngineVersion(), resourceName), @@ -222,24 +176,20 @@ func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { func testAccCheckCustomDBEngineVersionDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_rds_custom_db_engine_version" { continue } - input := &rds.DescribeCustomDBEngineVersionInput{ - CustomDBEngineVersionId: aws.String(rs.Primary.ID), - } - _, err := conn.DescribeCustomDBEngineVersion(ctx, &rds.DescribeCustomDBEngineVersionInput{ - CustomDBEngineVersionId: aws.String(rs.Primary.ID), - }) - if errs.IsA[*types.ResourceNotFoundException](err) { - return nil + _, err := tfrds.FindCustomDBEngineVersionByID(ctx, conn, rs.Primary.ID) + if tfresource.NotFound(err) { + continue } + if err != nil { - return nil + return err } return create.Error(names.RDS, create.ErrActionCheckingDestroyed, tfrds.ResNameCustomDBEngineVersion, rs.Primary.ID, errors.New("not destroyed")) @@ -249,7 +199,7 @@ func testAccCheckCustomDBEngineVersionDestroy(ctx context.Context) resource.Test } } -func testAccCheckCustomDBEngineVersionExists(ctx context.Context, name string, customdbengineversion *rds.DescribeCustomDBEngineVersionResponse) resource.TestCheckFunc { +func testAccCheckCustomDBEngineVersionExists(ctx context.Context, name string, customdbengineversion *rds.DBEngineVersion) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -260,26 +210,24 @@ func testAccCheckCustomDBEngineVersionExists(ctx context.Context, name string, c return create.Error(names.RDS, create.ErrActionCheckingExistence, tfrds.ResNameCustomDBEngineVersion, name, errors.New("not set")) } - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) - resp, err := conn.DescribeCustomDBEngineVersion(ctx, &rds.DescribeCustomDBEngineVersionInput{ - CustomDBEngineVersionId: aws.String(rs.Primary.ID), - }) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) + output, err := tfrds.FindCustomDBEngineVersionByID(ctx, conn, rs.Primary.ID) if err != nil { return create.Error(names.RDS, create.ErrActionCheckingExistence, tfrds.ResNameCustomDBEngineVersion, rs.Primary.ID, err) } - *customdbengineversion = *resp + *customdbengineversion = *output return nil } } func testAccPreCheck(ctx context.Context, t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) - input := &rds.ListCustomDBEngineVersionsInput{} - _, err := conn.ListCustomDBEngineVersions(ctx, input) + input := &rds.DescribeDBEngineVersionsInput{} + _, err := conn.DescribeDBEngineVersionsWithContext(ctx, input) if acctest.PreCheckSkipError(err) { t.Skipf("skipping acceptance testing: %s", err) @@ -289,39 +237,42 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } -func testAccCheckCustomDBEngineVersionNotRecreated(before, after *rds.DescribeCustomDBEngineVersionResponse) resource.TestCheckFunc { - return func(s *terraform.State) error { - if before, after := aws.ToString(before.CustomDBEngineVersionId), aws.ToString(after.CustomDBEngineVersionId); before != after { - return create.Error(names.RDS, create.ErrActionCheckingNotRecreated, tfrds.ResNameCustomDBEngineVersion, aws.ToString(before.CustomDBEngineVersionId), errors.New("recreated")) - } +func testAccCustomDBEngineVersionConfig_basic(rName string) string { + return fmt.Sprintf(` +data "aws_ami" "test" { + most_recent = true + owners = ["amazon"] - return nil - } -} + filter { + name = "name" + values = ["Windows_Server-2022-English-Full-SQL_2019_Standard-2023.08.10"] + } -func testAccCustomDBEngineVersionConfig_basic(rName, version string) string { - return fmt.Sprintf(` -resource "aws_security_group" "test" { - name = %[1]q -} + filter { + name = "virtualization-type" + values = ["hvm"] + } -resource "aws_rds_custom_db_engine_version" "test" { - custom_db_engine_version_name = %[1]q - engine_type = "ActiveRDS" - engine_version = %[2]q - host_instance_type = "rds.t2.micro" - security_groups = [aws_security_group.test.id] - authentication_strategy = "simple" - storage_type = "efs" - - logs { - general = true + filter { + name = "root-device-type" + values = ["ebs"] } - user { - username = "Test" - password = "TestTest1234" + filter { + name = "block-device-mapping.volume-type" + values = ["gp2"] } } -`, rName, version) + +resource "aws_kms_key" "rdscfo_kms_key" { + description = "KMS symmetric key for RDS Custom for Oracle" +} + +resource "aws_rds_custom_db_engine_version" "test" { + engine = "custom-sqlserver-se" + engine_version = %[1]q + image-id = aws_ami.test.id + kms_key_id = aws_kms_key.rdscfo_kms_key.key_id +} +`, rName) } diff --git a/internal/service/rds/test-fixtures/custom-oracle-manifest.json b/internal/service/rds/test-fixtures/custom-oracle-manifest.json new file mode 100644 index 00000000000..73f7b6b7480 --- /dev/null +++ b/internal/service/rds/test-fixtures/custom-oracle-manifest.json @@ -0,0 +1,18 @@ +{ + "mediaImportTemplateVersion":"2020-08-14", + "databaseInstallationFileNames":[ + "V982063-01.zip" + ], + "opatchFileNames":[ + "p6880880_190000_Linux-x86-64.zip" + ], + "psuRuPatchFileNames":[ + "p31720396_190000_Linux-x86-64.zip", + "p29213893_199000DBRU_Generic.zip", + "p29374604_199000DBRU_Linux-x86-64.zip", + "p28852325_190000_Linux-x86-64.zip", + "p29997937_190000_Linux-x86-64.zip", + "p31335037_190000_Linux-x86-64.zip", + "p31335142_190000_Generic.zip" + ] +} \ No newline at end of file From 961689d81ba76318016080c91fd69cb13e35348b Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 29 Aug 2023 16:01:56 -0400 Subject: [PATCH 05/20] added to package gen --- internal/service/rds/service_package_gen.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/service/rds/service_package_gen.go b/internal/service/rds/service_package_gen.go index e66ec36d2c0..7dc69c64b19 100644 --- a/internal/service/rds/service_package_gen.go +++ b/internal/service/rds/service_package_gen.go @@ -230,6 +230,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceClusterRoleAssociation, TypeName: "aws_rds_cluster_role_association", }, + { + Factory: ResourceCustomDBEngineVersion, + TypeName: "aws_rds_custom_db_engine_version", + Name: "Custom DB Engine Version", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, { Factory: ResourceGlobalCluster, TypeName: "aws_rds_global_cluster", From 1e26ba50907dc4776433357cbbed7472129f3c5b Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 30 Aug 2023 16:10:07 -0400 Subject: [PATCH 06/20] added CfSS, CfO --- .../service/rds/custom_db_engine_version.go | 15 +- .../rds/custom_db_engine_version_test.go | 241 ++++++++++-------- .../test-fixtures/custom-oracle-manifest.json | 12 - 3 files changed, 139 insertions(+), 129 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index 13795ae3399..1b78a55943d 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -322,17 +322,18 @@ func resourceCustomDBEngineVersionDelete(ctx context.Context, d *schema.Resource } const ( - statusAvailable = "available" - statusCreating = "creating" - statusDeleting = "deleting" - statusDeprecated = "deprecated" - statusFailed = "failed" + statusAvailable = "available" + statusCreating = "creating" + statusDeleting = "deleting" + statusDeprecated = "deprecated" + statusFailed = "failed" + statusPendingValidation = "pending-validation" // Custom for SQL Server, ready for validation by an instance ) func waitCustomDBEngineVersionCreated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { stateConf := &retry.StateChangeConf{ Pending: []string{statusCreating}, - Target: []string{statusAvailable}, + Target: []string{statusAvailable, statusPendingValidation}, Refresh: statusCustomDBEngineVersion(ctx, conn, id), Timeout: timeout, NotFoundChecks: 20, @@ -350,7 +351,7 @@ func waitCustomDBEngineVersionCreated(ctx context.Context, conn *rds.RDS, id str func waitCustomDBEngineVersionUpdated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) (*rds.DBEngineVersion, error) { stateConf := &retry.StateChangeConf{ Pending: []string{statusAvailable}, - Target: []string{statusAvailable}, + Target: []string{statusAvailable, statusPendingValidation}, Refresh: statusCustomDBEngineVersion(ctx, conn, id), Timeout: timeout, NotFoundChecks: 20, diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index 58dd23f5df6..41d287d7079 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -17,100 +17,99 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" - - // TIP: You will often need to import the package that this test file lives - // in. Since it is in the "test" context, it must import the package to use - // any normal context constants, variables, or functions. - tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" ) -// TIP: File Structure. The basic outline for all test files should be as -// follows. Improve this resource's maintainability by following this -// outline. -// -// 1. Package declaration (add "_test" since this is a test file) -// 2. Imports -// 3. Unit tests -// 4. Basic test -// 5. Disappears test -// 6. All the other tests -// 7. Helper functions (exists, destroy, check, etc.) -// 8. Functions that return Terraform configurations - -// TIP: ==== UNIT TESTS ==== -// This is an example of a unit test. Its name is not prefixed with -// "TestAcc" like an acceptance test. -// -// Unlike acceptance tests, unit tests do not access AWS and are focused on a -// function (or method). Because of this, they are quick and cheap to run. -// -// In designing a resource's implementation, isolate complex bits from AWS bits -// so that they can be tested through a unit test. We encourage more unit tests -// in the provider. -// -// Cut and dry functions using well-used patterns, like typical flatteners and -// expanders, don't need unit testing. However, if they are complex or -// intricate, they should be unit tested. -// func TestCustomDBEngineVersionExampleUnitTest(t *testing.T) { -// t.Parallel() - -// testCases := []struct { -// TestName string -// Input string -// Expected string -// Error bool -// }{ -// { -// TestName: "empty", -// Input: "", -// Expected: "", -// Error: true, -// }, -// { -// TestName: "descriptive name", -// Input: "some input", -// Expected: "some output", -// Error: false, -// }, -// { -// TestName: "another descriptive name", -// Input: "more input", -// Expected: "more output", -// Error: false, -// }, -// } - -// for _, testCase := range testCases { -// testCase := testCase -// t.Run(testCase.TestName, func(t *testing.T) { -// t.Parallel() -// got, err := tfrds.FunctionFromResource(testCase.Input) - -// if err != nil && !testCase.Error { -// t.Errorf("got error (%s), expected no error", err) -// } - -// if err == nil && testCase.Error { -// t.Errorf("got (%s) and no error, expected error", got) -// } - -// if got != testCase.Expected { -// t.Errorf("got %s, expected %s", got, testCase.Expected) -// } -// }) -// } -// } - -func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { +func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") } var customdbengineversion rds.DBEngineVersion - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) + ami := "ami-0bb58d385d4c80ea2" + resourceName := "aws_rds_custom_db_engine_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_sqlServer(rName, ami), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "engine_version", rName), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, + }, + }, + }) +} + +func TestAccRDSCustomDBEngineVersion_oracle(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customdbengineversion rds.DBEngineVersion + rName := fmt.Sprintf("%s%s%d", "19.19.ee.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) + resourceName := "aws_rds_custom_db_engine_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_oracle(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "engine_version", rName), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, + }, + }, + }) +} + +func TestAccRDSCustomDBEngineVersion_manifestFile(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customdbengineversion rds.DBEngineVersion + rName := fmt.Sprintf("%s%s%d", "19.19.ee.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) + filename := "test-fixtures/custom-oracle-manifest.json" resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ @@ -124,7 +123,7 @@ func TestAccRDSCustomDBEngineVersion_basic(t *testing.T) { CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCustomDBEngineVersionConfig_basic(rName), + Config: testAccCustomDBEngineVersionConfig_manifestFile(rName, filename), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), resource.TestCheckResourceAttr(resourceName, "engine_version", rName), @@ -150,6 +149,7 @@ func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { var customdbengineversion rds.DBEngineVersion rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + ami := "ami-0bb58d385d4c80ea2" resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ @@ -163,7 +163,7 @@ func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCustomDBEngineVersionConfig_basic(rName), + Config: testAccCustomDBEngineVersionConfig_sqlServer(rName, ami), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceCustomDBEngineVersion(), resourceName), @@ -237,42 +237,63 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } -func testAccCustomDBEngineVersionConfig_basic(rName string) string { +func testAccCustomDBEngineVersionConfig_sqlServer(rName, ami string) string { return fmt.Sprintf(` -data "aws_ami" "test" { - most_recent = true - owners = ["amazon"] +data "aws_region" "current" {} - filter { - name = "name" - values = ["Windows_Server-2022-English-Full-SQL_2019_Standard-2023.08.10"] - } +# Copy the Amazon AMI for Windows SQL Server, CEV creation requires an AMI owned by the operator +resource "aws_ami_copy" "test" { + name = %[1]q + source_ami_id = %[2]q + source_ami_region = data.aws_region.current.name +} - filter { - name = "virtualization-type" - values = ["hvm"] - } +resource "aws_kms_key" "rdscfss_kms_key" { + description = "KMS symmetric key for RDS Custom for SQL Server" +} - filter { - name = "root-device-type" - values = ["ebs"] - } +resource "aws_rds_custom_db_engine_version" "test" { + engine = "custom-sqlserver-se" + engine_version = %[1]q + image_id = aws_ami_copy.test.id + kms_key_id = aws_kms_key.rdscfss_kms_key.arn +} +`, rName, ami) +} - filter { - name = "block-device-mapping.volume-type" - values = ["gp2"] +func testAccCustomDBEngineVersionConfig_oracle(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "rdscfo_kms_key" { + description = "KMS symmetric key for RDS Custom for Oracle" +} + +resource "aws_rds_custom_db_engine_version" "test" { + database_installation_files_s3_bucket_name = "313127153659-rds" + engine = "custom-oracle-ee-cdb" + engine_version = %[1]q + kms_key_id = aws_kms_key.rdscfo_kms_key.arn + manifest = < Date: Thu, 31 Aug 2023 12:48:04 -0400 Subject: [PATCH 07/20] added tests and updates --- .../service/rds/custom_db_engine_version.go | 13 +- .../rds/custom_db_engine_version_test.go | 142 +++++++++++++++--- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index 1b78a55943d..9be61f7902b 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -106,7 +106,6 @@ func ResourceCustomDBEngineVersion() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, ValidateFunc: verify.ValidARN, }, "major_engine_version": { @@ -230,7 +229,9 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa } d.Set("arn", out.DBEngineVersionArn) - d.Set("create_time", out.CreateTime) + if out.CreateTime != nil { + d.Set("create_time", out.CreateTime.Format(time.RFC3339)) + } d.Set("database_installation_files_s3_bucket_name", out.DatabaseInstallationFilesS3BucketName) d.Set("database_installation_files_s3_prefix", out.DatabaseInstallationFilesS3Prefix) d.Set("db_parameter_group_family", out.DBParameterGroupFamily) @@ -238,7 +239,9 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa d.Set("engine", out.Engine) d.Set("engine_version", out.EngineVersion) d.Set("image_id", out.Image.ImageId) - d.Set("kms_key_id", out.KMSKeyId) + if out.KMSKeyId != nil { + d.Set("kms_key_id", out.KMSKeyId) + } d.Set("major_engine_version", out.MajorEngineVersion) d.Set("manifest", out.CustomDBEngineVersionManifest) d.Set("status", out.Status) @@ -252,6 +255,10 @@ func resourceCustomDBEngineVersionUpdate(ctx context.Context, d *schema.Resource var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RDSConn(ctx) + if d.HasChangesExcept("description", "status") { + return append(diags, create.DiagError(names.RDS, create.ErrActionUpdating, ResNameCustomDBEngineVersion, d.Id(), errors.New("only description and status can be updated"))...) + } + update := false engine, engineVersion, e := customEngineVersionParseID(d.Id()) if e != nil { diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index 41d287d7079..2505af5c365 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -7,9 +7,10 @@ import ( "context" "errors" "fmt" - "regexp" + "os" "testing" + "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/service/rds" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -28,9 +29,14 @@ func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { t.Skip("skipping long-running test in short mode") } + // Requires an existing Windows SQL Server AMI owned by operating account set as environmental variable + key := "RDS_CUSTOM_WINDOWS_SQLSERVER_AMI" + ami := os.Getenv(key) + if ami == "" { + t.Skipf("Environment variable %s is not set", key) + } var customdbengineversion rds.DBEngineVersion rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) - ami := "ami-0bb58d385d4c80ea2" resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ @@ -49,14 +55,70 @@ func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), resource.TestCheckResourceAttr(resourceName, "engine_version", rName), resource.TestCheckResourceAttrSet(resourceName, "create_time"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", + regexache.MustCompile(fmt.Sprintf(`cev:custom-sqlserver.+%s.+`, rName))), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, + }, + }, + }) +} + +func testAccRDSCustomDBEngineVersion_sqlServerUpdate(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + // Requires an existing Windows SQL Server AMI owned by operating account set as environmental variable + key := "RDS_CUSTOM_WINDOWS_SQLSERVER_AMI" + ami := os.Getenv(key) + if ami == "" { + t.Skipf("Environment variable %s is not set", key) + } + var customdbengineversion rds.DBEngineVersion + rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) + resourceName := "aws_rds_custom_db_engine_version.test" + status := "pending-validation" + status2 := "inactive" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_sqlServer(rName, ami), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "engine_version", rName), + resource.TestCheckResourceAttr(resourceName, "status", status), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, + }, + { + Config: testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, status2), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "engine_version", rName), + resource.TestCheckResourceAttr(resourceName, "status", status2), + ), }, }, }) @@ -68,6 +130,12 @@ func TestAccRDSCustomDBEngineVersion_oracle(t *testing.T) { t.Skip("skipping long-running test in short mode") } + // Requires an existing Oracle installation media in S3 bucket owned (bucket must be in operating region) by operating account set as environmental variable + key := "RDS_CUSTOM_ORACLE_S3_BUCKET" + bucket := os.Getenv(key) + if bucket == "" { + t.Skipf("Environment variable %s is not set", key) + } var customdbengineversion rds.DBEngineVersion rName := fmt.Sprintf("%s%s%d", "19.19.ee.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) resourceName := "aws_rds_custom_db_engine_version.test" @@ -83,19 +151,19 @@ func TestAccRDSCustomDBEngineVersion_oracle(t *testing.T) { CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCustomDBEngineVersionConfig_oracle(rName), + Config: testAccCustomDBEngineVersionConfig_oracle(rName, bucket), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), resource.TestCheckResourceAttr(resourceName, "engine_version", rName), resource.TestCheckResourceAttrSet(resourceName, "create_time"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexache.MustCompile(fmt.Sprintf(`cev:custom-oracle.+%s.+`, rName))), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, }, }, }) @@ -107,6 +175,12 @@ func TestAccRDSCustomDBEngineVersion_manifestFile(t *testing.T) { t.Skip("skipping long-running test in short mode") } + // Requires an existing Oracle installation media in S3 bucket owned (bucket must be in operating region) by operating account set as environmental variable + key := "RDS_CUSTOM_ORACLE_S3_BUCKET" + bucket := os.Getenv(key) + if bucket == "" { + t.Skipf("Environment variable %s is not set", key) + } var customdbengineversion rds.DBEngineVersion rName := fmt.Sprintf("%s%s%d", "19.19.ee.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) filename := "test-fixtures/custom-oracle-manifest.json" @@ -123,19 +197,19 @@ func TestAccRDSCustomDBEngineVersion_manifestFile(t *testing.T) { CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCustomDBEngineVersionConfig_manifestFile(rName, filename), + Config: testAccCustomDBEngineVersionConfig_manifestFile(rName, bucket, filename), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), resource.TestCheckResourceAttr(resourceName, "engine_version", rName), resource.TestCheckResourceAttrSet(resourceName, "create_time"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(`customdbengineversion:+.`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexache.MustCompile(fmt.Sprintf(`cev:custom-oracle.+%s.+`, rName))), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"filename", "manifest_hash"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, }, }, }) @@ -147,9 +221,14 @@ func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } + // Requires an existing Windows SQL Server AMI owned by operating account set as environmental variable + key := "RDS_CUSTOM_WINDOWS_SQLSERVER_AMI" + ami := os.Getenv(key) + if ami == "" { + t.Skipf("Environment variable %s is not set", key) + } var customdbengineversion rds.DBEngineVersion - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - ami := "ami-0bb58d385d4c80ea2" + rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) resourceName := "aws_rds_custom_db_engine_version.test" resource.ParallelTest(t, resource.TestCase{ @@ -248,27 +327,42 @@ resource "aws_ami_copy" "test" { source_ami_region = data.aws_region.current.name } -resource "aws_kms_key" "rdscfss_kms_key" { - description = "KMS symmetric key for RDS Custom for SQL Server" +resource "aws_rds_custom_db_engine_version" "test" { + engine = "custom-sqlserver-se" + engine_version = %[1]q + image_id = aws_ami_copy.test.id +} +`, rName, ami) +} + +func testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, status string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +# Copy the Amazon AMI for Windows SQL Server, CEV creation requires an AMI owned by the operator +resource "aws_ami_copy" "test" { + name = %[1]q + source_ami_id = %[2]q + source_ami_region = data.aws_region.current.name } resource "aws_rds_custom_db_engine_version" "test" { engine = "custom-sqlserver-se" engine_version = %[1]q image_id = aws_ami_copy.test.id - kms_key_id = aws_kms_key.rdscfss_kms_key.arn + status = %[3]q } -`, rName, ami) +`, rName, ami, status) } -func testAccCustomDBEngineVersionConfig_oracle(rName string) string { +func testAccCustomDBEngineVersionConfig_oracle(rName, bucket string) string { return fmt.Sprintf(` resource "aws_kms_key" "rdscfo_kms_key" { description = "KMS symmetric key for RDS Custom for Oracle" } resource "aws_rds_custom_db_engine_version" "test" { - database_installation_files_s3_bucket_name = "313127153659-rds" + database_installation_files_s3_bucket_name = %[2]q engine = "custom-oracle-ee-cdb" engine_version = %[1]q kms_key_id = aws_kms_key.rdscfo_kms_key.arn @@ -278,22 +372,22 @@ resource "aws_rds_custom_db_engine_version" "test" { } JSON } -`, rName) +`, rName, bucket) } -func testAccCustomDBEngineVersionConfig_manifestFile(rName, filename string) string { +func testAccCustomDBEngineVersionConfig_manifestFile(rName, bucket, filename string) string { return fmt.Sprintf(` resource "aws_kms_key" "rdscfo_kms_key" { description = "KMS symmetric key for RDS Custom for Oracle" } resource "aws_rds_custom_db_engine_version" "test" { - database_installation_files_s3_bucket_name = "313127153659-rds" + database_installation_files_s3_bucket_name = %[2]q engine = "custom-oracle-ee-cdb" engine_version = %[1]q kms_key_id = aws_kms_key.rdscfo_kms_key.arn - filename = %[2]q - manifest_hash = filebase64sha256(%[2]q) + filename = %[3]q + manifest_hash = filebase64sha256(%[3]q) } -`, rName, filename) +`, rName, bucket, filename) } From d030bf10cc03b87ad3e241ad9cea4c8a5e1330a0 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Thu, 31 Aug 2023 13:02:17 -0400 Subject: [PATCH 08/20] making tetests callable --- internal/service/rds/custom_db_engine_version_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index 2505af5c365..72b86de4317 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -69,7 +69,7 @@ func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { }) } -func testAccRDSCustomDBEngineVersion_sqlServerUpdate(t *testing.T) { +func TestAccRDSCustomDBEngineVersion_sqlServerUpdate(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") From c1c7756ab5490df7490766ca2bc2efd7d6ac88e3 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Fri, 1 Sep 2023 18:46:59 -0400 Subject: [PATCH 09/20] added source AMI support, buttoned up tests. --- .../service/rds/custom_db_engine_version.go | 29 +++- .../rds/custom_db_engine_version_test.go | 31 ++-- .../docs/r/custom_db_engine_version.markdown | 145 ++++++++++++++++++ 3 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 website/docs/r/custom_db_engine_version.markdown diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index 9be61f7902b..bb9ae6b18d7 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -60,11 +60,13 @@ func ResourceCustomDBEngineVersion() *schema.Resource { "database_installation_files_s3_bucket_name": { Type: schema.TypeString, Optional: true, + ForceNew: true, ValidateFunc: validation.StringLenBetween(3, 63), }, "database_installation_files_s3_prefix": { Type: schema.TypeString, Optional: true, + ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 255), }, "db_parameter_group_family": { @@ -94,18 +96,19 @@ func ResourceCustomDBEngineVersion() *schema.Resource { "filename": { Type: schema.TypeString, Optional: true, + ForceNew: true, ConflictsWith: []string{"manifest"}, }, + //API returns created image_id of the newly created image. "image_id": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(1, 255), + Type: schema.TypeString, + Computed: true, }, "kms_key_id": { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateFunc: verify.ValidARN, }, "major_engine_version": { @@ -115,7 +118,6 @@ func ResourceCustomDBEngineVersion() *schema.Resource { "manifest": { Type: schema.TypeString, Optional: true, - Computed: true, ForceNew: true, ValidateFunc: validation.All( validation.StringIsJSON, @@ -128,6 +130,11 @@ func ResourceCustomDBEngineVersion() *schema.Resource { return json }, }, + //API returns manifest with service added additions, non-determinestic. + "manifest_computed": { + Type: schema.TypeString, + Computed: true, + }, "manifest_hash": { Type: schema.TypeString, Optional: true, @@ -138,6 +145,14 @@ func ResourceCustomDBEngineVersion() *schema.Resource { Computed: true, ValidateFunc: validation.StringInSlice(rds.CustomEngineVersionStatus_Values(), false), }, + // Allow CEV creation from a source AMI ID. + // implicit state passthrough, virtual attribute + "source_image_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, @@ -171,7 +186,7 @@ func resourceCustomDBEngineVersionCreate(ctx context.Context, d *schema.Resource input.Description = aws.String(v.(string)) } - if v, ok := d.GetOk("image_id"); ok { + if v, ok := d.GetOk("source_image_id"); ok { input.ImageId = aws.String(v.(string)) } @@ -243,7 +258,7 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa d.Set("kms_key_id", out.KMSKeyId) } d.Set("major_engine_version", out.MajorEngineVersion) - d.Set("manifest", out.CustomDBEngineVersionManifest) + d.Set("manifest_computed", out.CustomDBEngineVersionManifest) d.Set("status", out.Status) setTagsOut(ctx, out.TagList) diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index 72b86de4317..978f12be2a8 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -30,6 +30,7 @@ func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { } // Requires an existing Windows SQL Server AMI owned by operating account set as environmental variable + // Blog: https://aws.amazon.com/blogs/database/persist-your-os-level-customization-within-amazon-rds-custom-for-sql-server-using-custom-engine-version-cev/ key := "RDS_CUSTOM_WINDOWS_SQLSERVER_AMI" ami := os.Getenv(key) if ami == "" { @@ -63,7 +64,7 @@ func TestAccRDSCustomDBEngineVersion_sqlServer(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest", "source_image_id"}, }, }, }) @@ -85,7 +86,7 @@ func TestAccRDSCustomDBEngineVersion_sqlServerUpdate(t *testing.T) { rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) resourceName := "aws_rds_custom_db_engine_version.test" status := "pending-validation" - status2 := "inactive" + description2 := "inactive" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -110,14 +111,14 @@ func TestAccRDSCustomDBEngineVersion_sqlServerUpdate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest"}, + ImportStateVerifyIgnore: []string{"filename", "manifest_hash", "manifest", "source_image_id"}, }, { - Config: testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, status2), + Config: testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, description2), Check: resource.ComposeTestCheckFunc( testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), resource.TestCheckResourceAttr(resourceName, "engine_version", rName), - resource.TestCheckResourceAttr(resourceName, "status", status2), + resource.TestCheckResourceAttr(resourceName, "description", description2), ), }, }, @@ -131,6 +132,7 @@ func TestAccRDSCustomDBEngineVersion_oracle(t *testing.T) { } // Requires an existing Oracle installation media in S3 bucket owned (bucket must be in operating region) by operating account set as environmental variable + // Pre-requisite: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev.preparing.html key := "RDS_CUSTOM_ORACLE_S3_BUCKET" bucket := os.Getenv(key) if bucket == "" { @@ -176,6 +178,7 @@ func TestAccRDSCustomDBEngineVersion_manifestFile(t *testing.T) { } // Requires an existing Oracle installation media in S3 bucket owned (bucket must be in operating region) by operating account set as environmental variable + // Pre-requisite: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev.preparing.html key := "RDS_CUSTOM_ORACLE_S3_BUCKET" bucket := os.Getenv(key) if bucket == "" { @@ -328,14 +331,14 @@ resource "aws_ami_copy" "test" { } resource "aws_rds_custom_db_engine_version" "test" { - engine = "custom-sqlserver-se" - engine_version = %[1]q - image_id = aws_ami_copy.test.id + engine = "custom-sqlserver-se" + engine_version = %[1]q + source_image_id = aws_ami_copy.test.id } `, rName, ami) } -func testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, status string) string { +func testAccCustomDBEngineVersionConfig_sqlServerUpdate(rName, ami, description string) string { return fmt.Sprintf(` data "aws_region" "current" {} @@ -347,12 +350,12 @@ resource "aws_ami_copy" "test" { } resource "aws_rds_custom_db_engine_version" "test" { - engine = "custom-sqlserver-se" - engine_version = %[1]q - image_id = aws_ami_copy.test.id - status = %[3]q + description = %[3]q + engine = "custom-sqlserver-se" + engine_version = %[1]q + source_image_id = aws_ami_copy.test.id } -`, rName, ami, status) +`, rName, ami, description) } func testAccCustomDBEngineVersionConfig_oracle(rName, bucket string) string { diff --git a/website/docs/r/custom_db_engine_version.markdown b/website/docs/r/custom_db_engine_version.markdown new file mode 100644 index 00000000000..764f7d973e0 --- /dev/null +++ b/website/docs/r/custom_db_engine_version.markdown @@ -0,0 +1,145 @@ +--- +subcategory: "RDS (Relational Database)" +layout: "aws" +page_title: "AWS: aws_rds_custom_db_engine_version" +description: |- + Provides an custom engine version (CEV) resource for Amazon RDS Custom. +--- + +# Resource: aws_rds_custom_db_engine_version + +Provides an custom engine version (CEV) resource for Amazon RDS Custom. For additional information, see [Working with CEVs for RDS Custom for Oracle](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev.html) and [Working with CEVs for RDS Custom for SQL Server](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev-sqlserver.html) in the the [RDS User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html). + +## RDS Custom for Oracle Usage + +```terraform +resource "aws_kms_key" "example" { + description = "KMS symmetric key for RDS Custom for Oracle" +} + +resource "aws_rds_custom_db_engine_version" "example" { + database_installation_files_s3_bucket_name = "DOC-EXAMPLE-BUCKET" + database_installation_files_s3_prefix = "1915_GI/" + engine = "custom-oracle-ee-cdb" + engine_version = "19.cdb_cev1" + kms_key_id = aws_kms_key.example.arn + manifest = < Date: Tue, 5 Sep 2023 09:11:24 -0400 Subject: [PATCH 10/20] added changelog --- .changelog/33285.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/33285.txt diff --git a/.changelog/33285.txt b/.changelog/33285.txt new file mode 100644 index 00000000000..aca5632f2d8 --- /dev/null +++ b/.changelog/33285.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_rds_custom_db_engine_version: Custon Engine Version (CEV) for RDS Custom (supports RDS Custom for Oracle and RDS Custom for SQL Server) +``` \ No newline at end of file From d2755311a1da23a29e1d04cd167a863a9f5e0281 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 13:01:22 -0400 Subject: [PATCH 11/20] renamed docs file --- ...ion.markdown => rds_custom_db_engine_version.markdown} | 8 ++++++++ 1 file changed, 8 insertions(+) rename website/docs/r/{custom_db_engine_version.markdown => rds_custom_db_engine_version.markdown} (99%) diff --git a/website/docs/r/custom_db_engine_version.markdown b/website/docs/r/rds_custom_db_engine_version.markdown similarity index 99% rename from website/docs/r/custom_db_engine_version.markdown rename to website/docs/r/rds_custom_db_engine_version.markdown index 764f7d973e0..15e4414af49 100644 --- a/website/docs/r/custom_db_engine_version.markdown +++ b/website/docs/r/rds_custom_db_engine_version.markdown @@ -13,6 +13,7 @@ Provides an custom engine version (CEV) resource for Amazon RDS Custom. For addi ## RDS Custom for Oracle Usage ```terraform + resource "aws_kms_key" "example" { description = "KMS symmetric key for RDS Custom for Oracle" } @@ -64,6 +65,7 @@ resource "aws_rds_custom_db_engine_version" "example" { ## RDS Custom for SQL Server Usage ```terraform + # CEV creation requires an AMI owned by the operator resource "aws_rds_custom_db_engine_version" "test" { engine = "custom-sqlserver-se" @@ -76,6 +78,7 @@ resource "aws_rds_custom_db_engine_version" "test" { ## RDS Custom for SQL Server Usage with AMI from another region ```terraform + resource "aws_ami_copy" "example" { name = "sqlserver-se-2019-15.00.4249.2" description = "A copy of ami-xxxxxxxx" @@ -88,6 +91,7 @@ resource "aws_rds_custom_db_engine_version" "test" { engine_version = "15.00.4249.2.cev-1" source_image_id = aws_ami_copy.example.id } + ``` ## Argument Reference @@ -132,14 +136,18 @@ This resource exports the following attributes in addition to the arguments abov In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: ```terraform + import { to = aws_rds_custom_db_engine_version.example id = "custom-oracle-ee-cdb:19.cdb_cev1" } + ``` Using `terraform import`, import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: ```console + + % terraform import aws_rds_custom_db_engine_version.example custom-oracle-ee-cdb:19.cdb_cev1 ``` From dc86de656408033783485b5963281a8ccea78a86 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 13:17:53 -0400 Subject: [PATCH 12/20] fix lint: Regex Cache --- internal/service/rds/custom_db_engine_version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index bb9ae6b18d7..e7e0f4529fc 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -9,10 +9,10 @@ import ( "fmt" "log" "os" - "regexp" "strings" "time" + "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/rds" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" @@ -83,7 +83,7 @@ func ResourceCustomDBEngineVersion() *schema.Resource { Required: true, ForceNew: true, ValidateFunc: validation.All( - validation.StringMatch(regexp.MustCompile(fmt.Sprintf(`^%s.*$`, InstanceEngineCustomPrefix)), fmt.Sprintf("must begin with %s", InstanceEngineCustomPrefix)), + validation.StringMatch(regexache.MustCompile(fmt.Sprintf(`^%s.*$`, InstanceEngineCustomPrefix)), fmt.Sprintf("must begin with %s", InstanceEngineCustomPrefix)), validation.StringLenBetween(1, 35), ), }, From 2bfca0b20ab1f4718c7b0021949672c1a85b26de Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 13:20:54 -0400 Subject: [PATCH 13/20] lint-fix null output checking --- internal/service/rds/custom_db_engine_version.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/service/rds/custom_db_engine_version.go b/internal/service/rds/custom_db_engine_version.go index e7e0f4529fc..42845da7ce2 100644 --- a/internal/service/rds/custom_db_engine_version.go +++ b/internal/service/rds/custom_db_engine_version.go @@ -254,9 +254,7 @@ func resourceCustomDBEngineVersionRead(ctx context.Context, d *schema.ResourceDa d.Set("engine", out.Engine) d.Set("engine_version", out.EngineVersion) d.Set("image_id", out.Image.ImageId) - if out.KMSKeyId != nil { - d.Set("kms_key_id", out.KMSKeyId) - } + d.Set("kms_key_id", out.KMSKeyId) d.Set("major_engine_version", out.MajorEngineVersion) d.Set("manifest_computed", out.CustomDBEngineVersionManifest) d.Set("status", out.Status) From 3cacd813cd84ddd9f8c935f5d876c2bbc725cedf Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 13:39:29 -0400 Subject: [PATCH 14/20] removed generated html.markdown --- ...rds_custom_db_engine_version.html.markdown | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 website/docs/r/rds_custom_db_engine_version.html.markdown diff --git a/website/docs/r/rds_custom_db_engine_version.html.markdown b/website/docs/r/rds_custom_db_engine_version.html.markdown deleted file mode 100644 index ce86478689c..00000000000 --- a/website/docs/r/rds_custom_db_engine_version.html.markdown +++ /dev/null @@ -1,71 +0,0 @@ ---- -subcategory: "RDS (Relational Database)" -layout: "aws" -page_title: "AWS: aws_rds_custom_db_engine_version" -description: |- - Terraform resource for managing an AWS RDS (Relational Database) Custom DB Engine Version. ---- -` -# Resource: aws_rds_custom_db_engine_version - -Terraform resource for managing an AWS RDS (Relational Database) Custom DB Engine Version. - -## Example Usage - -### Basic Usage - -```terraform -resource "aws_rds_custom_db_engine_version" "example" { -} -``` - -## Argument Reference - -The following arguments are required: - -* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. - -The following arguments are optional: - -* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. -* `tags` - (Optional) A map of tags assigned to the WorkSpaces Connection Alias. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. - -## Attribute Reference - -This resource exports the following attributes in addition to the arguments above: - -* `arn` - ARN of the Custom DB Engine Version. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. -* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. -* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). - -## Timeouts - -[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): - -* `create` - (Default `60m`) -* `update` - (Default `180m`) -* `delete` - (Default `90m`) - -## Import - -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import RDS (Relational Database) Custom DB Engine Version using the `example_id_arg`. For example: - -```terraform -import { - to = aws_rds_custom_db_engine_version.example - id = "custom_db_engine_version-id-12345678" -} -``` - -Using `terraform import`, import RDS (Relational Database) Custom DB Engine Version using the `example_id_arg`. For example: - -```console -% terraform import aws_rds_custom_db_engine_version.example custom_db_engine_version-id-12345678 -``` From 571ae6ee77c4e7c4310dca2fa806764f828c9993 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 13:42:26 -0400 Subject: [PATCH 15/20] lint-fix: website spacing --- .../docs/r/rds_custom_db_engine_version.markdown | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/website/docs/r/rds_custom_db_engine_version.markdown b/website/docs/r/rds_custom_db_engine_version.markdown index 15e4414af49..1b7fb7973d4 100644 --- a/website/docs/r/rds_custom_db_engine_version.markdown +++ b/website/docs/r/rds_custom_db_engine_version.markdown @@ -10,10 +10,11 @@ description: |- Provides an custom engine version (CEV) resource for Amazon RDS Custom. For additional information, see [Working with CEVs for RDS Custom for Oracle](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev.html) and [Working with CEVs for RDS Custom for SQL Server](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/custom-cev-sqlserver.html) in the the [RDS User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html). -## RDS Custom for Oracle Usage +## Example Usage -```terraform +### RDS Custom for Oracle Usage +```terraform resource "aws_kms_key" "example" { description = "KMS symmetric key for RDS Custom for Oracle" } @@ -35,10 +36,9 @@ resource "aws_rds_custom_db_engine_version" "example" { Key = "value" } } - ``` -## RDS Custom for Oracle External Manifest Usage +### RDS Custom for Oracle External Manifest Usage ```terraform resource "aws_kms_key" "example" { @@ -59,13 +59,11 @@ resource "aws_rds_custom_db_engine_version" "example" { Key = "value" } } - ``` -## RDS Custom for SQL Server Usage +### RDS Custom for SQL Server Usage ```terraform - # CEV creation requires an AMI owned by the operator resource "aws_rds_custom_db_engine_version" "test" { engine = "custom-sqlserver-se" @@ -91,7 +89,6 @@ resource "aws_rds_custom_db_engine_version" "test" { engine_version = "15.00.4249.2.cev-1" source_image_id = aws_ami_copy.example.id } - ``` ## Argument Reference From e8ff542af8b5065494ba3b5f1c93ae7e02e5268c Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Tue, 5 Sep 2023 14:08:13 -0400 Subject: [PATCH 16/20] added tags testing --- .../rds/custom_db_engine_version_test.go | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/internal/service/rds/custom_db_engine_version_test.go b/internal/service/rds/custom_db_engine_version_test.go index 978f12be2a8..c3a7e9d3cb5 100644 --- a/internal/service/rds/custom_db_engine_version_test.go +++ b/internal/service/rds/custom_db_engine_version_test.go @@ -218,6 +218,44 @@ func TestAccRDSCustomDBEngineVersion_manifestFile(t *testing.T) { }) } +func TestAccRDSCustomDBEngineVersion_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + // Requires an existing Windows SQL Server AMI owned by operating account set as environmental variable + key := "RDS_CUSTOM_WINDOWS_SQLSERVER_AMI" + ami := os.Getenv(key) + if ami == "" { + t.Skipf("Environment variable %s is not set", key) + } + var customdbengineversion rds.DBEngineVersion + rName := fmt.Sprintf("%s%s%d", "15.00.4249.2.", acctest.ResourcePrefix, sdkacctest.RandIntRange(100, 999)) + resourceName := "aws_rds_custom_db_engine_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, rds.EndpointsID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomDBEngineVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomDBEngineVersionConfig_tags(rName, ami, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomDBEngineVersionExists(ctx, resourceName, &customdbengineversion), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + func TestAccRDSCustomDBEngineVersion_disappears(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -394,3 +432,26 @@ resource "aws_rds_custom_db_engine_version" "test" { } `, rName, bucket, filename) } + +func testAccCustomDBEngineVersionConfig_tags(rName, ami, key, value string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +# Copy the Amazon AMI for Windows SQL Server, CEV creation requires an AMI owned by the operator +resource "aws_ami_copy" "test" { + name = %[1]q + source_ami_id = %[2]q + source_ami_region = data.aws_region.current.name +} + +resource "aws_rds_custom_db_engine_version" "test" { + engine = "custom-sqlserver-se" + engine_version = %[1]q + source_image_id = aws_ami_copy.test.id + + tags = { + %[3]q = %[4]q + } +} +`, rName, ami, key, value) +} From bb5ca414af1bd57db24d7a180a9d6c8795ec51e3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Sep 2023 10:59:32 -0400 Subject: [PATCH 17/20] Tweak CHANGELOG entry. --- .changelog/33285.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/33285.txt b/.changelog/33285.txt index aca5632f2d8..2320fd04668 100644 --- a/.changelog/33285.txt +++ b/.changelog/33285.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -aws_rds_custom_db_engine_version: Custon Engine Version (CEV) for RDS Custom (supports RDS Custom for Oracle and RDS Custom for SQL Server) -``` \ No newline at end of file +aws_rds_custom_db_engine_version +``` From 2ee2a4f267b170d01f41b5d6844690e2e798aa6f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Sep 2023 11:00:48 -0400 Subject: [PATCH 18/20] Tweak documentation. --- website/docs/r/rds_custom_db_engine_version.markdown | 4 ---- 1 file changed, 4 deletions(-) diff --git a/website/docs/r/rds_custom_db_engine_version.markdown b/website/docs/r/rds_custom_db_engine_version.markdown index 1b7fb7973d4..22850d7a8cb 100644 --- a/website/docs/r/rds_custom_db_engine_version.markdown +++ b/website/docs/r/rds_custom_db_engine_version.markdown @@ -133,18 +133,14 @@ This resource exports the following attributes in addition to the arguments abov In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: ```terraform - import { to = aws_rds_custom_db_engine_version.example id = "custom-oracle-ee-cdb:19.cdb_cev1" } - ``` Using `terraform import`, import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: ```console - - % terraform import aws_rds_custom_db_engine_version.example custom-oracle-ee-cdb:19.cdb_cev1 ``` From 4eca3c76e56f400c718b6ed6345f1fa11484ed97 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 12 Sep 2023 11:45:23 -0400 Subject: [PATCH 19/20] Update website/docs/r/rds_custom_db_engine_version.markdown Co-authored-by: Dave DeRicco <30156588+ddericco@users.noreply.github.com> --- website/docs/r/rds_custom_db_engine_version.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/rds_custom_db_engine_version.markdown b/website/docs/r/rds_custom_db_engine_version.markdown index 22850d7a8cb..d29fbde7e28 100644 --- a/website/docs/r/rds_custom_db_engine_version.markdown +++ b/website/docs/r/rds_custom_db_engine_version.markdown @@ -130,7 +130,7 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import custom engine versions for Amazon RDS custom using the `engine` and `engine_version` separated by a colon (`:`). For example: ```terraform import { From 748acb6688ce5d69bc87a2e409bba81e4a9a0fd9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 12 Sep 2023 11:45:30 -0400 Subject: [PATCH 20/20] Update website/docs/r/rds_custom_db_engine_version.markdown Co-authored-by: Dave DeRicco <30156588+ddericco@users.noreply.github.com> --- website/docs/r/rds_custom_db_engine_version.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/rds_custom_db_engine_version.markdown b/website/docs/r/rds_custom_db_engine_version.markdown index d29fbde7e28..8430e6c91dd 100644 --- a/website/docs/r/rds_custom_db_engine_version.markdown +++ b/website/docs/r/rds_custom_db_engine_version.markdown @@ -139,7 +139,7 @@ import { } ``` -Using `terraform import`, import Amazon Connect Contact Flow Modules using the `engine` and `engine_version` separated by a colon (`:`). For example: +Using `terraform import`, import custom engine versions for Amazon RDS custom using the `engine` and `engine_version` separated by a colon (`:`). For example: ```console % terraform import aws_rds_custom_db_engine_version.example custom-oracle-ee-cdb:19.cdb_cev1