diff --git a/.changelog/19320.txt b/.changelog/19320.txt new file mode 100644 index 00000000000..c4cdbdca9d4 --- /dev/null +++ b/.changelog/19320.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_appconfig_configuration_profile +``` diff --git a/aws/provider.go b/aws/provider.go index 90236fbf4ec..29aa1e41d05 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -507,6 +507,7 @@ func Provider() *schema.Provider { "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_appconfig_application": resourceAwsAppconfigApplication(), + "aws_appconfig_configuration_profile": resourceAwsAppconfigConfigurationProfile(), "aws_appconfig_environment": resourceAwsAppconfigEnvironment(), "aws_appmesh_gateway_route": resourceAwsAppmeshGatewayRoute(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go new file mode 100644 index 00000000000..6a17f212012 --- /dev/null +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -0,0 +1,373 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsAppconfigConfigurationProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppconfigConfigurationProfileCreate, + Read: resourceAwsAppconfigConfigurationProfileRead, + Update: resourceAwsAppconfigConfigurationProfileUpdate, + Delete: resourceAwsAppconfigConfigurationProfileDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "application_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "configuration_profile_id": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + "location_uri": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 2048), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + }, + "retrieval_role_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + "validator": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 2, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.Any( + validation.StringIsJSON, + validateArn, + ), + DiffSuppressFunc: suppressEquivalentJsonDiffs, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appconfig.ValidatorType_Values(), false), + }, + }, + }, + }, + }, + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsAppconfigConfigurationProfileCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + appId := d.Get("application_id").(string) + name := d.Get("name").(string) + + input := &appconfig.CreateConfigurationProfileInput{ + ApplicationId: aws.String(appId), + LocationUri: aws.String(d.Get("location_uri").(string)), + Name: aws.String(name), + Tags: tags.IgnoreAws().AppconfigTags(), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("retrieval_role_arn"); ok { + input.RetrievalRoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("validator"); ok && v.(*schema.Set).Len() > 0 { + input.Validators = expandAppconfigValidators(v.(*schema.Set).List()) + } + + profile, err := conn.CreateConfigurationProfile(input) + + if err != nil { + return fmt.Errorf("error creating AppConfig Configuration Profile (%s) for Application (%s): %w", name, appId, err) + } + + if profile == nil { + return fmt.Errorf("error creating AppConfig Configuration Profile (%s) for Application (%s): empty response", name, appId) + } + + d.SetId(fmt.Sprintf("%s:%s", aws.StringValue(profile.Id), aws.StringValue(profile.ApplicationId))) + + return resourceAwsAppconfigConfigurationProfileRead(d, meta) +} + +func resourceAwsAppconfigConfigurationProfileRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(d.Id()) + + if err != nil { + return err + } + + input := &appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + } + + output, err := conn.GetConfigurationProfile(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] AppConfig Configuration Profile (%s) for Application (%s) not found, removing from state", confProfID, appID) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) + } + + if output == nil { + return fmt.Errorf("error getting AppConfig Configuration Profile (%s) for Application (%s): empty response", confProfID, appID) + } + + d.Set("application_id", output.ApplicationId) + d.Set("configuration_profile_id", output.Id) + d.Set("description", output.Description) + d.Set("location_uri", output.LocationUri) + d.Set("name", output.Name) + + d.Set("retrieval_role_arn", output.RetrievalRoleArn) + + if err := d.Set("validator", flattenAwsAppconfigValidators(output.Validators)); err != nil { + return fmt.Errorf("error setting validator: %w", err) + } + + arn := arn.ARN{ + AccountID: meta.(*AWSClient).accountid, + Partition: meta.(*AWSClient).partition, + Region: meta.(*AWSClient).region, + Resource: fmt.Sprintf("application/%s/configurationprofile/%s", appID, confProfID), + Service: "appconfig", + }.String() + + d.Set("arn", arn) + + tags, err := keyvaluetags.AppconfigListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for AppConfig Configuration Profile (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + + if d.HasChangesExcept("tags", "tags_all") { + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(d.Id()) + + if err != nil { + return err + } + + updateInput := &appconfig.UpdateConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + } + + if d.HasChange("description") { + updateInput.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("name") { + updateInput.Name = aws.String(d.Get("name").(string)) + } + + if d.HasChange("retrieval_role_arn") { + updateInput.RetrievalRoleArn = aws.String(d.Get("retrieval_role_arn").(string)) + } + + if d.HasChange("validator") { + updateInput.Validators = expandAppconfigValidators(d.Get("validator").(*schema.Set).List()) + } + + _, err = conn.UpdateConfigurationProfile(updateInput) + + if err != nil { + return fmt.Errorf("error updating AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := keyvaluetags.AppconfigUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating AppConfig Configuration Profile (%s) tags: %w", d.Get("arn").(string), err) + } + } + + return resourceAwsAppconfigConfigurationProfileRead(d, meta) +} + +func resourceAwsAppconfigConfigurationProfileDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(d.Id()) + + if err != nil { + return err + } + + input := &appconfig.DeleteConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + } + + _, err = conn.DeleteConfigurationProfile(input) + + if tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) + } + + return nil +} + +func resourceAwsAppconfigConfigurationProfileParseID(id string) (string, string, error) { + parts := strings.Split(id, ":") + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%q), expected configurationProfileID:applicationID", id) + } + + return parts[0], parts[1], nil +} + +func expandAppconfigValidator(tfMap map[string]interface{}) *appconfig.Validator { + if tfMap == nil { + return nil + } + + validator := &appconfig.Validator{} + + // AppConfig API supports empty content + if v, ok := tfMap["content"].(string); ok { + validator.Content = aws.String(v) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + validator.Type = aws.String(v) + } + + return validator +} + +func expandAppconfigValidators(tfList []interface{}) []*appconfig.Validator { + // AppConfig API requires a 0 length slice instead of a nil value + // when updating from N validators to 0/nil validators + validators := make([]*appconfig.Validator, 0) + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + validator := expandAppconfigValidator(tfMap) + + if validator == nil { + continue + } + + validators = append(validators, validator) + } + + return validators +} + +func flattenAwsAppconfigValidator(validator *appconfig.Validator) map[string]interface{} { + if validator == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := validator.Content; v != nil { + tfMap["content"] = aws.StringValue(v) + } + + if v := validator.Type; v != nil { + tfMap["type"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenAwsAppconfigValidators(validators []*appconfig.Validator) []interface{} { + if len(validators) == 0 { + return nil + } + + var tfList []interface{} + + for _, validator := range validators { + if validator == nil { + continue + } + + tfList = append(tfList, flattenAwsAppconfigValidator(validator)) + } + + return tfList +} diff --git a/aws/resource_aws_appconfig_configuration_profile_test.go b/aws/resource_aws_appconfig_configuration_profile_test.go new file mode 100644 index 00000000000..23d98c250e9 --- /dev/null +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -0,0 +1,568 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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" +) + +func TestAccAWSAppConfigConfigurationProfile_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + appResourceName := "aws_appconfig_application.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appconfig", regexp.MustCompile(`application/[a-z0-9]{4,7}/configurationprofile/[a-z0-9]{4,7}`)), + resource.TestCheckResourceAttrPair(resourceName, "application_id", appResourceName, "id"), + resource.TestMatchResourceAttr(resourceName, "configuration_profile_id", regexp.MustCompile(`[a-z0-9]{4,7}`)), + resource.TestCheckResourceAttr(resourceName, "location_uri", "hosted"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppconfigConfigurationProfile(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_Validators_JSON(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigValidator_JSON(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "validator.*", map[string]string{ + "type": appconfig.ValidatorTypeJsonSchema, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAppConfigConfigurationProfileConfigValidator_NoJSONContent(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "validator.*", map[string]string{ + "content": "", + "type": appconfig.ValidatorTypeJsonSchema, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Validator Removal + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_Validators_Lambda(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigValidator_Lambda(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "validator.*.content", "aws_lambda_function.test", "arn"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "validator.*", map[string]string{ + "type": appconfig.ValidatorTypeLambda, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Validator Removal + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_Validators_Multiple(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigValidator_Multiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "validator.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "validator.*", map[string]string{ + "content": "{\"$schema\":\"http://json-schema.org/draft-05/schema#\",\"description\":\"BasicFeatureToggle-1\",\"title\":\"$id$\"}", + "type": appconfig.ValidatorTypeJsonSchema, + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "validator.*.content", "aws_lambda_function.test", "arn"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "validator.*", map[string]string{ + "type": appconfig.ValidatorTypeLambda, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_updateName(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + rNameUpdated := acctest.RandomWithPrefix("tf-acc-test-update") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + Config: testAccAWSAppConfigConfigurationProfileConfigName(rNameUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_updateDescription(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + description := acctest.RandomWithPrefix("tf-acc-test-update") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileConfigDescription(rName, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAppConfigConfigurationProfileConfigDescription(rName, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_Tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_configuration_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigConfigurationProfileTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAppConfigConfigurationProfileTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSAppConfigConfigurationProfileTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAppConfigConfigurationProfileDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appconfigconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appconfig_configuration_profile" { + continue + } + + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(rs.Primary.ID) + + if err != nil { + return err + } + + input := &appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + } + + output, err := conn.GetConfigurationProfile(input) + + if tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error reading AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) + } + + if output != nil { + return fmt.Errorf("AppConfig Configuration Profile (%s) for Application (%s) still exists", confProfID, appID) + } + } + + return nil +} + +func testAccCheckAWSAppConfigConfigurationProfileExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Resource (%s) ID not set", resourceName) + } + + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).appconfigconn + + output, err := conn.GetConfigurationProfile(&appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + }) + + if err != nil { + return fmt.Errorf("error reading AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) + } + + if output == nil { + return fmt.Errorf("AppConfig Configuration Profile (%s) for Application (%s) not found", confProfID, appID) + } + + return nil + } +} + +func testAccAWSAppConfigConfigurationProfileConfigName(rName string) string { + return composeConfig( + testAccAWSAppConfigApplicationConfigName(rName), + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + application_id = aws_appconfig_application.test.id + name = %q + location_uri = "hosted" +} +`, rName)) +} + +func testAccAWSAppConfigConfigurationProfileConfigDescription(rName, description string) string { + return composeConfig( + testAccAWSAppConfigApplicationConfigDescription(rName, description), + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + application_id = aws_appconfig_application.test.id + name = %[1]q + description = %[2]q + location_uri = "hosted" +} +`, rName, description)) +} + +func testAccAWSAppConfigConfigurationProfileConfigValidator_JSON(rName string) string { + return composeConfig( + testAccAWSAppConfigApplicationConfigName(rName), + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + application_id = aws_appconfig_application.test.id + name = %q + location_uri = "hosted" + + validator { + content = jsonencode({ + "$schema" = "http://json-schema.org/draft-04/schema#" + title = "$id$" + description = "BasicFeatureToggle-1" + type = "object" + additionalProperties = false + patternProperties = { + "[^\\s]+$" = { + type = "boolean" + } + } + minProperties = 1 + }) + + type = "JSON_SCHEMA" + } +} +`, rName)) +} + +func testAccAWSAppConfigConfigurationProfileConfigValidator_NoJSONContent(rName string) string { + return composeConfig( + testAccAWSAppConfigApplicationConfigName(rName), + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + application_id = aws_appconfig_application.test.id + name = %q + location_uri = "hosted" + + validator { + type = "JSON_SCHEMA" + } +} +`, rName)) +} + +func testAccAWSAppConfigApplicationConfigLambdaBase(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "lambda" { + name = "%[1]s-lambda" + + assume_role_policy = <` or the Amazon Resource Name (ARN). For a parameter, specify either the parameter name in the format `ssm-parameter://` or the ARN. For an Amazon S3 object, specify the URI in the following format: `s3:///`. +* `name` - (Required) The name for the configuration profile. Must be between 1 and 64 characters in length. +* `description` - (Optional) The description of the configuration profile. Can be at most 1024 characters. +* `retrieval_role_arn` - (Optional) The ARN of an IAM role with permission to access the configuration at the specified `location_uri`. A retrieval role ARN is not required for configurations stored in the AWS AppConfig `hosted` configuration store. It is required for all other sources that store your configuration. +* `tags` - (Optional) A map of tags to assign to the resource. 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. +* `validator` - (Optional) A set of methods for validating the configuration. Maximum of 2. See [Validator](#validator) below for more details. + +### Validator + +The `validator` block supports the following: + +* `content` - (Optional, Required when `type` is `LAMBDA`) Either the JSON Schema content or the Amazon Resource Name (ARN) of an AWS Lambda function. +* `type` - (Optional) The type of validator. Valid values: `JSON_SCHEMA` and `LAMBDA`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the AppConfig Configuration Profile. +* `configuration_profile_id` - The configuration profile ID. +* `id` - The AppConfig configuration profile ID and application ID separated by a colon (`:`). +* `tags_all` - A 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). + +## Import + +AppConfig Configuration Profiles can be imported by using the configuration profile ID and application ID separated by a colon (`:`), e.g. + +``` +$ terraform import aws_appconfig_configuration_profile.example 71abcde:11xxxxx +```