From 4e0217c2e20c876d296fd4a56d1c4104873efccf Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 11 May 2021 22:07:31 +0900 Subject: [PATCH 01/12] feat: add aws_appconfig_configuration_profile --- aws/provider.go | 1 + ...rce_aws_appconfig_configuration_profile.go | 222 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 aws/resource_aws_appconfig_configuration_profile.go diff --git a/aws/provider.go b/aws/provider.go index 90236fbf4ec..48e60dbd52c 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -508,6 +508,7 @@ func Provider() *schema.Provider { "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_appconfig_application": resourceAwsAppconfigApplication(), "aws_appconfig_environment": resourceAwsAppconfigEnvironment(), + "aws_appconfig_configuration_profile": resourceAwsAppconfigConfigurationProfile(), "aws_appmesh_gateway_route": resourceAwsAppmeshGatewayRoute(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), "aws_appmesh_route": resourceAwsAppmeshRoute(), diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go new file mode 100644 index 00000000000..700fe6f0f61 --- /dev/null +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -0,0 +1,222 @@ +package aws + +import ( + "fmt" + "log" + "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/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: resourceAwsAppconfigConfigurationProfileImport, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + ), + }, + "application_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(4, 7), + ), + }, + "location_uri": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(0, 2048), + ), + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringLenBetween(0, 1024), + ), + }, + "retrieval_role_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringLenBetween(20, 2048), + ), + }, + // "validators": { + // Type: schema.TypeString, + // Optional: true, + // ValidateFunc: validation.All( + // validation.StringLenBetween(20, 2048), + // ), + // }, + "tags": tagsSchema(), + "arn": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsAppconfigConfigurationProfileCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + + input := &appconfig.CreateConfigurationProfileInput{ + Name: aws.String(d.Get("name").(string)), + Description: aws.String(d.Get("description").(string)), + LocationUri: aws.String(d.Get("location_uri").(string)), + RetrievalRoleArn: aws.String(d.Get("retrieval_role_arn").(string)), + ApplicationId: aws.String(d.Get("application_id").(string)), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppconfigTags(), + } + + profile, err := conn.CreateConfigurationProfile(input) + if err != nil { + return fmt.Errorf("Error creating AppConfig ConfigurationProfile: %s", err) + } + + d.SetId(aws.StringValue(profile.Id)) + + return resourceAwsAppconfigConfigurationProfileRead(d, meta) +} + +func resourceAwsAppconfigConfigurationProfileRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + appID := d.Get("application_id").(string) + + input := &appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(d.Id()), + } + + output, err := conn.GetConfigurationProfile(input) + + if !d.IsNewResource() && isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Appconfig ConfigurationProfile (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting AppConfig ConfigurationProfile (%s): %s", d.Id(), err) + } + + if output == nil { + return fmt.Errorf("error getting AppConfig ConfigurationProfile (%s): empty response", d.Id()) + } + + d.Set("name", output.Name) + d.Set("description", output.Description) + d.Set("application_id", output.ApplicationId) + d.Set("location_uri", output.LocationUri) + d.Set("retrieval_role_arn", output.RetrievalRoleArn) + + profileARN := arn.ARN{ + AccountID: meta.(*AWSClient).accountid, + Partition: meta.(*AWSClient).partition, + Region: meta.(*AWSClient).region, + Resource: fmt.Sprintf("application/%s/configurationprofile/%s", appID, d.Id()), + Service: "appconfig", + }.String() + d.Set("arn", profileARN) + + tags, err := keyvaluetags.AppconfigListTags(conn, profileARN) + if err != nil { + return fmt.Errorf("error getting tags for AppConfig ConfigurationProfile (%s): %s", d.Id(), err) + } + + if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + + updateInput := &appconfig.UpdateConfigurationProfileInput{ + ConfigurationProfileId: aws.String(d.Id()), + ApplicationId: aws.String(d.Get("application_id").(string)), + } + + 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.Name = aws.String(d.Get("retrieval_role_arn").(string)) + } + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + if err := keyvaluetags.AppconfigUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating AppConfig (%s) tags: %s", d.Id(), err) + } + } + + _, err := conn.UpdateConfigurationProfile(updateInput) + if err != nil { + return fmt.Errorf("error updating AppConfig ConfigurationProfile (%s): %s", d.Id(), err) + } + + return resourceAwsAppconfigConfigurationProfileRead(d, meta) +} + +func resourceAwsAppconfigConfigurationProfileDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + + input := &appconfig.DeleteConfigurationProfileInput{ + ConfigurationProfileId: aws.String(d.Id()), + ApplicationId: aws.String(d.Get("application_id").(string)), + } + + _, err := conn.DeleteConfigurationProfile(input) + + if isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Appconfig ConfigurationProfile (%s): %s", d.Id(), err) + } + + return nil +} + +func resourceAwsAppconfigConfigurationProfileImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 2 { + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'application-id/configurationprofile-id'", d.Id()) + } + + d.SetId(parts[1]) + d.Set("application_id", parts[0]) + + return []*schema.ResourceData{d}, nil +} From 4fde4995f204465b51e2e7173f2e78b7afa38294 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 11 May 2021 22:44:58 +0900 Subject: [PATCH 02/12] feat: support AppConfig Validators --- ...rce_aws_appconfig_configuration_profile.go | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go index 700fe6f0f61..c0fce741008 100644 --- a/aws/resource_aws_appconfig_configuration_profile.go +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -61,13 +61,29 @@ func resourceAwsAppconfigConfigurationProfile() *schema.Resource { validation.StringLenBetween(20, 2048), ), }, - // "validators": { - // Type: schema.TypeString, - // Optional: true, - // ValidateFunc: validation.All( - // validation.StringLenBetween(20, 2048), - // ), - // }, + "validators": { + Type: schema.TypeList, + Optional: true, + MaxItems: 2, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringLenBetween(0, 32768), + ), + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "JSON_SCHEMA", "LAMBDA", + }, false), + }, + }, + }, + }, "tags": tagsSchema(), "arn": { Type: schema.TypeString, @@ -86,6 +102,7 @@ func resourceAwsAppconfigConfigurationProfileCreate(d *schema.ResourceData, meta LocationUri: aws.String(d.Get("location_uri").(string)), RetrievalRoleArn: aws.String(d.Get("retrieval_role_arn").(string)), ApplicationId: aws.String(d.Get("application_id").(string)), + Validators: expandAppconfigValidators(d.Get("validators").([]interface{})), Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppconfigTags(), } @@ -99,6 +116,29 @@ func resourceAwsAppconfigConfigurationProfileCreate(d *schema.ResourceData, meta return resourceAwsAppconfigConfigurationProfileRead(d, meta) } +func expandAppconfigValidators(list []interface{}) []*appconfig.Validator { + validators := make([]*appconfig.Validator, len(list)) + for i, validatorInterface := range list { + m := validatorInterface.(map[string]interface{}) + validators[i] = &appconfig.Validator{ + Content: aws.String(m["content"].(string)), + Type: aws.String(m["type"].(string)), + } + } + return validators +} + +func flattenAwsAppconfigValidators(validators []*appconfig.Validator) []interface{} { + list := make([]interface{}, len(validators)) + for i, validator := range validators { + list[i] = map[string]interface{}{ + "content": aws.StringValue(validator.Content), + "type": aws.StringValue(validator.Type), + } + } + return list +} + func resourceAwsAppconfigConfigurationProfileRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).appconfigconn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig @@ -131,6 +171,7 @@ func resourceAwsAppconfigConfigurationProfileRead(d *schema.ResourceData, meta i d.Set("application_id", output.ApplicationId) d.Set("location_uri", output.LocationUri) d.Set("retrieval_role_arn", output.RetrievalRoleArn) + d.Set("validators", flattenAwsAppconfigValidators(output.Validators)) profileARN := arn.ARN{ AccountID: meta.(*AWSClient).accountid, @@ -180,6 +221,10 @@ func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, meta } } + if d.HasChange("validators") { + updateInput.Validators = expandAppconfigValidators(d.Get("validators").([]interface{})) + } + _, err := conn.UpdateConfigurationProfile(updateInput) if err != nil { return fmt.Errorf("error updating AppConfig ConfigurationProfile (%s): %s", d.Id(), err) From 94edc9e54bf9069ce11df63a08a5c307c886cd47 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 11 May 2021 22:53:50 +0900 Subject: [PATCH 03/12] docs: add a document of aws_appconfig_configuration_profile --- ...config_configuration_profile.html.markdown | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 website/docs/r/appconfig_configuration_profile.html.markdown diff --git a/website/docs/r/appconfig_configuration_profile.html.markdown b/website/docs/r/appconfig_configuration_profile.html.markdown new file mode 100644 index 00000000000..862e1648474 --- /dev/null +++ b/website/docs/r/appconfig_configuration_profile.html.markdown @@ -0,0 +1,67 @@ +--- +subcategory: "AppConfig" +layout: "aws" +page_title: "AWS: aws_appconfig_configuration_profile" +description: |- + Provides an AppConfig Configuration Profile resource. +--- + +# Resource: aws_appconfig_configuration_profile + +Provides an AppConfig Configuration Profile resource. + +## Example Usage + +### AppConfig Configuration Profile + +```hcl +resource "aws_appconfig_configuration_profile" "production" { + name = "test" + description = "test" + application_id = aws_appconfig_application.test.id + validators { + content = "arn:aws:lambda:us-east-1:111111111111:function:test" + type = "LAMBDA" + } +} + +resource "aws_appconfig_application" "test" { + name = "test" + description = "Test" + tags = { + Type = "Test" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +- `name` - (Required) The environment name. Must be between 1 and 64 characters in length. +- `application_id` - (Required) The application id. Must be between 4 and 7 characters in length. +- `description` - (Optional) The description of the environment. Can be at most 1024 characters. +- `location_uri` - (Optional) A URI to locate the configuration. +- `validators` - (Optional) A list of methods for validating the configuration. Detailed below. +- `retrieval_role_arn` - (Optional) The ARN of an IAM role with permission to access the configuration at the specified LocationUri. +- `tags` - (Optional) A map of tags to assign to the resource. + +### validator + +- `content` - (Optional) Either the JSON Schema content or the Amazon Resource Name (ARN) of an AWS Lambda function. +- `type` - (Optional) AWS AppConfig supports validators of type `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. +- `id` - The AppConfig Configuration Profile ID + +## Import + +`aws_appconfig_configuration_profile` can be imported by the Application ID and Configuration Profile ID, e.g. + +``` +$ terraform import aws_appconfig_configuration_profile.test 71abcde/11xxxxx +``` From a2d6abe3bad4c99e69360d3879c7d44a62d48ed1 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 07:20:57 +0900 Subject: [PATCH 04/12] test: add tests for aws_appconfig_configuration_profile --- ...ws_appconfig_configuration_profile_test.go | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 aws/resource_aws_appconfig_configuration_profile_test.go 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..a25999e52ff --- /dev/null +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -0,0 +1,258 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appconfig" + "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) { + var profile appconfig.GetConfigurationProfileOutput + profileName := acctest.RandomWithPrefix("tf-acc-test") + profileDesc := acctest.RandomWithPrefix("desc") + 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: testAccAWSAppConfigConfigurationProfile(profileName, profileDesc), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + resource.TestCheckResourceAttr(resourceName, "name", profileName), + testAccCheckAWSAppConfigConfigurationProfileARN(resourceName, &profile), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "description", profileDesc), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAppConfigConfigurationProfileImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_disappears(t *testing.T) { + var profile appconfig.GetConfigurationProfileOutput + + profileName := acctest.RandomWithPrefix("tf-acc-test") + profileDesc := acctest.RandomWithPrefix("desc") + 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: testAccAWSAppConfigConfigurationProfile(profileName, profileDesc), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + testAccCheckAWSAppConfigConfigurationProfileDisappears(&profile), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSAppConfigConfigurationProfile_Tags(t *testing.T) { + var profile appconfig.GetConfigurationProfileOutput + + 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, &profile), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAppConfigConfigurationProfileImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAppConfigConfigurationProfileTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + 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, &profile), + 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 + } + + input := &appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(rs.Primary.Attributes["application_id"]), + ConfigurationProfileId: aws.String(rs.Primary.ID), + } + + output, err := conn.GetConfigurationProfile(input) + + if isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + if output != nil { + return fmt.Errorf("AppConfig ConfigurationProfile (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAWSAppConfigConfigurationProfileDisappears(profile *appconfig.GetConfigurationProfileOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appconfigconn + + _, err := conn.DeleteConfigurationProfile(&appconfig.DeleteConfigurationProfileInput{ + ApplicationId: profile.ApplicationId, + ConfigurationProfileId: profile.Id, + }) + + return err + } +} + +func testAccCheckAWSAppConfigConfigurationProfileExists(resourceName string, profile *appconfig.GetConfigurationProfileOutput) 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) + } + + conn := testAccProvider.Meta().(*AWSClient).appconfigconn + + output, err := conn.GetConfigurationProfile(&appconfig.GetConfigurationProfileInput{ + ApplicationId: aws.String(rs.Primary.Attributes["application_id"]), + ConfigurationProfileId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *profile = *output + + return nil + } +} + +func testAccCheckAWSAppConfigConfigurationProfileARN(resourceName string, profile *appconfig.GetConfigurationProfileOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + return testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appconfig", fmt.Sprintf("application/%s/configurationprofile/%s", aws.StringValue(profile.ApplicationId), aws.StringValue(profile.Id)))(s) + } +} + +func testAccAWSAppConfigConfigurationProfile(profileName, profileDesc string) string { + appName := acctest.RandomWithPrefix("tf-acc-test") + appDesc := acctest.RandomWithPrefix("desc") + return testAccAWSAppConfigApplicationName(appName, appDesc) + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + name = %[1]q + description = %[2]q + application_id = aws_appconfig_application.test.id + validators { + type = "JSON_SCHEMA" + 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 + }) + } +} +`, profileName, profileDesc) +} + +func testAccAWSAppConfigConfigurationProfileTags1(rName, tagKey1, tagValue1 string) string { + return testAccAWSAppConfigApplicationTags1(rName, tagKey1, tagValue1) + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + name = %[1]q + application_id = aws_appconfig_application.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccAWSAppConfigConfigurationProfileTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return testAccAWSAppConfigApplicationTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2) + fmt.Sprintf(` +resource "aws_appconfig_configuration_profile" "test" { + name = %[1]q + application_id = aws_appconfig_application.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccAWSAppConfigConfigurationProfileImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not Found: %s", resourceName) + } + + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["application_id"], rs.Primary.ID), nil + } +} From 16e0a34871217880c8abd1f306483212f6a2f09b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 09:39:35 +0900 Subject: [PATCH 05/12] docs: fix document of aws_appconfig_configuration_profile --- website/docs/r/appconfig_configuration_profile.html.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/r/appconfig_configuration_profile.html.markdown b/website/docs/r/appconfig_configuration_profile.html.markdown index 862e1648474..01da1fef385 100644 --- a/website/docs/r/appconfig_configuration_profile.html.markdown +++ b/website/docs/r/appconfig_configuration_profile.html.markdown @@ -19,6 +19,7 @@ resource "aws_appconfig_configuration_profile" "production" { name = "test" description = "test" application_id = aws_appconfig_application.test.id + location_uri = "hosted" validators { content = "arn:aws:lambda:us-east-1:111111111111:function:test" type = "LAMBDA" @@ -39,9 +40,9 @@ resource "aws_appconfig_application" "test" { The following arguments are supported: - `name` - (Required) The environment name. Must be between 1 and 64 characters in length. -- `application_id` - (Required) The application id. Must be between 4 and 7 characters in length. +- `application_id` - (Required, Forces new resource) The application id. Must be between 4 and 7 characters in length. +- `location_uri` - (Required, Forces new resource) A URI to locate the configuration. - `description` - (Optional) The description of the environment. Can be at most 1024 characters. -- `location_uri` - (Optional) A URI to locate the configuration. - `validators` - (Optional) A list of methods for validating the configuration. Detailed below. - `retrieval_role_arn` - (Optional) The ARN of an IAM role with permission to access the configuration at the specified LocationUri. - `tags` - (Optional) A map of tags to assign to the resource. From 5e42c00bc2cc40074bd9e6ed24cf1f1b4ccce5ab Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 09:41:16 +0900 Subject: [PATCH 06/12] test: specify location_uri --- aws/resource_aws_appconfig_configuration_profile_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws/resource_aws_appconfig_configuration_profile_test.go b/aws/resource_aws_appconfig_configuration_profile_test.go index a25999e52ff..8b08b7130e7 100644 --- a/aws/resource_aws_appconfig_configuration_profile_test.go +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -199,6 +199,7 @@ resource "aws_appconfig_configuration_profile" "test" { name = %[1]q description = %[2]q application_id = aws_appconfig_application.test.id + location_uri = "hosted" validators { type = "JSON_SCHEMA" content = jsonencode({ @@ -224,6 +225,7 @@ func testAccAWSAppConfigConfigurationProfileTags1(rName, tagKey1, tagValue1 stri resource "aws_appconfig_configuration_profile" "test" { name = %[1]q application_id = aws_appconfig_application.test.id + location_uri = "hosted" tags = { %[2]q = %[3]q @@ -237,6 +239,7 @@ func testAccAWSAppConfigConfigurationProfileTags2(rName, tagKey1, tagValue1, tag resource "aws_appconfig_configuration_profile" "test" { name = %[1]q application_id = aws_appconfig_application.test.id + location_uri = "hosted" tags = { %[2]q = %[3]q From d5338d41244480fb3b9346fa065d78ba7ab124dd Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 09:42:45 +0900 Subject: [PATCH 07/12] docs: fix typo --- website/docs/r/appconfig_configuration_profile.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/appconfig_configuration_profile.html.markdown b/website/docs/r/appconfig_configuration_profile.html.markdown index 01da1fef385..c3974e546ee 100644 --- a/website/docs/r/appconfig_configuration_profile.html.markdown +++ b/website/docs/r/appconfig_configuration_profile.html.markdown @@ -39,10 +39,10 @@ resource "aws_appconfig_application" "test" { The following arguments are supported: -- `name` - (Required) The environment name. Must be between 1 and 64 characters in length. +- `name` - (Required) The configuration profile name. Must be between 1 and 64 characters in length. - `application_id` - (Required, Forces new resource) The application id. Must be between 4 and 7 characters in length. - `location_uri` - (Required, Forces new resource) A URI to locate the configuration. -- `description` - (Optional) The description of the environment. Can be at most 1024 characters. +- `description` - (Optional) The description of the configuration profile. Can be at most 1024 characters. - `validators` - (Optional) A list of methods for validating the configuration. Detailed below. - `retrieval_role_arn` - (Optional) The ARN of an IAM role with permission to access the configuration at the specified LocationUri. - `tags` - (Optional) A map of tags to assign to the resource. From 82aff38a9d140f5c72d8ae8e350b1c4320706a39 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 09:45:38 +0900 Subject: [PATCH 08/12] style: format code --- aws/resource_aws_appconfig_configuration_profile_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_appconfig_configuration_profile_test.go b/aws/resource_aws_appconfig_configuration_profile_test.go index 8b08b7130e7..4b873474fde 100644 --- a/aws/resource_aws_appconfig_configuration_profile_test.go +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -203,10 +203,10 @@ resource "aws_appconfig_configuration_profile" "test" { validators { type = "JSON_SCHEMA" content = jsonencode({ - "$schema" = "http://json-schema.org/draft-04/schema#" - title = "$id$" - description = "BasicFeatureToggle-1" - type = "object" + "$schema" = "http://json-schema.org/draft-04/schema#" + title = "$id$" + description = "BasicFeatureToggle-1" + type = "object" additionalProperties = false patternProperties = { "[^\\s]+$" = { From 2f911b800a68ad481266593f95905d863273947d Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 12 May 2021 22:31:15 +0900 Subject: [PATCH 09/12] fix: fix typo --- aws/resource_aws_appconfig_configuration_profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go index c0fce741008..331c1fc6372 100644 --- a/aws/resource_aws_appconfig_configuration_profile.go +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -211,7 +211,7 @@ func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, meta } if d.HasChange("retrieval_role_arn") { - updateInput.Name = aws.String(d.Get("retrieval_role_arn").(string)) + updateInput.RetrievalRoleArn = aws.String(d.Get("retrieval_role_arn").(string)) } if d.HasChange("tags") { From eae43b84de10f91104bddc6ab64dfc00402ca4c4 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Fri, 14 May 2021 20:36:12 +0900 Subject: [PATCH 10/12] fix: set RetrievalRoleArn only when the value isn't empty To avoid the following error. ``` Error: Error creating AppConfig ConfigurationProfile: InvalidParameter: 1 validation error(s) found. - minimum field size of 20, CreateConfigurationProfileInput.RetrievalRoleArn. ``` --- ...ource_aws_appconfig_configuration_profile.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go index 331c1fc6372..321aba0c793 100644 --- a/aws/resource_aws_appconfig_configuration_profile.go +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -97,13 +97,16 @@ func resourceAwsAppconfigConfigurationProfileCreate(d *schema.ResourceData, meta conn := meta.(*AWSClient).appconfigconn input := &appconfig.CreateConfigurationProfileInput{ - Name: aws.String(d.Get("name").(string)), - Description: aws.String(d.Get("description").(string)), - LocationUri: aws.String(d.Get("location_uri").(string)), - RetrievalRoleArn: aws.String(d.Get("retrieval_role_arn").(string)), - ApplicationId: aws.String(d.Get("application_id").(string)), - Validators: expandAppconfigValidators(d.Get("validators").([]interface{})), - Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppconfigTags(), + Name: aws.String(d.Get("name").(string)), + Description: aws.String(d.Get("description").(string)), + LocationUri: aws.String(d.Get("location_uri").(string)), + ApplicationId: aws.String(d.Get("application_id").(string)), + Validators: expandAppconfigValidators(d.Get("validators").([]interface{})), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppconfigTags(), + } + + if retrievalRoleARN := d.Get("retrieval_role_arn").(string); retrievalRoleARN != "" { + input.RetrievalRoleArn = aws.String(retrievalRoleARN) } profile, err := conn.CreateConfigurationProfile(input) From 9b6b71a320b34214bdde8ad2cb45216cd812a906 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Mon, 12 Jul 2021 12:05:24 +0900 Subject: [PATCH 11/12] fix: fix the error `undefined: testAccAWSAppConfigApplicationName` --- aws/resource_aws_appconfig_configuration_profile_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_appconfig_configuration_profile_test.go b/aws/resource_aws_appconfig_configuration_profile_test.go index 4b873474fde..f594dad9bf6 100644 --- a/aws/resource_aws_appconfig_configuration_profile_test.go +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -194,7 +194,7 @@ func testAccCheckAWSAppConfigConfigurationProfileARN(resourceName string, profil func testAccAWSAppConfigConfigurationProfile(profileName, profileDesc string) string { appName := acctest.RandomWithPrefix("tf-acc-test") appDesc := acctest.RandomWithPrefix("desc") - return testAccAWSAppConfigApplicationName(appName, appDesc) + fmt.Sprintf(` + return testAccAWSAppConfigApplicationConfigDescription(appName, appDesc) + fmt.Sprintf(` resource "aws_appconfig_configuration_profile" "test" { name = %[1]q description = %[2]q From a0d51c5230321458e9d90e3d50dfd049313008a6 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Mon, 12 Jul 2021 02:42:29 -0400 Subject: [PATCH 12/12] CR updates; add CHANGELOG entry --- .changelog/19320.txt | 3 + aws/provider.go | 2 +- ...rce_aws_appconfig_configuration_profile.go | 357 +++++++++----- ...ws_appconfig_configuration_profile_test.go | 453 +++++++++++++++--- aws/resource_aws_appconfig_environment.go | 12 +- ...resource_aws_appconfig_environment_test.go | 2 +- website/docs/index.html.markdown | 1 + ...config_configuration_profile.html.markdown | 55 ++- 8 files changed, 648 insertions(+), 237 deletions(-) create mode 100644 .changelog/19320.txt 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 48e60dbd52c..29aa1e41d05 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -507,8 +507,8 @@ func Provider() *schema.Provider { "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_appconfig_application": resourceAwsAppconfigApplication(), - "aws_appconfig_environment": resourceAwsAppconfigEnvironment(), "aws_appconfig_configuration_profile": resourceAwsAppconfigConfigurationProfile(), + "aws_appconfig_environment": resourceAwsAppconfigEnvironment(), "aws_appmesh_gateway_route": resourceAwsAppmeshGatewayRoute(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), "aws_appmesh_route": resourceAwsAppmeshRoute(), diff --git a/aws/resource_aws_appconfig_configuration_profile.go b/aws/resource_aws_appconfig_configuration_profile.go index 321aba0c793..6a17f212012 100644 --- a/aws/resource_aws_appconfig_configuration_profile.go +++ b/aws/resource_aws_appconfig_configuration_profile.go @@ -3,11 +3,13 @@ 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" @@ -20,178 +22,187 @@ func resourceAwsAppconfigConfigurationProfile() *schema.Resource { Update: resourceAwsAppconfigConfigurationProfileUpdate, Delete: resourceAwsAppconfigConfigurationProfileDelete, Importer: &schema.ResourceImporter{ - State: resourceAwsAppconfigConfigurationProfileImport, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 64), - ), - }, "application_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(4, 7), - ), + Computed: true, }, - "location_uri": { + "configuration_profile_id": { Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(0, 2048), - ), + Computed: true, }, "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.All( - validation.StringLenBetween(0, 1024), - ), + 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: validation.All( - validation.StringLenBetween(20, 2048), - ), + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, }, - "validators": { - Type: schema.TypeList, + "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, - ValidateFunc: validation.All( - validation.StringLenBetween(0, 32768), + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.Any( + validation.StringIsJSON, + validateArn, ), + DiffSuppressFunc: suppressEquivalentJsonDiffs, }, "type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "JSON_SCHEMA", "LAMBDA", - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appconfig.ValidatorType_Values(), false), }, }, }, }, - "tags": tagsSchema(), - "arn": { - Type: schema.TypeString, - Computed: true, - }, }, + 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{ - Name: aws.String(d.Get("name").(string)), - Description: aws.String(d.Get("description").(string)), + ApplicationId: aws.String(appId), LocationUri: aws.String(d.Get("location_uri").(string)), - ApplicationId: aws.String(d.Get("application_id").(string)), - Validators: expandAppconfigValidators(d.Get("validators").([]interface{})), - Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppconfigTags(), + Name: aws.String(name), + Tags: tags.IgnoreAws().AppconfigTags(), } - if retrievalRoleARN := d.Get("retrieval_role_arn").(string); retrievalRoleARN != "" { - input.RetrievalRoleArn = aws.String(retrievalRoleARN) + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) } - profile, err := conn.CreateConfigurationProfile(input) - if err != nil { - return fmt.Errorf("Error creating AppConfig ConfigurationProfile: %s", err) + if v, ok := d.GetOk("retrieval_role_arn"); ok { + input.RetrievalRoleArn = aws.String(v.(string)) } - d.SetId(aws.StringValue(profile.Id)) + if v, ok := d.GetOk("validator"); ok && v.(*schema.Set).Len() > 0 { + input.Validators = expandAppconfigValidators(v.(*schema.Set).List()) + } - return resourceAwsAppconfigConfigurationProfileRead(d, meta) -} + profile, err := conn.CreateConfigurationProfile(input) -func expandAppconfigValidators(list []interface{}) []*appconfig.Validator { - validators := make([]*appconfig.Validator, len(list)) - for i, validatorInterface := range list { - m := validatorInterface.(map[string]interface{}) - validators[i] = &appconfig.Validator{ - Content: aws.String(m["content"].(string)), - Type: aws.String(m["type"].(string)), - } + if err != nil { + return fmt.Errorf("error creating AppConfig Configuration Profile (%s) for Application (%s): %w", name, appId, err) } - return validators -} -func flattenAwsAppconfigValidators(validators []*appconfig.Validator) []interface{} { - list := make([]interface{}, len(validators)) - for i, validator := range validators { - list[i] = map[string]interface{}{ - "content": aws.StringValue(validator.Content), - "type": aws.StringValue(validator.Type), - } + if profile == nil { + return fmt.Errorf("error creating AppConfig Configuration Profile (%s) for Application (%s): empty response", name, appId) } - return list + + 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 - appID := d.Get("application_id").(string) + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(d.Id()) + + if err != nil { + return err + } input := &appconfig.GetConfigurationProfileInput{ ApplicationId: aws.String(appID), - ConfigurationProfileId: aws.String(d.Id()), + ConfigurationProfileId: aws.String(confProfID), } output, err := conn.GetConfigurationProfile(input) - if !d.IsNewResource() && isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] Appconfig ConfigurationProfile (%s) not found, removing from state", d.Id()) + 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 ConfigurationProfile (%s): %s", d.Id(), err) + 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 ConfigurationProfile (%s): empty response", d.Id()) + return fmt.Errorf("error getting AppConfig Configuration Profile (%s) for Application (%s): empty response", confProfID, appID) } - d.Set("name", output.Name) - d.Set("description", output.Description) 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) - d.Set("validators", flattenAwsAppconfigValidators(output.Validators)) - profileARN := arn.ARN{ + 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, d.Id()), + Resource: fmt.Sprintf("application/%s/configurationprofile/%s", appID, confProfID), Service: "appconfig", }.String() - d.Set("arn", profileARN) - tags, err := keyvaluetags.AppconfigListTags(conn, profileARN) + d.Set("arn", arn) + + tags, err := keyvaluetags.AppconfigListTags(conn, arn) + if err != nil { - return fmt.Errorf("error getting tags for AppConfig ConfigurationProfile (%s): %s", d.Id(), err) + return fmt.Errorf("error listing tags for AppConfig Configuration Profile (%s): %w", d.Id(), err) } - if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %s", 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 @@ -200,37 +211,46 @@ func resourceAwsAppconfigConfigurationProfileRead(d *schema.ResourceData, meta i func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).appconfigconn - updateInput := &appconfig.UpdateConfigurationProfileInput{ - ConfigurationProfileId: aws.String(d.Id()), - ApplicationId: aws.String(d.Get("application_id").(string)), - } + if d.HasChangesExcept("tags", "tags_all") { + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(d.Id()) - if d.HasChange("description") { - updateInput.Description = aws.String(d.Get("description").(string)) - } + if err != nil { + return err + } - if d.HasChange("name") { - updateInput.Name = aws.String(d.Get("name").(string)) - } + updateInput := &appconfig.UpdateConfigurationProfileInput{ + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), + } - if d.HasChange("retrieval_role_arn") { - updateInput.RetrievalRoleArn = aws.String(d.Get("retrieval_role_arn").(string)) - } + if d.HasChange("description") { + updateInput.Description = aws.String(d.Get("description").(string)) + } - if d.HasChange("tags") { - o, n := d.GetChange("tags") - if err := keyvaluetags.AppconfigUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating AppConfig (%s) tags: %s", d.Id(), err) + if d.HasChange("name") { + updateInput.Name = aws.String(d.Get("name").(string)) } - } - if d.HasChange("validators") { - updateInput.Validators = expandAppconfigValidators(d.Get("validators").([]interface{})) + 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) + } } - _, err := conn.UpdateConfigurationProfile(updateInput) - if err != nil { - return fmt.Errorf("error updating AppConfig ConfigurationProfile (%s): %s", d.Id(), 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) @@ -239,32 +259,115 @@ func resourceAwsAppconfigConfigurationProfileUpdate(d *schema.ResourceData, 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{ - ConfigurationProfileId: aws.String(d.Id()), - ApplicationId: aws.String(d.Get("application_id").(string)), + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), } - _, err := conn.DeleteConfigurationProfile(input) + _, err = conn.DeleteConfigurationProfile(input) - if isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { + if tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { return nil } if err != nil { - return fmt.Errorf("error deleting Appconfig ConfigurationProfile (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) } return nil } -func resourceAwsAppconfigConfigurationProfileImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - parts := strings.Split(d.Id(), "/") - if len(parts) != 2 { - return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'application-id/configurationprofile-id'", d.Id()) +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) } - d.SetId(parts[1]) - d.Set("application_id", parts[0]) + 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 []*schema.ResourceData{d}, nil + return tfList } diff --git a/aws/resource_aws_appconfig_configuration_profile_test.go b/aws/resource_aws_appconfig_configuration_profile_test.go index f594dad9bf6..23d98c250e9 100644 --- a/aws/resource_aws_appconfig_configuration_profile_test.go +++ b/aws/resource_aws_appconfig_configuration_profile_test.go @@ -2,20 +2,22 @@ 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) { - var profile appconfig.GetConfigurationProfileOutput - profileName := acctest.RandomWithPrefix("tf-acc-test") - profileDesc := acctest.RandomWithPrefix("desc") + 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), @@ -23,18 +25,20 @@ func TestAccAWSAppConfigConfigurationProfile_basic(t *testing.T) { CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAppConfigConfigurationProfile(profileName, profileDesc), + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), - resource.TestCheckResourceAttr(resourceName, "name", profileName), - testAccCheckAWSAppConfigConfigurationProfileARN(resourceName, &profile), + 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"), - resource.TestCheckResourceAttr(resourceName, "description", profileDesc), ), }, { ResourceName: resourceName, - ImportStateIdFunc: testAccAWSAppConfigConfigurationProfileImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, @@ -43,10 +47,7 @@ func TestAccAWSAppConfigConfigurationProfile_basic(t *testing.T) { } func TestAccAWSAppConfigConfigurationProfile_disappears(t *testing.T) { - var profile appconfig.GetConfigurationProfileOutput - - profileName := acctest.RandomWithPrefix("tf-acc-test") - profileDesc := acctest.RandomWithPrefix("desc") + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_appconfig_configuration_profile.test" resource.ParallelTest(t, resource.TestCase{ @@ -56,10 +57,10 @@ func TestAccAWSAppConfigConfigurationProfile_disappears(t *testing.T) { CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAppConfigConfigurationProfile(profileName, profileDesc), + Config: testAccAWSAppConfigConfigurationProfileConfigName(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), - testAccCheckAWSAppConfigConfigurationProfileDisappears(&profile), + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppconfigConfigurationProfile(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -67,9 +68,205 @@ func TestAccAWSAppConfigConfigurationProfile_disappears(t *testing.T) { }) } -func TestAccAWSAppConfigConfigurationProfile_Tags(t *testing.T) { - var profile appconfig.GetConfigurationProfileOutput +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" @@ -82,21 +279,20 @@ func TestAccAWSAppConfigConfigurationProfile_Tags(t *testing.T) { { Config: testAccAWSAppConfigConfigurationProfileTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { ResourceName: resourceName, - ImportStateIdFunc: testAccAWSAppConfigConfigurationProfileImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, { Config: testAccAWSAppConfigConfigurationProfileTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -105,7 +301,7 @@ func TestAccAWSAppConfigConfigurationProfile_Tags(t *testing.T) { { Config: testAccAWSAppConfigConfigurationProfileTags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAppConfigConfigurationProfileExists(resourceName, &profile), + testAccCheckAWSAppConfigConfigurationProfileExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), @@ -122,43 +318,36 @@ func testAccCheckAppConfigConfigurationProfileDestroy(s *terraform.State) error continue } + confProfID, appID, err := resourceAwsAppconfigConfigurationProfileParseID(rs.Primary.ID) + + if err != nil { + return err + } + input := &appconfig.GetConfigurationProfileInput{ - ApplicationId: aws.String(rs.Primary.Attributes["application_id"]), - ConfigurationProfileId: aws.String(rs.Primary.ID), + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), } output, err := conn.GetConfigurationProfile(input) - if isAWSErr(err, appconfig.ErrCodeResourceNotFoundException, "") { + if tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { continue } if err != nil { - return err + return fmt.Errorf("error reading AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) } if output != nil { - return fmt.Errorf("AppConfig ConfigurationProfile (%s) still exists", rs.Primary.ID) + return fmt.Errorf("AppConfig Configuration Profile (%s) for Application (%s) still exists", confProfID, appID) } } return nil } -func testAccCheckAWSAppConfigConfigurationProfileDisappears(profile *appconfig.GetConfigurationProfileOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).appconfigconn - - _, err := conn.DeleteConfigurationProfile(&appconfig.DeleteConfigurationProfileInput{ - ApplicationId: profile.ApplicationId, - ConfigurationProfileId: profile.Id, - }) - - return err - } -} - -func testAccCheckAWSAppConfigConfigurationProfileExists(resourceName string, profile *appconfig.GetConfigurationProfileOutput) resource.TestCheckFunc { +func testAccCheckAWSAppConfigConfigurationProfileExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -169,39 +358,66 @@ func testAccCheckAWSAppConfigConfigurationProfileExists(resourceName string, pro 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(rs.Primary.Attributes["application_id"]), - ConfigurationProfileId: aws.String(rs.Primary.ID), + ApplicationId: aws.String(appID), + ConfigurationProfileId: aws.String(confProfID), }) + if err != nil { - return err + return fmt.Errorf("error reading AppConfig Configuration Profile (%s) for Application (%s): %w", confProfID, appID, err) } - *profile = *output + if output == nil { + return fmt.Errorf("AppConfig Configuration Profile (%s) for Application (%s) not found", confProfID, appID) + } return nil } } -func testAccCheckAWSAppConfigConfigurationProfileARN(resourceName string, profile *appconfig.GetConfigurationProfileOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - return testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appconfig", fmt.Sprintf("application/%s/configurationprofile/%s", aws.StringValue(profile.ApplicationId), aws.StringValue(profile.Id)))(s) - } +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 testAccAWSAppConfigConfigurationProfile(profileName, profileDesc string) string { - appName := acctest.RandomWithPrefix("tf-acc-test") - appDesc := acctest.RandomWithPrefix("desc") - return testAccAWSAppConfigApplicationConfigDescription(appName, appDesc) + fmt.Sprintf(` +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" - validators { - type = "JSON_SCHEMA" + + validator { content = jsonencode({ "$schema" = "http://json-schema.org/draft-04/schema#" title = "$id$" @@ -215,47 +431,138 @@ resource "aws_appconfig_configuration_profile" "test" { } minProperties = 1 }) + + type = "JSON_SCHEMA" } } -`, profileName, profileDesc) +`, rName)) } -func testAccAWSAppConfigConfigurationProfileTags1(rName, tagKey1, tagValue1 string) string { - return testAccAWSAppConfigApplicationTags1(rName, tagKey1, tagValue1) + fmt.Sprintf(` +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 -### validator +The `validator` block supports the following: -- `content` - (Optional) Either the JSON Schema content or the Amazon Resource Name (ARN) of an AWS Lambda function. -- `type` - (Optional) AWS AppConfig supports validators of type `JSON_SCHEMA` and `LAMBDA` +* `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. -- `id` - The AppConfig Configuration Profile ID +* `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 -`aws_appconfig_configuration_profile` can be imported by the Application ID and Configuration Profile ID, e.g. +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.test 71abcde/11xxxxx +$ terraform import aws_appconfig_configuration_profile.example 71abcde:11xxxxx ```