From f76bf66fffd7370eb478f04ad176273a5c940e5d Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Wed, 14 Jul 2021 08:46:14 +0900 Subject: [PATCH 01/12] feat: add aws_appconfig_deployment --- aws/provider.go | 1 + aws/resource_aws_appconfig_deployment.go | 207 +++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 aws/resource_aws_appconfig_deployment.go diff --git a/aws/provider.go b/aws/provider.go index dd950720b79..81141ccdc64 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_configuration_profile": resourceAwsAppconfigConfigurationProfile(), + "aws_appconfig_deployment": resourceAwsAppconfigDeployment(), "aws_appconfig_deployment_strategy": resourceAwsAppconfigDeploymentStrategy(), "aws_appconfig_environment": resourceAwsAppconfigEnvironment(), "aws_appconfig_hosted_configuration_version": resourceAwsAppconfigHostedConfigurationVersion(), diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go new file mode 100644 index 00000000000..0689cb9e7dd --- /dev/null +++ b/aws/resource_aws_appconfig_deployment.go @@ -0,0 +1,207 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strconv" + "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 resourceAwsAppconfigDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppconfigDeploymentCreate, + Read: resourceAwsAppconfigDeploymentRead, + Update: resourceAwsAppconfigDeploymentUpdate, + Delete: resourceAwsAppconfigDeploymentDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsAppconfigDeploymentImport, + }, + + Schema: map[string]*schema.Schema{ + "application_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "configuration_profile_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "configuration_version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "deployment_strategy_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + "environment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), + }, + "deployment_number": { + Type: schema.TypeInt, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + }, + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsAppconfigDeploymentCreate(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) + envID := d.Get("environment_id").(string) + + input := &appconfig.StartDeploymentInput{ + ApplicationId: aws.String(appID), + EnvironmentId: aws.String(envID), + ConfigurationProfileId: aws.String(d.Get("configuration_profile_id").(string)), + ConfigurationVersion: aws.String(d.Get("configuration_version").(string)), + DeploymentStrategyId: aws.String(d.Get("deployment_strategy_id").(string)), + Description: aws.String(d.Get("description").(string)), + Tags: tags.IgnoreAws().AppconfigTags(), + } + + output, err := conn.StartDeployment(input) + + if err != nil { + return fmt.Errorf("error starting AppConfig Deployment: %w", err) + } + + if output == nil { + return fmt.Errorf("error starting AppConfig Deployment: empty response") + } + + d.Set("deployment_number", aws.Int64Value(output.DeploymentNumber)) + d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(aws.Int64Value(output.DeploymentNumber), 10)) + + return resourceAwsAppconfigDeploymentRead(d, meta) +} + +func resourceAwsAppconfigDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), "/") + if len(idParts) != 3 { + return nil, fmt.Errorf("unexpected format of ID (%q), expected //", d.Id()) + } + + appID := idParts[0] + envID := idParts[1] + depS := idParts[2] + depNum, err := strconv.Atoi(depS) + if err != nil { + return nil, fmt.Errorf("deployment number is invalid (%s): %w", depS, err) + } + + d.Set("application_id", appID) + d.Set("environment_id", envID) + d.Set("deployment_number", depNum) + + return []*schema.ResourceData{d}, nil +} + +func resourceAwsAppconfigDeploymentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appconfigconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + appID := d.Get("application_id").(string) + envID := d.Get("environment_id").(string) + deployNum := d.Get("deployment_number").(int) + + input := &appconfig.GetDeploymentInput{ + ApplicationId: aws.String(appID), + EnvironmentId: aws.String(envID), + DeploymentNumber: aws.Int64(int64(deployNum)), + } + + output, err := conn.GetDeployment(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, appconfig.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Appconfig Deployment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting AppConfig Deployment (%s): %w", d.Id(), err) + } + + if output == nil { + return fmt.Errorf("error getting AppConfig Deployment (%s): empty response", d.Id()) + } + + arn := arn.ARN{ + AccountID: meta.(*AWSClient).accountid, + Partition: meta.(*AWSClient).partition, + Region: meta.(*AWSClient).region, + Resource: fmt.Sprintf("application/%s/environment/%s/deployment/%d", appID, envID, deployNum), + Service: "appconfig", + }.String() + + d.Set("arn", arn) + d.Set("description", output.Description) + d.Set("configuration_profile_id", output.ConfigurationProfileId) + d.Set("configuration_version", output.ConfigurationVersion) + d.Set("deployment_strategy_id", output.DeploymentStrategyId) + + tags, err := keyvaluetags.AppconfigListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for AppConfig Application (%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 resourceAwsAppconfigDeploymentUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceAwsAppconfigDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} From 903fce30928954c48e212141dc8e3fe8916a996c Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Wed, 14 Jul 2021 21:28:29 +0900 Subject: [PATCH 02/12] docs: add the document of aws_appconfig_deployment --- .../docs/r/appconfig_deployment.html.markdown | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 website/docs/r/appconfig_deployment.html.markdown diff --git a/website/docs/r/appconfig_deployment.html.markdown b/website/docs/r/appconfig_deployment.html.markdown new file mode 100644 index 00000000000..db6db1314f0 --- /dev/null +++ b/website/docs/r/appconfig_deployment.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "AppConfig" +layout: "aws" +page_title: "AWS: aws_appconfig_deployment" +description: |- + Provides an AppConfig Deployment resource. +--- + +# Resource: aws_appconfig_deployment + +Provides an AppConfig Deployment resource for an [`aws_appconfig_application` resource](appconfig_application.html.markdown). + +## Example Usage + +```terraform +resource "aws_appconfig_deployment" "test" { + application_id = aws_appconfig_application.example.id + configuration_profile_id = aws_appconfig_configuration_profile.example.configuration_profile_id + configuration_version = "00000000-0000-0000-0000-000000000000" + deployment_strategy_id = aws_appconfig_deployment_strategy.example.id + description = "My test deployment" + environment_id = aws_appconfig_environment.example.environment_id + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `application_id` - (Required, Forces new resource) The application ID. Must be between 4 and 7 characters in length. +* `configuration_profile_id` - (Required, Forces new resource) The configuration profile ID. Must be between 4 and 7 characters in length. +* `configuration_version` - (Required, Forces new resource) The configuration version to deploy. Can be at most 1024 characters. +* `deployment_strategy_id` - (Required, Forces new resource) The deployment strategy ID. Must be between 4 and 7 characters in length. +* `description` - (Optional, Fources new resource) The description of the deployment. Can be at most 1024 characters. +* `environment_id` - (Required, Forces new resource) The environment ID. Must be between 4 and 7 characters in length. +* `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. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the AppConfig Environment. +* `id` - The AppConfig application ID and environment ID and deployment number separated by a colon (`/`). +* `deployment_number` - The deployment number. +* `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 Deployment can be imported by using the application ID and environment ID and deployment number separated by a colon (`/`), e.g. + +``` +$ terraform import aws_appconfig_deployment.example 71abcde/11xxxxx/1 +``` From 54867768f725a2f52350c729d58a5544448693e5 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Wed, 14 Jul 2021 22:27:52 +0900 Subject: [PATCH 03/12] fix: fix semgrep error AWS Go SDK pointer conversion function for `d.Set()` value is extraneous --- aws/resource_aws_appconfig_deployment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 0689cb9e7dd..2e6f0550056 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -106,7 +106,7 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface return fmt.Errorf("error starting AppConfig Deployment: empty response") } - d.Set("deployment_number", aws.Int64Value(output.DeploymentNumber)) + d.Set("deployment_number", output.DeploymentNumber) d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(aws.Int64Value(output.DeploymentNumber), 10)) return resourceAwsAppconfigDeploymentRead(d, meta) From 2bc303fef34e6881b88289a669e2efbe217e843e Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 17:43:11 +0900 Subject: [PATCH 04/12] feat: implement the update of AppConfig Deployment --- aws/resource_aws_appconfig_deployment.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 2e6f0550056..5a3a72be259 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -199,7 +199,17 @@ func resourceAwsAppconfigDeploymentRead(d *schema.ResourceData, meta interface{} } func resourceAwsAppconfigDeploymentUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + conn := meta.(*AWSClient).appconfigconn + + 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 EFS file system (%s) tags: %w", d.Id(), err) + } + } + + return resourceAwsAppconfigDeploymentRead(d, meta) } func resourceAwsAppconfigDeploymentDelete(d *schema.ResourceData, meta interface{}) error { From c19dcf5c62073091d562bdf67c8b02a89abd159e Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 17:47:28 +0900 Subject: [PATCH 05/12] fix: add the warning log when AppConfig Deployment resource is removed --- aws/resource_aws_appconfig_deployment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 5a3a72be259..81037abf7ee 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -213,5 +213,6 @@ func resourceAwsAppconfigDeploymentUpdate(d *schema.ResourceData, meta interface } func resourceAwsAppconfigDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[WARN] Cannot destroy AppConfig Deployment. Terraform will remove this resource from the state file, however this resource remains.") return nil } From 7f4b6bce2157e0a4cea2f3c7177007b5a49c73ca Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 18:12:33 +0900 Subject: [PATCH 06/12] feat: wait for the creation of AppConfig Deployment --- .../service/appconfig/waiter/status.go | 31 +++++++++++++++++++ .../service/appconfig/waiter/waiter.go | 25 +++++++++++++++ aws/resource_aws_appconfig_deployment.go | 9 +++++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 aws/internal/service/appconfig/waiter/status.go create mode 100644 aws/internal/service/appconfig/waiter/waiter.go diff --git a/aws/internal/service/appconfig/waiter/status.go b/aws/internal/service/appconfig/waiter/status.go new file mode 100644 index 00000000000..9c1456e4325 --- /dev/null +++ b/aws/internal/service/appconfig/waiter/status.go @@ -0,0 +1,31 @@ +package waiter + +// https://github.com/hashicorp/terraform-provider-aws/pull/20172#issuecomment-881017208 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func DeploymentStatus(conn *appconfig.AppConfig, appID, envID string, deployNum int64) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &appconfig.GetDeploymentInput{ + ApplicationId: aws.String(appID), + DeploymentNumber: aws.Int64(deployNum), + EnvironmentId: aws.String(envID), + } + + output, err := conn.GetDeployment(input) + + if err != nil { + return nil, "", err + } + + if output == nil { + return nil, "", nil + } + + return output, aws.StringValue(output.State), nil + } +} diff --git a/aws/internal/service/appconfig/waiter/waiter.go b/aws/internal/service/appconfig/waiter/waiter.go new file mode 100644 index 00000000000..04a17777ff7 --- /dev/null +++ b/aws/internal/service/appconfig/waiter/waiter.go @@ -0,0 +1,25 @@ +package waiter + +// https://github.com/hashicorp/terraform-provider-aws/pull/20172#issuecomment-881017208 + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const DeploymentCreatedTimeout = 5 * time.Minute + +func DeploymentCreated(conn *appconfig.AppConfig, appID, envID string, deployNum int64) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{appconfig.DeploymentStateBaking, appconfig.DeploymentStateRollingBack, appconfig.DeploymentStateValidating, appconfig.DeploymentStateDeploying}, + Target: []string{appconfig.DeploymentStateComplete}, + Refresh: DeploymentStatus(conn, appID, envID, deployNum), + Timeout: DeploymentCreatedTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 81037abf7ee..706160d3284 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -14,6 +14,7 @@ import ( "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appconfig/waiter" ) func resourceAwsAppconfigDeployment() *schema.Resource { @@ -106,8 +107,14 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface return fmt.Errorf("error starting AppConfig Deployment: empty response") } + deployNum := aws.Int64Value(output.DeploymentNumber) + d.Set("deployment_number", output.DeploymentNumber) - d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(aws.Int64Value(output.DeploymentNumber), 10)) + d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(deployNum, 10)) + + if err := waiter.DeploymentCreated(conn, appID, envID, deployNum); err != nil { + return fmt.Errorf("error waiting for AppConfig Deployment (%s) creation: %w", d.Id(), err) + } return resourceAwsAppconfigDeploymentRead(d, meta) } From ba6c76bb263dbe9ada41e634f80589b5d9a1e288 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 20:08:33 +0900 Subject: [PATCH 07/12] test: add tests of AppConfig Deployment --- aws/resource_aws_appconfig_deployment.go | 18 ++- aws/resource_aws_appconfig_deployment_test.go | 117 ++++++++++++++++++ 2 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 aws/resource_aws_appconfig_deployment_test.go diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 706160d3284..10fe38e3973 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -119,18 +119,26 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface return resourceAwsAppconfigDeploymentRead(d, meta) } -func resourceAwsAppconfigDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - idParts := strings.Split(d.Id(), "/") +func parseAwsAppconfigDeploymentID(id string) (string, string, int64, error) { + idParts := strings.Split(id, "/") if len(idParts) != 3 { - return nil, fmt.Errorf("unexpected format of ID (%q), expected //", d.Id()) + return "", "", 0, fmt.Errorf("unexpected format of ID (%q), expected //", id) } appID := idParts[0] envID := idParts[1] depS := idParts[2] - depNum, err := strconv.Atoi(depS) + depNum, err := strconv.ParseInt(depS, 10, 64) + if err != nil { + return "", "", 0, fmt.Errorf("deployment number is invalid (%s): %w", depS, err) + } + return appID, envID, depNum, nil +} + +func resourceAwsAppconfigDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + appID, envID, depNum, err := parseAwsAppconfigDeploymentID(d.Id()) if err != nil { - return nil, fmt.Errorf("deployment number is invalid (%s): %w", depS, err) + return nil, err } d.Set("application_id", appID) diff --git a/aws/resource_aws_appconfig_deployment_test.go b/aws/resource_aws_appconfig_deployment_test.go new file mode 100644 index 00000000000..92fa162daf5 --- /dev/null +++ b/aws/resource_aws_appconfig_deployment_test.go @@ -0,0 +1,117 @@ +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 TestAccAWSAppConfigDeployment_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_deployment.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: testAccAWSAppConfigDeployment_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigDeploymentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttrPair(resourceName, "application_id", appResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "description", "My test deployment"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSAppConfigDeploymentExists(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) + } + appID, envID, depNum, err := parseAwsAppconfigDeploymentID(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).appconfigconn + + output, err := conn.GetDeployment(&appconfig.GetDeploymentInput{ + ApplicationId: aws.String(appID), + EnvironmentId: aws.String(envID), + DeploymentNumber: aws.Int64(depNum), + }) + + if err != nil { + return fmt.Errorf("error reading AppConfig Deployment (%s): %w", rs.Primary.ID, err) + } + + if output == nil { + return fmt.Errorf("AppConfig Deployment (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAWSAppConfigDeployment_basic(rName string) string { + return composeConfig( + testAccAWSAppConfigConfigurationProfileConfigName(rName), + `resource "aws_appconfig_environment" "test" { + name = "test" + application_id = aws_appconfig_application.test.id +} + +resource "aws_appconfig_hosted_configuration_version" "test" { + application_id = aws_appconfig_application.test.id + configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id + content_type = "application/json" + + content = jsonencode({ + foo = "bar" + }) +} + +resource "aws_appconfig_deployment" "test" { + application_id = aws_appconfig_application.test.id + configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id + configuration_version = aws_appconfig_hosted_configuration_version.test.version_number + deployment_strategy_id = aws_appconfig_deployment_strategy.test.id + description = "My test deployment" + environment_id = aws_appconfig_environment.test.environment_id + tags = { + Env = "test" + } +} +`, + fmt.Sprintf(` +resource "aws_appconfig_deployment_strategy" "test" { + name = "%s" + deployment_duration_in_minutes = 0 + final_bake_time_in_minutes = 0 + growth_factor = 100 + replicate_to = "NONE" +} +`, rName)) +} From ab7433fdf073b5fea2922e599acbff8595f7a549 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 20:11:05 +0900 Subject: [PATCH 08/12] docs: update the example of aws_appconfig_deployment --- website/docs/r/appconfig_deployment.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/appconfig_deployment.html.markdown b/website/docs/r/appconfig_deployment.html.markdown index db6db1314f0..492548e34c9 100644 --- a/website/docs/r/appconfig_deployment.html.markdown +++ b/website/docs/r/appconfig_deployment.html.markdown @@ -16,7 +16,7 @@ Provides an AppConfig Deployment resource for an [`aws_appconfig_application` re resource "aws_appconfig_deployment" "test" { application_id = aws_appconfig_application.example.id configuration_profile_id = aws_appconfig_configuration_profile.example.configuration_profile_id - configuration_version = "00000000-0000-0000-0000-000000000000" + configuration_version = aws_appconfig_hosted_configuration_version.example.version_number deployment_strategy_id = aws_appconfig_deployment_strategy.example.id description = "My test deployment" environment_id = aws_appconfig_environment.example.environment_id From 0834f152fbfae640532825171ade5ce68c93a156 Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Sat, 17 Jul 2021 20:56:28 +0900 Subject: [PATCH 09/12] Revert "feat: wait for the creation of AppConfig Deployment" This reverts commit 7f4b6bce2157e0a4cea2f3c7177007b5a49c73ca. --- .../service/appconfig/waiter/status.go | 31 ------------------- .../service/appconfig/waiter/waiter.go | 25 --------------- aws/resource_aws_appconfig_deployment.go | 9 +----- 3 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 aws/internal/service/appconfig/waiter/status.go delete mode 100644 aws/internal/service/appconfig/waiter/waiter.go diff --git a/aws/internal/service/appconfig/waiter/status.go b/aws/internal/service/appconfig/waiter/status.go deleted file mode 100644 index 9c1456e4325..00000000000 --- a/aws/internal/service/appconfig/waiter/status.go +++ /dev/null @@ -1,31 +0,0 @@ -package waiter - -// https://github.com/hashicorp/terraform-provider-aws/pull/20172#issuecomment-881017208 - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/appconfig" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func DeploymentStatus(conn *appconfig.AppConfig, appID, envID string, deployNum int64) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &appconfig.GetDeploymentInput{ - ApplicationId: aws.String(appID), - DeploymentNumber: aws.Int64(deployNum), - EnvironmentId: aws.String(envID), - } - - output, err := conn.GetDeployment(input) - - if err != nil { - return nil, "", err - } - - if output == nil { - return nil, "", nil - } - - return output, aws.StringValue(output.State), nil - } -} diff --git a/aws/internal/service/appconfig/waiter/waiter.go b/aws/internal/service/appconfig/waiter/waiter.go deleted file mode 100644 index 04a17777ff7..00000000000 --- a/aws/internal/service/appconfig/waiter/waiter.go +++ /dev/null @@ -1,25 +0,0 @@ -package waiter - -// https://github.com/hashicorp/terraform-provider-aws/pull/20172#issuecomment-881017208 - -import ( - "time" - - "github.com/aws/aws-sdk-go/service/appconfig" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -const DeploymentCreatedTimeout = 5 * time.Minute - -func DeploymentCreated(conn *appconfig.AppConfig, appID, envID string, deployNum int64) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{appconfig.DeploymentStateBaking, appconfig.DeploymentStateRollingBack, appconfig.DeploymentStateValidating, appconfig.DeploymentStateDeploying}, - Target: []string{appconfig.DeploymentStateComplete}, - Refresh: DeploymentStatus(conn, appID, envID, deployNum), - Timeout: DeploymentCreatedTimeout, - } - - _, err := stateConf.WaitForState() - - return err -} diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 10fe38e3973..95e3e93229f 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -14,7 +14,6 @@ import ( "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" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appconfig/waiter" ) func resourceAwsAppconfigDeployment() *schema.Resource { @@ -107,14 +106,8 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface return fmt.Errorf("error starting AppConfig Deployment: empty response") } - deployNum := aws.Int64Value(output.DeploymentNumber) - d.Set("deployment_number", output.DeploymentNumber) - d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(deployNum, 10)) - - if err := waiter.DeploymentCreated(conn, appID, envID, deployNum); err != nil { - return fmt.Errorf("error waiting for AppConfig Deployment (%s) creation: %w", d.Id(), err) - } + d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(aws.Int64Value(output.DeploymentNumber), 10)) return resourceAwsAppconfigDeploymentRead(d, meta) } From 8ff5e6ece03cd7519c78af2102c64c2e17a75caf Mon Sep 17 00:00:00 2001 From: Suzuki Shunsuke Date: Wed, 21 Jul 2021 21:00:51 +0900 Subject: [PATCH 10/12] docs: add CHANGELOG entry --- .changelog/20172.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/20172.txt diff --git a/.changelog/20172.txt b/.changelog/20172.txt new file mode 100644 index 00000000000..912d55d588f --- /dev/null +++ b/.changelog/20172.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_appconfig_deployment +``` From b134b32ac7cbcc41c94d1dbe364101f603869cca Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Thu, 22 Jul 2021 11:13:40 -0400 Subject: [PATCH 11/12] update deployment resource to use importstatepassthrough, add test coverage, and update docs --- aws/config.go | 11 ++ .../service/appconfig/waiter/status.go | 29 ++++ .../service/appconfig/waiter/waiter.go | 23 +++ aws/resource_aws_appconfig_deployment.go | 100 ++++++----- aws/resource_aws_appconfig_deployment_test.go | 160 +++++++++++++++--- website/docs/index.html.markdown | 1 + .../docs/r/appconfig_deployment.html.markdown | 15 +- 7 files changed, 255 insertions(+), 84 deletions(-) create mode 100644 aws/internal/service/appconfig/waiter/status.go create mode 100644 aws/internal/service/appconfig/waiter/waiter.go diff --git a/aws/config.go b/aws/config.go index 56cc8b205bc..77c2bcae9fb 100644 --- a/aws/config.go +++ b/aws/config.go @@ -695,6 +695,17 @@ func (c *Config) Client() (interface{}, error) { } }) + // StartDeployment operations can return a ConflictException + // if ongoing deployments are in-progress, thus we handle them + // here for the service client. + client.appconfigconn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name == "StartDeployment" { + if tfawserr.ErrCodeEquals(r.Error, appconfig.ErrCodeConflictException) { + r.Retryable = aws.Bool(true) + } + } + }) + client.appsyncconn.Handlers.Retry.PushBack(func(r *request.Request) { if r.Operation.Name == "CreateGraphqlApi" { if isAWSErr(r.Error, appsync.ErrCodeConcurrentModificationException, "a GraphQL API creation is already in progress") { diff --git a/aws/internal/service/appconfig/waiter/status.go b/aws/internal/service/appconfig/waiter/status.go new file mode 100644 index 00000000000..9228c079528 --- /dev/null +++ b/aws/internal/service/appconfig/waiter/status.go @@ -0,0 +1,29 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func DeploymentStatus(conn *appconfig.AppConfig, appID, envID string, deployNum int64) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &appconfig.GetDeploymentInput{ + ApplicationId: aws.String(appID), + DeploymentNumber: aws.Int64(deployNum), + EnvironmentId: aws.String(envID), + } + + output, err := conn.GetDeployment(input) + + if err != nil { + return nil, "", err + } + + if output == nil { + return nil, "", nil + } + + return output, aws.StringValue(output.State), nil + } +} diff --git a/aws/internal/service/appconfig/waiter/waiter.go b/aws/internal/service/appconfig/waiter/waiter.go new file mode 100644 index 00000000000..b09cfa81560 --- /dev/null +++ b/aws/internal/service/appconfig/waiter/waiter.go @@ -0,0 +1,23 @@ +package waiter + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const DeploymentCreatedTimeout = 10 * time.Minute + +func DeploymentCreated(conn *appconfig.AppConfig, appID, envID string, deployNum int64) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{appconfig.DeploymentStateBaking, appconfig.DeploymentStateRollingBack, appconfig.DeploymentStateValidating, appconfig.DeploymentStateDeploying}, + Target: []string{appconfig.DeploymentStateComplete}, + Refresh: DeploymentStatus(conn, appID, envID, deployNum), + Timeout: DeploymentCreatedTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/resource_aws_appconfig_deployment.go b/aws/resource_aws_appconfig_deployment.go index 95e3e93229f..b02f480cd3d 100644 --- a/aws/resource_aws_appconfig_deployment.go +++ b/aws/resource_aws_appconfig_deployment.go @@ -14,6 +14,7 @@ import ( "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appconfig/waiter" ) func resourceAwsAppconfigDeployment() *schema.Resource { @@ -23,7 +24,7 @@ func resourceAwsAppconfigDeployment() *schema.Resource { Update: resourceAwsAppconfigDeploymentUpdate, Delete: resourceAwsAppconfigDeploymentDelete, Importer: &schema.ResourceImporter{ - State: resourceAwsAppconfigDeploymentImport, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ @@ -33,6 +34,10 @@ func resourceAwsAppconfigDeployment() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, "configuration_profile_id": { Type: schema.TypeString, Required: true, @@ -45,6 +50,10 @@ func resourceAwsAppconfigDeployment() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1024), }, + "deployment_number": { + Type: schema.TypeInt, + Computed: true, + }, "deployment_strategy_id": { Type: schema.TypeString, Required: true, @@ -63,14 +72,6 @@ func resourceAwsAppconfigDeployment() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-z0-9]{4,7}`), ""), }, - "deployment_number": { - Type: schema.TypeInt, - Computed: true, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -83,12 +84,9 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) - appID := d.Get("application_id").(string) - envID := d.Get("environment_id").(string) - input := &appconfig.StartDeploymentInput{ - ApplicationId: aws.String(appID), - EnvironmentId: aws.String(envID), + ApplicationId: aws.String(d.Get("application_id").(string)), + EnvironmentId: aws.String(d.Get("environment_id").(string)), ConfigurationProfileId: aws.String(d.Get("configuration_profile_id").(string)), ConfigurationVersion: aws.String(d.Get("configuration_version").(string)), DeploymentStrategyId: aws.String(d.Get("deployment_strategy_id").(string)), @@ -106,39 +104,17 @@ func resourceAwsAppconfigDeploymentCreate(d *schema.ResourceData, meta interface return fmt.Errorf("error starting AppConfig Deployment: empty response") } - d.Set("deployment_number", output.DeploymentNumber) - d.SetId(appID + "/" + envID + "/" + strconv.FormatInt(aws.Int64Value(output.DeploymentNumber), 10)) + appID := aws.StringValue(output.ApplicationId) + envID := aws.StringValue(output.EnvironmentId) + deployNum := aws.Int64Value(output.DeploymentNumber) - return resourceAwsAppconfigDeploymentRead(d, meta) -} + d.SetId(fmt.Sprintf("%s/%s/%d", appID, envID, deployNum)) -func parseAwsAppconfigDeploymentID(id string) (string, string, int64, error) { - idParts := strings.Split(id, "/") - if len(idParts) != 3 { - return "", "", 0, fmt.Errorf("unexpected format of ID (%q), expected //", id) + if err := waiter.DeploymentCreated(conn, appID, envID, deployNum); err != nil { + return fmt.Errorf("error waiting for AppConfig Deployment (%s) creation: %w", d.Id(), err) } - appID := idParts[0] - envID := idParts[1] - depS := idParts[2] - depNum, err := strconv.ParseInt(depS, 10, 64) - if err != nil { - return "", "", 0, fmt.Errorf("deployment number is invalid (%s): %w", depS, err) - } - return appID, envID, depNum, nil -} - -func resourceAwsAppconfigDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - appID, envID, depNum, err := parseAwsAppconfigDeploymentID(d.Id()) - if err != nil { - return nil, err - } - - d.Set("application_id", appID) - d.Set("environment_id", envID) - d.Set("deployment_number", depNum) - - return []*schema.ResourceData{d}, nil + return resourceAwsAppconfigDeploymentRead(d, meta) } func resourceAwsAppconfigDeploymentRead(d *schema.ResourceData, meta interface{}) error { @@ -146,14 +122,16 @@ func resourceAwsAppconfigDeploymentRead(d *schema.ResourceData, meta interface{} defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - appID := d.Get("application_id").(string) - envID := d.Get("environment_id").(string) - deployNum := d.Get("deployment_number").(int) + appID, envID, deploymentNum, err := resourceAwsAppconfigDeploymentParseID(d.Id()) + + if err != nil { + return err + } input := &appconfig.GetDeploymentInput{ ApplicationId: aws.String(appID), + DeploymentNumber: aws.Int64(int64(deploymentNum)), EnvironmentId: aws.String(envID), - DeploymentNumber: aws.Int64(int64(deployNum)), } output, err := conn.GetDeployment(input) @@ -176,20 +154,23 @@ func resourceAwsAppconfigDeploymentRead(d *schema.ResourceData, meta interface{} AccountID: meta.(*AWSClient).accountid, Partition: meta.(*AWSClient).partition, Region: meta.(*AWSClient).region, - Resource: fmt.Sprintf("application/%s/environment/%s/deployment/%d", appID, envID, deployNum), + Resource: fmt.Sprintf("application/%s/environment/%s/deployment/%d", aws.StringValue(output.ApplicationId), aws.StringValue(output.EnvironmentId), aws.Int64Value(output.DeploymentNumber)), Service: "appconfig", }.String() + d.Set("application_id", output.ApplicationId) d.Set("arn", arn) - d.Set("description", output.Description) d.Set("configuration_profile_id", output.ConfigurationProfileId) d.Set("configuration_version", output.ConfigurationVersion) + d.Set("deployment_number", output.DeploymentNumber) d.Set("deployment_strategy_id", output.DeploymentStrategyId) + d.Set("description", output.Description) + d.Set("environment_id", output.EnvironmentId) tags, err := keyvaluetags.AppconfigListTags(conn, arn) if err != nil { - return fmt.Errorf("error listing tags for AppConfig Application (%s): %w", d.Id(), err) + return fmt.Errorf("error listing tags for AppConfig Deployment (%s): %w", d.Id(), err) } tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -213,14 +194,29 @@ func resourceAwsAppconfigDeploymentUpdate(d *schema.ResourceData, meta interface o, n := d.GetChange("tags_all") if err := keyvaluetags.AppconfigUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating EFS file system (%s) tags: %w", d.Id(), err) + return fmt.Errorf("error updating AppConfig Deployment (%s) tags: %w", d.Get("arn").(string), err) } } return resourceAwsAppconfigDeploymentRead(d, meta) } -func resourceAwsAppconfigDeploymentDelete(d *schema.ResourceData, meta interface{}) error { +func resourceAwsAppconfigDeploymentDelete(_ *schema.ResourceData, _ interface{}) error { log.Printf("[WARN] Cannot destroy AppConfig Deployment. Terraform will remove this resource from the state file, however this resource remains.") return nil } + +func resourceAwsAppconfigDeploymentParseID(id string) (string, string, int, error) { + parts := strings.Split(id, "/") + + if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", 0, fmt.Errorf("unexpected format of ID (%q), expected ApplicationID:EnvironmentID:DeploymentNumber", id) + } + + num, err := strconv.Atoi(parts[2]) + if err != nil { + return "", "", 0, fmt.Errorf("error parsing AppConfig Deployment resource ID deployment_number: %w", err) + } + + return parts[0], parts[1], num, nil +} diff --git a/aws/resource_aws_appconfig_deployment_test.go b/aws/resource_aws_appconfig_deployment_test.go index 92fa162daf5..6371d26d329 100644 --- a/aws/resource_aws_appconfig_deployment_test.go +++ b/aws/resource_aws_appconfig_deployment_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -15,20 +16,59 @@ func TestAccAWSAppConfigDeployment_basic(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_appconfig_deployment.test" appResourceName := "aws_appconfig_application.test" + confProfResourceName := "aws_appconfig_configuration_profile.test" + depStrategyResourceName := "aws_appconfig_deployment_strategy.test" + envResourceName := "aws_appconfig_environment.test" + confVersionResourceName := "aws_appconfig_hosted_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), + Providers: testAccProviders, + // AppConfig Deployments cannot be destroyed, but we want to ensure + // the Application and its dependents are removed. + CheckDestroy: testAccCheckAppConfigApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAppConfigDeploymentConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigDeploymentExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appconfig", regexp.MustCompile(`application/[a-z0-9]{4,7}/environment/[a-z0-9]{4,7}/deployment/1`)), + resource.TestCheckResourceAttrPair(resourceName, "application_id", appResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "configuration_profile_id", confProfResourceName, "configuration_profile_id"), + resource.TestCheckResourceAttrPair(resourceName, "configuration_version", confVersionResourceName, "version_number"), + resource.TestCheckResourceAttr(resourceName, "deployment_number", "1"), + resource.TestCheckResourceAttrPair(resourceName, "deployment_strategy_id", depStrategyResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "description", rName), + resource.TestCheckResourceAttrPair(resourceName, "environment_id", envResourceName, "environment_id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppConfigDeployment_Tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appconfig_deployment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, appconfig.EndpointsID), Providers: testAccProviders, - CheckDestroy: testAccCheckAppConfigConfigurationProfileDestroy, + CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: testAccAWSAppConfigDeployment_basic(rName), + Config: testAccAWSAppConfigDeploymentTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAppConfigDeploymentExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttrPair(resourceName, "application_id", appResourceName, "id"), - resource.TestCheckResourceAttr(resourceName, "description", "My test deployment"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -36,6 +76,23 @@ func TestAccAWSAppConfigDeployment_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccAWSAppConfigDeploymentTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigDeploymentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSAppConfigDeploymentTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAppConfigDeploymentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, }, }) } @@ -50,21 +107,25 @@ func testAccCheckAWSAppConfigDeploymentExists(resourceName string) resource.Test if rs.Primary.ID == "" { return fmt.Errorf("Resource (%s) ID not set", resourceName) } - appID, envID, depNum, err := parseAwsAppconfigDeploymentID(rs.Primary.ID) + + appID, envID, deploymentNum, err := resourceAwsAppconfigDeploymentParseID(rs.Primary.ID) + if err != nil { return err } conn := testAccProvider.Meta().(*AWSClient).appconfigconn - output, err := conn.GetDeployment(&appconfig.GetDeploymentInput{ + input := &appconfig.GetDeploymentInput{ ApplicationId: aws.String(appID), + DeploymentNumber: aws.Int64(int64(deploymentNum)), EnvironmentId: aws.String(envID), - DeploymentNumber: aws.Int64(depNum), - }) + } + + output, err := conn.GetDeployment(input) if err != nil { - return fmt.Errorf("error reading AppConfig Deployment (%s): %w", rs.Primary.ID, err) + return fmt.Errorf("error getting Appconfig Deployment (%s): %w", rs.Primary.ID, err) } if output == nil { @@ -75,14 +136,30 @@ func testAccCheckAWSAppConfigDeploymentExists(resourceName string) resource.Test } } -func testAccAWSAppConfigDeployment_basic(rName string) string { - return composeConfig( - testAccAWSAppConfigConfigurationProfileConfigName(rName), - `resource "aws_appconfig_environment" "test" { - name = "test" +func testAccAWSAppConfigDeploymentConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_appconfig_application" "test" { + name = %[1]q +} + +resource "aws_appconfig_environment" "test" { + name = %[1]q application_id = aws_appconfig_application.test.id } +resource "aws_appconfig_configuration_profile" "test" { + application_id = aws_appconfig_application.test.id + name = %[1]q + location_uri = "hosted" +} + +resource "aws_appconfig_deployment_strategy" "test" { + name = %[1]q + deployment_duration_in_minutes = 3 + growth_factor = 10 + replicate_to = "NONE" +} + resource "aws_appconfig_hosted_configuration_version" "test" { application_id = aws_appconfig_application.test.id configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id @@ -91,27 +168,60 @@ resource "aws_appconfig_hosted_configuration_version" "test" { content = jsonencode({ foo = "bar" }) + + description = %[1]q +} +`, rName) +} + +func testAccAWSAppConfigDeploymentConfigName(rName string) string { + return composeConfig( + testAccAWSAppConfigDeploymentConfigBase(rName), + fmt.Sprintf(` +resource "aws_appconfig_deployment" "test"{ + application_id = aws_appconfig_application.test.id + configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id + configuration_version = aws_appconfig_hosted_configuration_version.test.version_number + description = %[1]q + deployment_strategy_id = aws_appconfig_deployment_strategy.test.id + environment_id = aws_appconfig_environment.test.environment_id +} +`, rName)) } -resource "aws_appconfig_deployment" "test" { +func testAccAWSAppConfigDeploymentTags1(rName, tagKey1, tagValue1 string) string { + return composeConfig( + testAccAWSAppConfigDeploymentConfigBase(rName), + fmt.Sprintf(` +resource "aws_appconfig_deployment" "test"{ application_id = aws_appconfig_application.test.id configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id configuration_version = aws_appconfig_hosted_configuration_version.test.version_number deployment_strategy_id = aws_appconfig_deployment_strategy.test.id - description = "My test deployment" environment_id = aws_appconfig_environment.test.environment_id + tags = { - Env = "test" + %[2]q = %[3]q } } -`, +`, rName, tagKey1, tagValue1)) +} + +func testAccAWSAppConfigDeploymentTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return composeConfig( + testAccAWSAppConfigDeploymentConfigBase(rName), fmt.Sprintf(` -resource "aws_appconfig_deployment_strategy" "test" { - name = "%s" - deployment_duration_in_minutes = 0 - final_bake_time_in_minutes = 0 - growth_factor = 100 - replicate_to = "NONE" +resource "aws_appconfig_deployment" "test"{ + application_id = aws_appconfig_application.test.id + configuration_profile_id = aws_appconfig_configuration_profile.test.configuration_profile_id + configuration_version = aws_appconfig_hosted_configuration_version.test.version_number + deployment_strategy_id = aws_appconfig_deployment_strategy.test.id + environment_id = aws_appconfig_environment.test.environment_id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } } -`, rName)) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 570f08ad738..52fbebd020b 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -253,6 +253,7 @@ for more information about connecting to alternate AWS endpoints or AWS compatib - [`aws_apigatewayv2_stage` resource](/docs/providers/aws/r/apigatewayv2_stage.html) - [`aws_appconfig_application` resource](/docs/providers/aws/r/appconfig_application.html) - [`aws_appconfig_configuration_profile` resource](/docs/providers/aws/r/appconfig_configuration_profile.html) + - [`aws_appconfig_deployment` resource](/docs/providers/aws/r/appconfig_deployment.html) - [`aws_appconfig_deployment_strategy` resource](/docs/providers/aws/r/appconfig_deployment_strategy.html) - [`aws_appconfig_environment` resource](/docs/providers/aws/r/appconfig_environment.html) - [`aws_appconfig_hosted_configuration_version` resource](/docs/providers/aws/r/appconfig_hosted_configuration_version.html) diff --git a/website/docs/r/appconfig_deployment.html.markdown b/website/docs/r/appconfig_deployment.html.markdown index 492548e34c9..f24ec39d6c9 100644 --- a/website/docs/r/appconfig_deployment.html.markdown +++ b/website/docs/r/appconfig_deployment.html.markdown @@ -13,15 +13,16 @@ Provides an AppConfig Deployment resource for an [`aws_appconfig_application` re ## Example Usage ```terraform -resource "aws_appconfig_deployment" "test" { +resource "aws_appconfig_deployment" "example" { application_id = aws_appconfig_application.example.id configuration_profile_id = aws_appconfig_configuration_profile.example.configuration_profile_id configuration_version = aws_appconfig_hosted_configuration_version.example.version_number deployment_strategy_id = aws_appconfig_deployment_strategy.example.id - description = "My test deployment" + description = "My example deployment" environment_id = aws_appconfig_environment.example.environment_id + tags = { - Env = "test" + Type = "AppConfig Deployment" } } ``` @@ -34,7 +35,7 @@ The following arguments are supported: * `configuration_profile_id` - (Required, Forces new resource) The configuration profile ID. Must be between 4 and 7 characters in length. * `configuration_version` - (Required, Forces new resource) The configuration version to deploy. Can be at most 1024 characters. * `deployment_strategy_id` - (Required, Forces new resource) The deployment strategy ID. Must be between 4 and 7 characters in length. -* `description` - (Optional, Fources new resource) The description of the deployment. Can be at most 1024 characters. +* `description` - (Optional, Forces new resource) The description of the deployment. Can be at most 1024 characters. * `environment_id` - (Required, Forces new resource) The environment ID. Must be between 4 and 7 characters in length. * `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. @@ -42,14 +43,14 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: -* `arn` - The Amazon Resource Name (ARN) of the AppConfig Environment. -* `id` - The AppConfig application ID and environment ID and deployment number separated by a colon (`/`). +* `id` - The AppConfig application ID, environment ID, and deployment number separated by a slash (`/`). +* `arn` - The Amazon Resource Name (ARN) of the AppConfig Deployment. * `deployment_number` - The deployment number. * `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 Deployment can be imported by using the application ID and environment ID and deployment number separated by a colon (`/`), e.g. +AppConfig Deployments can be imported by using the application ID, environment ID, and deployment number separated by a slash (`/`), e.g. ``` $ terraform import aws_appconfig_deployment.example 71abcde/11xxxxx/1 From 3e93b551b0481f816dabe3cf12b9b14581f2518a Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Thu, 22 Jul 2021 12:13:34 -0400 Subject: [PATCH 12/12] increase creation timeout --- aws/internal/service/appconfig/waiter/waiter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/internal/service/appconfig/waiter/waiter.go b/aws/internal/service/appconfig/waiter/waiter.go index b09cfa81560..a05a262c626 100644 --- a/aws/internal/service/appconfig/waiter/waiter.go +++ b/aws/internal/service/appconfig/waiter/waiter.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -const DeploymentCreatedTimeout = 10 * time.Minute +const DeploymentCreatedTimeout = 20 * time.Minute func DeploymentCreated(conn *appconfig.AppConfig, appID, envID string, deployNum int64) error { stateConf := &resource.StateChangeConf{