diff --git a/.changelog/24941.txt b/.changelog/24941.txt new file mode 100644 index 000000000000..b121bbce1a4b --- /dev/null +++ b/.changelog/24941.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_appintegrations_data_integration +``` \ No newline at end of file diff --git a/internal/service/appintegrations/data_integration.go b/internal/service/appintegrations/data_integration.go new file mode 100644 index 000000000000..6d58524ad7e4 --- /dev/null +++ b/internal/service/appintegrations/data_integration.go @@ -0,0 +1,226 @@ +package appintegrations + +import ( + "context" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appintegrationsservice" + "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/id" + "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" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_appintegrations_data_integration", name="Data Integration") +// @Tags(identifierAttribute="arn") +func ResourceDataIntegration() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceDataIntegrationCreate, + ReadWithoutTimeout: resourceDataIntegrationRead, + UpdateWithoutTimeout: resourceDataIntegrationUpdate, + DeleteWithoutTimeout: resourceDataIntegrationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "kms_key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 255), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9\/\._\-]+$`), "should be not be more than 255 alphanumeric, forward slashes, dots, underscores, or hyphen characters"), + ), + }, + "schedule_config": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "first_execution_from": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "object": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 255), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9\/\._\-]+$`), "should be not be more than 255 alphanumeric, forward slashes, dots, underscores, or hyphen characters"), + ), + }, + "schedule_expression": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "source_uri": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 1000), + validation.StringMatch(regexp.MustCompile(`^\w+\:\/\/\w+\/[\w/!@#+=.-]+$`), "should be a valid source uri"), + ), + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceDataIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AppIntegrationsConn() + + name := d.Get("name").(string) + input := &appintegrationsservice.CreateDataIntegrationInput{ + ClientToken: aws.String(id.UniqueId()), + KmsKey: aws.String(d.Get("kms_key").(string)), + Name: aws.String(name), + ScheduleConfig: expandScheduleConfig(d.Get("schedule_config").([]interface{})), + SourceURI: aws.String(d.Get("source_uri").(string)), + Tags: GetTagsIn(ctx), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + output, err := conn.CreateDataIntegrationWithContext(ctx, input) + + if err != nil { + return diag.Errorf("creating AppIntegrations Data Integration (%s): %s", name, err) + } + + d.SetId(aws.StringValue(output.Id)) + + return resourceDataIntegrationRead(ctx, d, meta) +} + +func resourceDataIntegrationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AppIntegrationsConn() + + output, err := conn.GetDataIntegrationWithContext(ctx, &appintegrationsservice.GetDataIntegrationInput{ + Identifier: aws.String(d.Id()), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, appintegrationsservice.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] AppIntegrations Data Integration (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading AppIntegrations Data Integration (%s): %s", d.Id(), err) + } + + d.Set("arn", output.Arn) + d.Set("description", output.Description) + d.Set("kms_key", output.KmsKey) + d.Set("name", output.Name) + if err := d.Set("schedule_config", flattenScheduleConfig(output.ScheduleConfiguration)); err != nil { + return diag.Errorf("schedule_config tags: %s", err) + } + d.Set("source_uri", output.SourceURI) + + return nil +} + +func resourceDataIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AppIntegrationsConn() + + if d.HasChanges("description", "name") { + _, err := conn.UpdateDataIntegrationWithContext(ctx, &appintegrationsservice.UpdateDataIntegrationInput{ + Description: aws.String(d.Get("description").(string)), + Identifier: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + }) + + if err != nil { + return diag.Errorf("updating AppIntegrations Data Integration (%s): %s", d.Id(), err) + } + } + + return resourceDataIntegrationRead(ctx, d, meta) +} + +func resourceDataIntegrationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AppIntegrationsConn() + + _, err := conn.DeleteDataIntegrationWithContext(ctx, &appintegrationsservice.DeleteDataIntegrationInput{ + DataIntegrationIdentifier: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("deleting AppIntegrations Data Integration (%s): %s", d.Id(), err) + } + + return nil +} + +func expandScheduleConfig(scheduleConfig []interface{}) *appintegrationsservice.ScheduleConfiguration { + if len(scheduleConfig) == 0 || scheduleConfig[0] == nil { + return nil + } + + tfMap, ok := scheduleConfig[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &appintegrationsservice.ScheduleConfiguration{ + FirstExecutionFrom: aws.String(tfMap["first_execution_from"].(string)), + Object: aws.String(tfMap["object"].(string)), + ScheduleExpression: aws.String(tfMap["schedule_expression"].(string)), + } + + return result +} + +func flattenScheduleConfig(scheduleConfig *appintegrationsservice.ScheduleConfiguration) []interface{} { + if scheduleConfig == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "first_execution_from": aws.StringValue(scheduleConfig.FirstExecutionFrom), + "object": aws.StringValue(scheduleConfig.Object), + "schedule_expression": aws.StringValue(scheduleConfig.ScheduleExpression), + } + + return []interface{}{values} +} diff --git a/internal/service/appintegrations/data_integration_test.go b/internal/service/appintegrations/data_integration_test.go new file mode 100644 index 000000000000..2e76c5091017 --- /dev/null +++ b/internal/service/appintegrations/data_integration_test.go @@ -0,0 +1,370 @@ +package appintegrations_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appintegrationsservice" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +func TestAccAppIntegrationsDataIntegration_basic(t *testing.T) { + ctx := acctest.Context(t) + var dataIntegration appintegrationsservice.GetDataIntegrationOutput + + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + description := "example description" + firstExecutionFrom := "1439788442681" + + resourceName := "aws_appintegrations_data_integration.test" + + key := "DATA_INTEGRATION_SOURCE_URI" + sourceUri := os.Getenv(key) + if sourceUri == "" { + t.Skip("Environment variable DATA_INTEGRATION_SOURCE_URI is not set") + // sourceUri of the form Salesforce://AppFlow/ + // sourceUri = "Salesforce://AppFlow/test" + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, appintegrationsservice.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDataIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDataIntegrationConfig_basic(rName, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttrPair(resourceName, "kms_key", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "source_uri", sourceUri), + resource.TestCheckResourceAttr(resourceName, "schedule_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule_config.0.first_execution_from", firstExecutionFrom), + resource.TestCheckResourceAttr(resourceName, "schedule_config.0.object", "Account"), + resource.TestCheckResourceAttr(resourceName, "schedule_config.0.schedule_expression", "rate(1 hour)"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAppIntegrationsDataIntegration_updateDescription(t *testing.T) { + ctx := acctest.Context(t) + var dataIntegration appintegrationsservice.GetDataIntegrationOutput + + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + originalDescription := "original description" + updatedDescription := "updated description" + firstExecutionFrom := "1439788442681" + + resourceName := "aws_appintegrations_data_integration.test" + + key := "DATA_INTEGRATION_SOURCE_URI" + sourceUri := os.Getenv(key) + if sourceUri == "" { + t.Skip("Environment variable DATA_INTEGRATION_SOURCE_URI is not set") + // sourceUri of the form Salesforce://AppFlow/ + // sourceUri = "Salesforce://AppFlow/test" + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, appintegrationsservice.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDataIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDataIntegrationConfig_basic(rName, originalDescription, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "description", originalDescription), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataIntegrationConfig_basic(rName, updatedDescription, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "description", updatedDescription), + ), + }, + }, + }) +} + +func TestAccAppIntegrationsDataIntegration_updateName(t *testing.T) { + ctx := acctest.Context(t) + var dataIntegration appintegrationsservice.GetDataIntegrationOutput + + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + description := "example description" + firstExecutionFrom := "1439788442681" + + resourceName := "aws_appintegrations_data_integration.test" + + key := "DATA_INTEGRATION_SOURCE_URI" + sourceUri := os.Getenv(key) + if sourceUri == "" { + t.Skip("Environment variable DATA_INTEGRATION_SOURCE_URI is not set") + // sourceUri of the form Salesforce://AppFlow/ + // sourceUri = "Salesforce://AppFlow/test" + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, appintegrationsservice.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDataIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDataIntegrationConfig_basic(rName, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataIntegrationConfig_basic(rName2, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + }, + }) +} + +func TestAccAppIntegrationsDataIntegration_updateTags(t *testing.T) { + ctx := acctest.Context(t) + var dataIntegration appintegrationsservice.GetDataIntegrationOutput + + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + description := "example description" + firstExecutionFrom := "1439788442681" + + resourceName := "aws_appintegrations_data_integration.test" + + key := "DATA_INTEGRATION_SOURCE_URI" + sourceUri := os.Getenv(key) + if sourceUri == "" { + t.Skip("Environment variable DATA_INTEGRATION_SOURCE_URI is not set") + // sourceUri of the form Salesforce://AppFlow/ + // sourceUri = "Salesforce://AppFlow/test" + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, appintegrationsservice.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDataIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDataIntegrationConfig_basic(rName, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataIntegrationConfig_tags(rName, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2a"), + ), + }, + { + Config: testAccDataIntegrationConfig_tagsUpdated(rName, description, sourceUri, firstExecutionFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataIntegrationExists(ctx, resourceName, &dataIntegration), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2b"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + ), + }, + }, + }) +} + +func testAccCheckDataIntegrationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).AppIntegrationsConn() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appintegrations_data_integration" { + continue + } + + input := &appintegrationsservice.GetDataIntegrationInput{ + Identifier: aws.String(rs.Primary.ID), + } + + resp, err := conn.GetDataIntegrationWithContext(ctx, input) + + if err == nil { + if aws.StringValue(resp.Id) == rs.Primary.ID { + return fmt.Errorf("Data Integration '%s' was not deleted properly", rs.Primary.ID) + } + } + } + + return nil + } +} + +func testAccCheckDataIntegrationExists(ctx context.Context, name string, dataIntegration *appintegrationsservice.GetDataIntegrationOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).AppIntegrationsConn() + input := &appintegrationsservice.GetDataIntegrationInput{ + Identifier: aws.String(rs.Primary.ID), + } + resp, err := conn.GetDataIntegrationWithContext(ctx, input) + + if err != nil { + return err + } + + *dataIntegration = *resp + + return nil + } +} + +func testAccDataIntegrationBaseConfig() string { + return ` +resource "aws_kms_key" "test" { + deletion_window_in_days = 7 + + policy = <