diff --git a/.changelog/31803.txt b/.changelog/31803.txt new file mode 100644 index 000000000000..88d9e8aa57f6 --- /dev/null +++ b/.changelog/31803.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_finspace_kx_database +``` diff --git a/internal/service/finspace/kx_database.go b/internal/service/finspace/kx_database.go new file mode 100644 index 000000000000..decb48eb2ad9 --- /dev/null +++ b/internal/service/finspace/kx_database.go @@ -0,0 +1,224 @@ +package finspace + +import ( + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/finspace" + "github.com/aws/aws-sdk-go-v2/service/finspace/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "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/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + 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" +) + +// @SDKResource("aws_finspace_kx_database", name="Kx Database") +// @Tags(identifierAttribute="arn") +func ResourceKxDatabase() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceKxDatabaseCreate, + ReadWithoutTimeout: resourceKxDatabaseRead, + UpdateWithoutTimeout: resourceKxDatabaseUpdate, + DeleteWithoutTimeout: resourceKxDatabaseDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "created_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "environment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 32), + }, + "last_modified_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 63), + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameKxDatabase = "Kx Database" + + kxDatabaseIDPartCount = 2 +) + +func resourceKxDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).FinSpaceClient() + + in := &finspace.CreateKxDatabaseInput{ + DatabaseName: aws.String(d.Get("name").(string)), + EnvironmentId: aws.String(d.Get("environment_id").(string)), + ClientToken: aws.String(id.UniqueId()), + Tags: GetTagsIn(ctx), + } + + if v, ok := d.GetOk("description"); ok { + in.Description = aws.String(v.(string)) + } + + out, err := conn.CreateKxDatabase(ctx, in) + if err != nil { + return append(diags, create.DiagError(names.FinSpace, create.ErrActionCreating, ResNameKxDatabase, d.Get("name").(string), err)...) + } + + if out == nil || out.DatabaseArn == nil { + return append(diags, create.DiagError(names.FinSpace, create.ErrActionCreating, ResNameKxDatabase, d.Get("name").(string), errors.New("empty output"))...) + } + + idParts := []string{ + aws.ToString(out.EnvironmentId), + aws.ToString(out.DatabaseName), + } + id, err := flex.FlattenResourceId(idParts, kxDatabaseIDPartCount, false) + if err != nil { + return append(diags, create.DiagError(names.FinSpace, create.ErrActionFlatteningResourceId, ResNameKxDatabase, d.Get("name").(string), err)...) + } + + d.SetId(id) + + return append(diags, resourceKxDatabaseRead(ctx, d, meta)...) +} + +func resourceKxDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).FinSpaceClient() + + out, err := findKxDatabaseByID(ctx, conn, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] FinSpace KxDatabase (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return append(diags, create.DiagError(names.FinSpace, create.ErrActionReading, ResNameKxDatabase, d.Id(), err)...) + } + + d.Set("arn", out.DatabaseArn) + d.Set("name", out.DatabaseName) + d.Set("environment_id", out.EnvironmentId) + d.Set("description", out.Description) + d.Set("created_timestamp", out.CreatedTimestamp.String()) + d.Set("last_modified_timestamp", out.LastModifiedTimestamp.String()) + + return diags +} + +func resourceKxDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).FinSpaceClient() + + if d.HasChanges("description") { + in := &finspace.UpdateKxDatabaseInput{ + EnvironmentId: aws.String(d.Get("environment_id").(string)), + DatabaseName: aws.String(d.Get("name").(string)), + Description: aws.String(d.Get("description").(string)), + } + + _, err := conn.UpdateKxDatabase(ctx, in) + if err != nil { + return append(diags, create.DiagError(names.FinSpace, create.ErrActionUpdating, ResNameKxDatabase, d.Id(), err)...) + } + } + + return append(diags, resourceKxDatabaseRead(ctx, d, meta)...) +} + +func resourceKxDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).FinSpaceClient() + + log.Printf("[INFO] Deleting FinSpace KxDatabase %s", d.Id()) + + _, err := conn.DeleteKxDatabase(ctx, &finspace.DeleteKxDatabaseInput{ + EnvironmentId: aws.String(d.Get("environment_id").(string)), + DatabaseName: aws.String(d.Get("name").(string)), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return diags + } + + return append(diags, create.DiagError(names.FinSpace, create.ErrActionDeleting, ResNameKxDatabase, d.Id(), err)...) + } + + return diags +} + +func findKxDatabaseByID(ctx context.Context, conn *finspace.Client, id string) (*finspace.GetKxDatabaseOutput, error) { + parts, err := flex.ExpandResourceId(id, kxDatabaseIDPartCount, false) + if err != nil { + return nil, err + } + + in := &finspace.GetKxDatabaseInput{ + EnvironmentId: aws.String(parts[0]), + DatabaseName: aws.String(parts[1]), + } + + out, err := conn.GetKxDatabase(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.DatabaseArn == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} diff --git a/internal/service/finspace/kx_database_test.go b/internal/service/finspace/kx_database_test.go new file mode 100644 index 000000000000..5dc02834ac0c --- /dev/null +++ b/internal/service/finspace/kx_database_test.go @@ -0,0 +1,293 @@ +package finspace_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/finspace" + "github.com/aws/aws-sdk-go-v2/service/finspace/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" + tffinspace "github.com/hashicorp/terraform-provider-aws/internal/service/finspace" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccFinSpaceKxDatabase_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var kxdatabase finspace.GetKxDatabaseOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_finspace_kx_database.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, finspace.ServiceID) + }, + ErrorCheck: acctest.ErrorCheck(t, finspace.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKxDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKxDatabaseConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccFinSpaceKxDatabase_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var kxdatabase finspace.GetKxDatabaseOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_finspace_kx_database.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, finspace.ServiceID) + }, + ErrorCheck: acctest.ErrorCheck(t, finspace.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKxDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKxDatabaseConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tffinspace.ResourceKxDatabase(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccFinSpaceKxDatabase_description(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var kxdatabase finspace.GetKxDatabaseOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_finspace_kx_database.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, finspace.ServiceID) + }, + ErrorCheck: acctest.ErrorCheck(t, finspace.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKxDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKxDatabaseConfig_description(rName, "description 1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "description", "description 1"), + ), + }, + { + Config: testAccKxDatabaseConfig_description(rName, "description 2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "description", "description 2"), + ), + }, + }, + }) +} + +func TestAccFinSpaceKxDatabase_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var kxdatabase finspace.GetKxDatabaseOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_finspace_kx_database.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, finspace.ServiceID) + }, + ErrorCheck: acctest.ErrorCheck(t, finspace.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKxDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKxDatabaseConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + Config: testAccKxDatabaseConfig_tags2(rName, "key1", "value1", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccKxDatabaseConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKxDatabaseExists(ctx, resourceName, &kxdatabase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckKxDatabaseDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).FinSpaceClient() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_finspace_kx_database" { + continue + } + + input := &finspace.GetKxDatabaseInput{ + DatabaseName: aws.String(rs.Primary.Attributes["name"]), + EnvironmentId: aws.String(rs.Primary.Attributes["environment_id"]), + } + _, err := conn.GetKxDatabase(ctx, input) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.FinSpace, create.ErrActionCheckingDestroyed, tffinspace.ResNameKxDatabase, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckKxDatabaseExists(ctx context.Context, name string, kxdatabase *finspace.GetKxDatabaseOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.FinSpace, create.ErrActionCheckingExistence, tffinspace.ResNameKxDatabase, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.FinSpace, create.ErrActionCheckingExistence, tffinspace.ResNameKxDatabase, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).FinSpaceClient() + resp, err := conn.GetKxDatabase(ctx, &finspace.GetKxDatabaseInput{ + DatabaseName: aws.String(rs.Primary.Attributes["name"]), + EnvironmentId: aws.String(rs.Primary.Attributes["environment_id"]), + }) + + if err != nil { + return create.Error(names.FinSpace, create.ErrActionCheckingExistence, tffinspace.ResNameKxDatabase, rs.Primary.ID, err) + } + + *kxdatabase = *resp + + return nil + } +} + +func testAccKxDatabaseConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + deletion_window_in_days = 7 +} + +resource "aws_finspace_kx_environment" "test" { + name = %[1]q + kms_key_id = aws_kms_key.test.arn +} +`, rName) +} + +func testAccKxDatabaseConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccKxDatabaseConfigBase(rName), + fmt.Sprintf(` +resource "aws_finspace_kx_database" "test" { + name = %[1]q + environment_id = aws_finspace_kx_environment.test.id +} +`, rName)) +} + +func testAccKxDatabaseConfig_description(rName, description string) string { + return acctest.ConfigCompose( + testAccKxDatabaseConfigBase(rName), + fmt.Sprintf(` +resource "aws_finspace_kx_database" "test" { + name = %[1]q + environment_id = aws_finspace_kx_environment.test.id + description = %[2]q +} +`, rName, description)) +} + +func testAccKxDatabaseConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose( + testAccKxDatabaseConfigBase(rName), + fmt.Sprintf(` +resource "aws_finspace_kx_database" "test" { + name = %[1]q + environment_id = aws_finspace_kx_environment.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccKxDatabaseConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose( + testAccKxDatabaseConfigBase(rName), + fmt.Sprintf(` +resource "aws_finspace_kx_database" "test" { + name = %[1]q + environment_id = aws_finspace_kx_environment.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/internal/service/finspace/service_package_gen.go b/internal/service/finspace/service_package_gen.go index 8d807f4fd5b0..58b7cccc7647 100644 --- a/internal/service/finspace/service_package_gen.go +++ b/internal/service/finspace/service_package_gen.go @@ -25,6 +25,14 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ + { + Factory: ResourceKxDatabase, + TypeName: "aws_finspace_kx_database", + Name: "Kx Database", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, { Factory: ResourceKxEnvironment, TypeName: "aws_finspace_kx_environment", diff --git a/website/docs/r/finspace_kx_database.html.markdown b/website/docs/r/finspace_kx_database.html.markdown new file mode 100644 index 000000000000..a9458eea0e69 --- /dev/null +++ b/website/docs/r/finspace_kx_database.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "FinSpace" +layout: "aws" +page_title: "AWS: aws_finspace_kx_database" +description: |- + Terraform resource for managing an AWS FinSpace Kx Database. +--- + +# Resource: aws_finspace_kx_database + +Terraform resource for managing an AWS FinSpace Kx Database. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_kms_key" "example" { + description = "Example KMS Key" + deletion_window_in_days = 7 +} + +resource "aws_finspace_kx_environment" "example" { + name = "my-tf-kx-environment" + kms_key_id = aws_kms_key.example.arn +} + +resource "aws_finspace_kx_database" "example" { + environment_id = aws_finspace_kx_environment.example.id + name = "my-tf-kx-database" + description = "Example database description" +} +``` + +## Argument Reference + +The following arguments are required: + +* `environment_id` - (Required) Unique identifier for the KX environment. +* `name` - (Required) Name of the KX database. + +The following arguments are optional: + +* `description` - (Optional) Description of the KX database. +* `tags` - (Optional) Key-value mapping of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Amazon Resource Name (ARN) identifier of the KX database. +* `created_timestamp` - Timestamp at which the databse is created in FinSpace. Value determined as epoch time in seconds. For example, the value for Monday, November 1, 2021 12:00:00 PM UTC is specified as 1635768000. +* `id` - A comma-delimited string joining environment ID and database name. +* `last_modified_timestamp` - Last timestamp at which the database was updated in FinSpace. Value determined as epoch time in seconds. For example, the value for Monday, November 1, 2021 12:00:00 PM UTC is specified as 1635768000. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +An AWS FinSpace Kx Database can be imported using the `id` (environment ID and database name, comma-delimited), e.g., + +``` +$ terraform import aws_finspace_kx_database.example n3ceo7wqxoxcti5tujqwzs,my-tf-kx-database +```