From 303a43d8daddcad0af6d42e0c1bda1df3317f15a Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Fri, 8 Mar 2019 10:22:05 +0000 Subject: [PATCH 01/16] Add Codepipeline action region support Adds support for the region parameter of Codepipeline actions. This was added in November: https://aws.amazon.com/about-aws/whats-new/2018/11/aws-codepipeline-now-supports-cross-region-actions/ --- aws/resource_aws_codepipeline.go | 13 +++++++++++++ website/docs/r/codepipeline.markdown | 1 + 2 files changed, 14 insertions(+) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 0234732184eb..4e90ca5a0a65 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -155,6 +155,11 @@ func resourceAwsCodePipeline() *schema.Resource { Optional: true, Computed: true, }, + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, }, }, @@ -319,6 +324,10 @@ func expandAwsCodePipelineActions(s []interface{}) []*codepipeline.ActionDeclara if ro > 0 { action.RunOrder = aws.Int64(int64(ro)) } + r := data["region"].(string) + if r != "" { + action.Region = aws.String(r) + } actions = append(actions, &action) } return actions @@ -360,6 +369,10 @@ func flattenAwsCodePipelineStageActions(actions []*codepipeline.ActionDeclaratio values["run_order"] = int(*action.RunOrder) } + if action.Region != nil { + values["region"] = *action.Region + } + actionsList = append(actionsList, values) } return actionsList diff --git a/website/docs/r/codepipeline.markdown b/website/docs/r/codepipeline.markdown index 386913ca07cf..3bd08d7909bd 100644 --- a/website/docs/r/codepipeline.markdown +++ b/website/docs/r/codepipeline.markdown @@ -190,6 +190,7 @@ A `action` block supports the following arguments: * `output_artifacts` - (Optional) A list of artifact names to output. Output artifact names must be unique within a pipeline. * `role_arn` - (Optional) The ARN of the IAM service role that will perform the declared action. This is assumed through the roleArn for the pipeline. * `run_order` - (Optional) The order in which actions are run. +* `region` - (Optional) The region in which to run the action. ~> **Note:** The input artifact of an action must exactly match the output artifact declared in a preceding action, but the input artifact does not have to be the next action in strict sequence from the action that provided the output artifact. Actions in parallel can declare different output artifacts, which are in turn consumed by different following actions. From 3cb893ee6471bfc0f32af86c08cb963b8a3a15e4 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Fri, 8 Mar 2019 17:28:05 +0000 Subject: [PATCH 02/16] Add support for Codepipeline artifact_stores Adds support for specifying Artifact Stores to enable multi region pipelines. --- aws/resource_aws_codepipeline.go | 106 +++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 4e90ca5a0a65..52166518d5bf 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -40,10 +40,9 @@ func resourceAwsCodePipeline() *schema.Resource { Type: schema.TypeString, Required: true, }, - "artifact_store": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -84,6 +83,59 @@ func resourceAwsCodePipeline() *schema.Resource { }, }, }, + "artifact_stores": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "artifact_store": { + Type: schema.TypeList, + Required: false, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.ArtifactStoreTypeS3, + }, false), + }, + "encryption_key": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.EncryptionKeyTypeKms, + }, false), + }, + }, + }, + }, + }, + }, + }, + "region": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "stage": { Type: schema.TypeList, MinItems: 2, @@ -206,19 +258,28 @@ func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) err } func expandAwsCodePipeline(d *schema.ResourceData) *codepipeline.PipelineDeclaration { - pipelineArtifactStore := expandAwsCodePipelineArtifactStore(d) pipelineStages := expandAwsCodePipelineStages(d) + pipelineArtifactStore := expandAwsCodePipelineArtifactStore(d) + pipelineArtifactStores := expandAwsCodePipelineArtifactStores(d) pipeline := codepipeline.PipelineDeclaration{ - Name: aws.String(d.Get("name").(string)), - RoleArn: aws.String(d.Get("role_arn").(string)), - ArtifactStore: pipelineArtifactStore, - Stages: pipelineStages, + Name: aws.String(d.Get("name").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + ArtifactStore: pipelineArtifactStore, + ArtifactStores: pipelineArtifactStores, + Stages: pipelineStages, } + return &pipeline } + func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.ArtifactStore { configs := d.Get("artifact_store").([]interface{}) + + if configs == nil { + return nil + } + data := configs[0].(map[string]interface{}) pipelineArtifactStore := codepipeline.ArtifactStore{ Location: aws.String(data["location"].(string)), @@ -236,6 +297,37 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar return &pipelineArtifactStore } +func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { + configs := d.Get("artifact_stores").([]interface{}) + + if configs == nil { + return nil + } + + pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) + + for _, config := range configs { + data := config.(map[string]interface{}) + pipelineArtifactStores[data["region"].(string)] = &codepipeline.ArtifactStore{ + Location: aws.String(data["location"].(string)), + Type: aws.String(data["type"].(string)), + } + + tek := data["encryption_key"].([]interface{}) + + if len(tek) > 0 { + vk := tek[0].(map[string]interface{}) + ek := codepipeline.EncryptionKey{ + Type: aws.String(vk["type"].(string)), + Id: aws.String(vk["id"].(string)), + } + pipelineArtifactStores[data["region"].(string)].EncryptionKey = &ek + } + } + + return pipelineArtifactStores +} + func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactStore) []interface{} { values := map[string]interface{}{} values["type"] = *artifactStore.Type From 629a5aa22c4d62d9dc79eddcbe8c653c5f853e13 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Wed, 19 Feb 2020 23:50:00 +0000 Subject: [PATCH 03/16] One definition of the artifactStoreSchema --- aws/resource_aws_codepipeline.go | 124 ++++++++++--------------------- 1 file changed, 39 insertions(+), 85 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 52166518d5bf..f9aba663be0d 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -15,6 +15,43 @@ import ( ) func resourceAwsCodePipeline() *schema.Resource { + var artifactStoreSchema = map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.ArtifactStoreTypeS3, + }, false), + }, + + "encryption_key": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.EncryptionKeyTypeKms, + }, false), + }, + }, + }, + }, + } + return &schema.Resource{ Create: resourceAwsCodePipelineCreate, Read: resourceAwsCodePipelineRead, @@ -44,96 +81,13 @@ func resourceAwsCodePipeline() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "location": { - Type: schema.TypeString, - Required: true, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.ArtifactStoreTypeS3, - }, false), - }, - - "encryption_key": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.EncryptionKeyTypeKms, - }, false), - }, - }, - }, - }, - }, - }, + Elem: artifactStoreSchema, }, "artifact_stores": { Type: schema.TypeMap, Optional: true, Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "artifact_store": { - Type: schema.TypeList, - Required: false, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "location": { - Type: schema.TypeString, - Required: true, - }, - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.ArtifactStoreTypeS3, - }, false), - }, - "encryption_key": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.EncryptionKeyTypeKms, - }, false), - }, - }, - }, - }, - }, - }, - }, - "region": { - Type: schema.TypeString, - Required: true, - }, - }, + Schema: artifactStoreSchema, }, }, "stage": { From 9df6d5a90b51a7f436daadff8ca88c972b4e939d Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Thu, 20 Feb 2020 00:05:49 +0000 Subject: [PATCH 04/16] Use expandAwsCodePipelineArtifactStore to expand artifactStores --- aws/resource_aws_codepipeline.go | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index f9aba663be0d..5877336b8bf3 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -252,7 +252,7 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar } func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { - configs := d.Get("artifact_stores").([]interface{}) + configs := d.Get("artifact_stores").(map[string]*schema.ResourceData) if configs == nil { return nil @@ -260,23 +260,8 @@ func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*cod pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) - for _, config := range configs { - data := config.(map[string]interface{}) - pipelineArtifactStores[data["region"].(string)] = &codepipeline.ArtifactStore{ - Location: aws.String(data["location"].(string)), - Type: aws.String(data["type"].(string)), - } - - tek := data["encryption_key"].([]interface{}) - - if len(tek) > 0 { - vk := tek[0].(map[string]interface{}) - ek := codepipeline.EncryptionKey{ - Type: aws.String(vk["type"].(string)), - Id: aws.String(vk["id"].(string)), - } - pipelineArtifactStores[data["region"].(string)].EncryptionKey = &ek - } + for region, config := range configs { + pipelineArtifactStores[region] = expandAwsCodePipelineArtifactStore(config) } return pipelineArtifactStores From 0d78dfa46fd77538c16da68fe4d2ed4fa9c83838 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Thu, 20 Feb 2020 17:51:16 +0000 Subject: [PATCH 05/16] [WIP] Test for multi region codepipeline. --- aws/resource_aws_codepipeline_test.go | 172 ++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 22bb36b486a1..7496a07a3e5a 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -182,6 +182,34 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { }) } +func TestAccAWSCodePipeline_basic_multiregion(t *testing.T) { + if os.Getenv("GITHUB_TOKEN") == "" { + t.Skip("Environment variable GITHUB_TOKEN is not set") + } + + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists("aws_codepipeline.bar"), + resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.name", "Build"), + resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.0.region", "eu-west-1"), + resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.1.region", "eu-central-1"), + resource.TestMatchResourceAttr( + "aws_codepipeline.bar", "stage.2.action.0.role_arn", + regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/codepipeline-action-role.*")), + ), + }, + }, + }) +} + func testAccCheckAWSCodePipelineExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -876,3 +904,147 @@ resource "aws_codepipeline" "test" { } `, rName, tag1, tag2) } + +func testAccAWSCodePipelineConfig_multiregion(rName string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "eu-west-1" { + bucket = "tf-test-pipeline-eu-west-1-%s" + acl = "private" +} + +resource "aws_s3_bucket" "eu-central-1" { + bucket = "tf-test-pipeline-eu-central-1-%s" + acl = "private" +} + +resource "aws_iam_role" "codepipeline_role" { + name = "codepipeline-role-%s" + + assume_role_policy = < Date: Thu, 26 Mar 2020 14:23:06 -0700 Subject: [PATCH 06/16] Fixes and test updates --- aws/resource_aws_codepipeline.go | 30 +- aws/resource_aws_codepipeline_test.go | 788 ++++++++---------- aws/resource_aws_codepipeline_webhook_test.go | 21 - 3 files changed, 364 insertions(+), 475 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 5877336b8bf3..0ce759c76192 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -81,10 +81,12 @@ func resourceAwsCodePipeline() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - Elem: artifactStoreSchema, + Elem: &schema.Resource{ + Schema: artifactStoreSchema, + }, }, "artifact_stores": { - Type: schema.TypeMap, + Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: artifactStoreSchema, @@ -108,6 +110,7 @@ func resourceAwsCodePipeline() *schema.Resource { "configuration": { Type: schema.TypeMap, Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, "category": { Type: schema.TypeString, @@ -230,7 +233,7 @@ func expandAwsCodePipeline(d *schema.ResourceData) *codepipeline.PipelineDeclara func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.ArtifactStore { configs := d.Get("artifact_store").([]interface{}) - if configs == nil { + if len(configs) == 0 { return nil } @@ -252,17 +255,17 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar } func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { - configs := d.Get("artifact_stores").(map[string]*schema.ResourceData) + configs := d.Get("artifact_stores").([]interface{}) - if configs == nil { + if len(configs) == 0 { return nil } pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) - for region, config := range configs { - pipelineArtifactStores[region] = expandAwsCodePipelineArtifactStore(config) - } + // for region, config := range configs { + // pipelineArtifactStores[region] = expandAwsCodePipelineArtifactStore(config.(*schema.ResourceData)) + // } return pipelineArtifactStores } @@ -281,6 +284,14 @@ func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactSto return []interface{}{values} } +func flattenAwsCodePipelineArtifactStores(artifactStores map[string]*codepipeline.ArtifactStore) []interface{} { + values := []interface{}{} + for _, artifactStore := range artifactStores { + values = append(values, artifactStore) + } + return values +} + func expandAwsCodePipelineStages(d *schema.ResourceData) []*codepipeline.StageDeclaration { configs := d.Get("stage").([]interface{}) pipelineStages := []*codepipeline.StageDeclaration{} @@ -490,6 +501,9 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error if err := d.Set("artifact_store", flattenAwsCodePipelineArtifactStore(pipeline.ArtifactStore)); err != nil { return err } + if err := d.Set("artifact_stores", flattenAwsCodePipelineArtifactStores(pipeline.ArtifactStores)); err != nil { + return err + } if err := d.Set("stage", flattenAwsCodePipelineStages(pipeline.Stages)); err != nil { return err diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 7496a07a3e5a..8c35e5d035b5 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -14,10 +14,6 @@ import ( ) func TestAccAWSCodePipeline_basic(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -30,11 +26,49 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { Config: testAccAWSCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName), - resource.TestMatchResourceAttr(resourceName, "arn", - regexp.MustCompile(fmt.Sprintf("^arn:aws:codepipeline:[^:]+:[0-9]{12}:test-pipeline-%s", name))), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "1234"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), + + resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.category", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.owner", "ThirdParty"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.provider", "GitHub"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.version", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.input_artifacts.#", "0"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.0", "test"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.%", "3"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Owner", "lifesum-terraform"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Repo", "test"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Branch", "master"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.role_arn", ""), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.run_order", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.region", ""), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.category", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.owner", "AWS"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.provider", "CodeBuild"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.version", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.0", "test"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.output_artifacts.#", "0"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.%", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "test"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.role_arn", ""), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.run_order", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", ""), ), }, { @@ -47,8 +81,30 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "4567"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), + + resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.input_artifacts.#", "0"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.0", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.%", "3"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Owner", "foo-terraform"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Repo", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Branch", "stable"), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.0", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.%", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "foo"), ), }, }, @@ -56,10 +112,6 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { } func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -72,17 +124,10 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { Config: testAccAWSCodePipelineConfig_emptyArtifacts(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName), - resource.TestMatchResourceAttr(resourceName, "arn", - regexp.MustCompile(fmt.Sprintf("^arn:aws:codepipeline:[^:]+:[0-9]{12}:test-pipeline-%s", name))), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.category", "Build"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.owner", "AWS"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.provider", "CodeBuild"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.#", "1"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.output_artifacts.#", "0"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "0"), ), ExpectNonEmptyPlan: true, }, @@ -96,10 +141,6 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { } func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -114,9 +155,7 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName), resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Deploy"), resource.TestCheckResourceAttr(resourceName, "stage.2.action.0.category", "Deploy"), - resource.TestMatchResourceAttr( - resourceName, "stage.2.action.0.role_arn", - regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/codepipeline-action-role.*")), + resource.TestCheckResourceAttrPair(resourceName, "stage.2.action.0.role_arn", "aws_iam_role.codepipeline_action_role", "arn"), ), }, { @@ -129,10 +168,6 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { } func TestAccAWSCodePipeline_tags(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -182,33 +217,32 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { }) } -func TestAccAWSCodePipeline_basic_multiregion(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - - name := acctest.RandString(10) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCodePipelineDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCodePipelineConfig_multiregion(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists("aws_codepipeline.bar"), - resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.name", "Build"), - resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.0.region", "eu-west-1"), - resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.1.region", "eu-central-1"), - resource.TestMatchResourceAttr( - "aws_codepipeline.bar", "stage.2.action.0.role_arn", - regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/codepipeline-action-role.*")), - ), - }, - }, - }) -} +// func TestAccAWSCodePipeline_basic_multiregion(t *testing.T) { +// name := acctest.RandString(10) + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { testAccPreCheck(t) }, +// Providers: testAccProviders, +// CheckDestroy: testAccCheckAWSCodePipelineDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccAWSCodePipelineConfig_multiregion(name), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckAWSCodePipelineExists("aws_codepipeline.bar"), +// resource.TestCheckResourceAttr("aws_codepipeline.bar", "artifact_stores.%", "2"), +// resource.TestCheckNoResourceAttr("aws_codepipeline.bar", "artifact_store.#"), +// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.name", "Build"), +// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.0.region", "eu-west-1"), +// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.1.region", "eu-central-1"), +// resource.TestMatchResourceAttr( +// "aws_codepipeline.bar", "stage.2.action.0.role_arn", +// regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/codepipeline-action-role.*"), +// ), +// ), +// }, +// }, +// }) +// } func testAccCheckAWSCodePipelineExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -253,6 +287,10 @@ func testAccCheckAWSCodePipelineDestroy(s *terraform.State) error { } func testAccPreCheckAWSCodePipeline(t *testing.T) { + if os.Getenv("GITHUB_TOKEN") == "" { + t.Skip("Environment variable GITHUB_TOKEN is not set") + } + conn := testAccProvider.Meta().(*AWSClient).codepipelineconn input := &codepipeline.ListPipelinesInput{} @@ -268,13 +306,17 @@ func testAccPreCheckAWSCodePipeline(t *testing.T) { } } -func testAccAWSCodePipelineConfig_basic(rName string) string { +func testAccAWSCodePipelineS3Bucket(rName string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "foo" { bucket = "tf-test-pipeline-%s" acl = "private" } +`, rName) +} +func testAccAWSCodePipelineServiceIAMRole(rName string) string { + return fmt.Sprintf(` resource "aws_iam_role" "codepipeline_role" { name = "codepipeline-role-%s" @@ -326,67 +368,11 @@ resource "aws_iam_role_policy" "codepipeline_policy" { } EOF } - -resource "aws_codepipeline" "test" { - name = "test-pipeline-%s" - role_arn = "${aws_iam_role.codepipeline_role.arn}" - - artifact_store { - location = "${aws_s3_bucket.foo.bucket}" - type = "S3" - - encryption_key { - id = "1234" - type = "KMS" - } - } - - stage { - name = "Source" - - action { - name = "Source" - category = "Source" - owner = "ThirdParty" - provider = "GitHub" - version = "1" - output_artifacts = ["test"] - - configuration = { - Owner = "lifesum-terraform" - Repo = "test" - Branch = "master" - } - } - } - - stage { - name = "Build" - - action { - name = "Build" - category = "Build" - owner = "AWS" - provider = "CodeBuild" - input_artifacts = ["test"] - version = "1" - - configuration = { - ProjectName = "test" - } - } - } -} -`, rName, rName, rName) +`, rName) } -func testAccAWSCodePipelineConfig_basicUpdated(rName string) string { +func testAccAWSCodePipelineServiceIAMRoleWithAssumeRole(rName string) string { return fmt.Sprintf(` -resource "aws_s3_bucket" "foo" { - bucket = "tf-test-pipeline-%s" - acl = "private" -} - resource "aws_iam_role" "codepipeline_role" { name = "codepipeline-role-%s" @@ -433,12 +419,26 @@ resource "aws_iam_role_policy" "codepipeline_policy" { "codebuild:StartBuild" ], "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "sts:AssumeRole" + ], + "Resource": "${aws_iam_role.codepipeline_action_role.arn}" } ] } EOF } +`, rName) +} +func testAccAWSCodePipelineConfig_basic(rName string) string { + return composeConfig( + testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineServiceIAMRole(rName), + fmt.Sprintf(` resource "aws_codepipeline" "test" { name = "test-pipeline-%s" role_arn = "${aws_iam_role.codepipeline_role.arn}" @@ -448,7 +448,7 @@ resource "aws_codepipeline" "test" { type = "S3" encryption_key { - id = "4567" + id = "1234" type = "KMS" } } @@ -462,12 +462,12 @@ resource "aws_codepipeline" "test" { owner = "ThirdParty" provider = "GitHub" version = "1" - output_artifacts = ["bar"] + output_artifacts = ["test"] configuration = { - Owner = "foo-terraform" - Repo = "bar" - Branch = "stable" + Owner = "lifesum-terraform" + Repo = "test" + Branch = "master" } } } @@ -480,77 +480,81 @@ resource "aws_codepipeline" "test" { category = "Build" owner = "AWS" provider = "CodeBuild" - input_artifacts = ["bar"] + input_artifacts = ["test"] version = "1" configuration = { - ProjectName = "foo" + ProjectName = "test" } } } } -`, rName, rName, rName) +`, rName)) } -func testAccAWSCodePipelineConfig_emptyArtifacts(rName string) string { - return fmt.Sprintf(` -resource "aws_s3_bucket" "foo" { - bucket = "tf-test-pipeline-%s" - acl = "private" -} +func testAccAWSCodePipelineConfig_basicUpdated(rName string) string { + return composeConfig( + testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineServiceIAMRole(rName), + fmt.Sprintf(` +resource "aws_codepipeline" "test" { + name = "test-pipeline-%s" + role_arn = "${aws_iam_role.codepipeline_role.arn}" -resource "aws_iam_role" "codepipeline_role" { - name = "codepipeline-role-%s" + artifact_store { + location = "${aws_s3_bucket.foo.bucket}" + type = "S3" - assume_role_policy = < Date: Thu, 26 Mar 2020 16:21:07 -0700 Subject: [PATCH 07/16] Corrects error in destroy check --- aws/resource_aws_codepipeline_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 8c35e5d035b5..5c48ca499d21 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -280,10 +280,13 @@ func testAccCheckAWSCodePipelineDestroy(s *terraform.State) error { if err == nil { return fmt.Errorf("Expected AWS CodePipeline to be gone, but was still found") } - return nil + if isAWSErr(err, "PipelineNotFoundException", "") { + return nil + } + return err } - return fmt.Errorf("Default error in CodePipeline Test") + return nil } func testAccPreCheckAWSCodePipeline(t *testing.T) { From 26c5417565c4421a7e7bf31725ff3e0a27201670 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 27 Mar 2020 17:24:08 -0700 Subject: [PATCH 08/16] Removes redundant CodePipelineExists test from CodePipleline webhook tests --- aws/resource_aws_codepipeline_webhook_test.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/aws/resource_aws_codepipeline_webhook_test.go b/aws/resource_aws_codepipeline_webhook_test.go index a1cb4dce10fa..bda745d7ccdf 100644 --- a/aws/resource_aws_codepipeline_webhook_test.go +++ b/aws/resource_aws_codepipeline_webhook_test.go @@ -15,7 +15,6 @@ func TestAccAWSCodePipelineWebhook_basic(t *testing.T) { var v codepipeline.ListWebhookItem rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_codepipeline_webhook.test" - pipelineResourceName := "aws_codepipeline.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, @@ -25,7 +24,6 @@ func TestAccAWSCodePipelineWebhook_basic(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), @@ -46,7 +44,6 @@ func TestAccAWSCodePipelineWebhook_ipAuth(t *testing.T) { var v codepipeline.ListWebhookItem rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_codepipeline_webhook.test" - pipelineResourceName := "aws_codepipeline.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, @@ -56,7 +53,6 @@ func TestAccAWSCodePipelineWebhook_ipAuth(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfig_ipAuth(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), @@ -77,7 +73,6 @@ func TestAccAWSCodePipelineWebhook_unauthenticated(t *testing.T) { var v codepipeline.ListWebhookItem rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_codepipeline_webhook.test" - pipelineResourceName := "aws_codepipeline.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, @@ -87,7 +82,6 @@ func TestAccAWSCodePipelineWebhook_unauthenticated(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfig_unauthenticated(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), @@ -106,7 +100,6 @@ func TestAccAWSCodePipelineWebhook_tags(t *testing.T) { var v1, v2, v3 codepipeline.ListWebhookItem rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_codepipeline_webhook.test" - pipelineResourceName := "aws_codepipeline.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, @@ -116,7 +109,6 @@ func TestAccAWSCodePipelineWebhook_tags(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1value", "tag2value"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), @@ -127,7 +119,6 @@ func TestAccAWSCodePipelineWebhook_tags(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1valueUpdate", "tag2valueUpdate"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), @@ -149,7 +140,6 @@ func TestAccAWSCodePipelineWebhook_tags(t *testing.T) { { Config: testAccAWSCodePipelineWebhookConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), func(s *terraform.State) error { @@ -168,7 +158,6 @@ func TestAccAWSCodePipelineWebhook_UpdateAuthenticationConfiguration_SecretToken var v1, v2 codepipeline.ListWebhookItem rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_codepipeline_webhook.test" - pipelineResourceName := "aws_codepipeline.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, @@ -178,7 +167,6 @@ func TestAccAWSCodePipelineWebhook_UpdateAuthenticationConfiguration_SecretToken { Config: testAccAWSCodePipelineWebhookConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), @@ -189,7 +177,6 @@ func TestAccAWSCodePipelineWebhook_UpdateAuthenticationConfiguration_SecretToken { Config: testAccAWSCodePipelineWebhookConfig_secretTokenUpdated(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(pipelineResourceName), testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), From e8f9097fbcb7e4ad6d99231b81407bb27ef1aa66 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 27 Mar 2020 17:39:46 -0700 Subject: [PATCH 09/16] Handles creation and import of cross-region CodePipeline actions --- aws/resource_aws_codepipeline.go | 65 +++-- aws/resource_aws_codepipeline_test.go | 398 ++++++++++++++------------ 2 files changed, 261 insertions(+), 202 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 0ce759c76192..5b6e46c80d1a 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -50,6 +51,10 @@ func resourceAwsCodePipeline() *schema.Resource { }, }, }, + "region": { + Type: schema.TypeString, + Optional: true, + }, } return &schema.Resource{ @@ -86,11 +91,12 @@ func resourceAwsCodePipeline() *schema.Resource { }, }, "artifact_stores": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: artifactStoreSchema, }, + Set: resourceAwsCodePipelineArtifactStoreHash, }, "stage": { Type: schema.TypeList, @@ -237,7 +243,29 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar return nil } - data := configs[0].(map[string]interface{}) + _, pipelineArtifactStore := expandAwsCodePipelineArtifactStoreData(configs[0].(map[string]interface{})) + + return pipelineArtifactStore +} + +func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { + configs := d.Get("artifact_stores").(*schema.Set).List() + + if len(configs) == 0 { + return nil + } + + pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) + + for _, config := range configs { + region, store := expandAwsCodePipelineArtifactStoreData(config.(map[string]interface{})) + pipelineArtifactStores[region] = store + } + + return pipelineArtifactStores +} + +func expandAwsCodePipelineArtifactStoreData(data map[string]interface{}) (string, *codepipeline.ArtifactStore) { pipelineArtifactStore := codepipeline.ArtifactStore{ Location: aws.String(data["location"].(string)), Type: aws.String(data["type"].(string)), @@ -251,26 +279,15 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar } pipelineArtifactStore.EncryptionKey = &ek } - return &pipelineArtifactStore -} - -func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { - configs := d.Get("artifact_stores").([]interface{}) - - if len(configs) == 0 { - return nil - } - pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) - - // for region, config := range configs { - // pipelineArtifactStores[region] = expandAwsCodePipelineArtifactStore(config.(*schema.ResourceData)) - // } - - return pipelineArtifactStores + return data["region"].(string), &pipelineArtifactStore } func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactStore) []interface{} { + if artifactStore == nil { + return []interface{}{} + } + values := map[string]interface{}{} values["type"] = *artifactStore.Type values["location"] = *artifactStore.Location @@ -286,8 +303,10 @@ func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactSto func flattenAwsCodePipelineArtifactStores(artifactStores map[string]*codepipeline.ArtifactStore) []interface{} { values := []interface{}{} - for _, artifactStore := range artifactStores { - values = append(values, artifactStore) + for region, artifactStore := range artifactStores { + store := flattenAwsCodePipelineArtifactStore(artifactStore)[0].(map[string]interface{}) + store["region"] = region + values = append(values, store) } return values } @@ -571,3 +590,9 @@ func resourceAwsCodePipelineDelete(d *schema.ResourceData, meta interface{}) err return err } + +func resourceAwsCodePipelineArtifactStoreHash(v interface{}) int { + m := v.(map[string]interface{}) + + return hashcode.String(m["region"].(string)) +} diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 5c48ca499d21..8fa139b22f80 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -9,11 +9,14 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" ) func TestAccAWSCodePipeline_basic(t *testing.T) { + var p1, p2 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -25,11 +28,12 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { { Config: testAccAWSCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), + resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.foo", "bucket"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "1234"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), @@ -79,7 +83,7 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { { Config: testAccAWSCodePipelineConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "4567"), @@ -112,6 +116,7 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { } func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -123,7 +128,7 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { { Config: testAccAWSCodePipelineConfig_emptyArtifacts(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), @@ -141,6 +146,7 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { } func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -152,7 +158,7 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { { Config: testAccAWSCodePipelineConfig_deployWithServiceRole(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Deploy"), resource.TestCheckResourceAttr(resourceName, "stage.2.action.0.category", "Deploy"), resource.TestCheckResourceAttrPair(resourceName, "stage.2.action.0.role_arn", "aws_iam_role.codepipeline_action_role", "arn"), @@ -168,6 +174,7 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { } func TestAccAWSCodePipeline_tags(t *testing.T) { + var p1, p2, p3 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -179,7 +186,7 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { { Config: testAccAWSCodePipelineConfigWithTags(name, "tag1value", "tag2value"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("test-pipeline-%s", name)), resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"), @@ -194,7 +201,7 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { { Config: testAccAWSCodePipelineConfigWithTags(name, "tag1valueUpdate", "tag2valueUpdate"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("test-pipeline-%s", name)), resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1valueUpdate"), @@ -209,7 +216,7 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { { Config: testAccAWSCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -217,34 +224,64 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { }) } -// func TestAccAWSCodePipeline_basic_multiregion(t *testing.T) { -// name := acctest.RandString(10) - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { testAccPreCheck(t) }, -// Providers: testAccProviders, -// CheckDestroy: testAccCheckAWSCodePipelineDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccAWSCodePipelineConfig_multiregion(name), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckAWSCodePipelineExists("aws_codepipeline.bar"), -// resource.TestCheckResourceAttr("aws_codepipeline.bar", "artifact_stores.%", "2"), -// resource.TestCheckNoResourceAttr("aws_codepipeline.bar", "artifact_store.#"), -// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.name", "Build"), -// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.0.region", "eu-west-1"), -// resource.TestCheckResourceAttr("aws_codepipeline.bar", "stage.2.action.1.region", "eu-central-1"), -// resource.TestMatchResourceAttr( -// "aws_codepipeline.bar", "stage.2.action.0.role_arn", -// regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/codepipeline-action-role.*"), -// ), -// ), -// }, -// }, -// }) -// } - -func testAccCheckAWSCodePipelineExists(n string) resource.TestCheckFunc { +func testAccCheckAWSCodePipelineHashArtifactStore(region string) int { + return hashcode.String(region) +} + +func testAccCheckAWSCodePipelineHashArtifactStoreKey(region, key string) string { + return fmt.Sprintf("artifact_stores.%d.%s", testAccCheckAWSCodePipelineHashArtifactStore(region), key) +} + +func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { + var p codepipeline.PipelineDeclaration + resourceName := "aws_codepipeline.test" + var providers []*schema.Provider + + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + + resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "type"), "S3"), + resource.TestCheckResourceAttrPair(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "location"), "aws_s3_bucket.local", "bucket"), + resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "region"), testAccGetRegion()), + + resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "type"), "S3"), + resource.TestCheckResourceAttrPair(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "location"), "aws_s3_bucket.alternate", "bucket"), + resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "region"), testAccGetAlternateRegion()), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-Build", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-Build", testAccGetAlternateRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), + ), + }, + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSCodePipelineExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -257,11 +294,16 @@ func testAccCheckAWSCodePipelineExists(n string) resource.TestCheckFunc { conn := testAccProvider.Meta().(*AWSClient).codepipelineconn - _, err := conn.GetPipeline(&codepipeline.GetPipelineInput{ + out, err := conn.GetPipeline(&codepipeline.GetPipelineInput{ Name: aws.String(rs.Primary.ID), }) + if err != nil { + return err + } - return err + *pipeline = *out.Pipeline + + return nil } } @@ -802,148 +844,140 @@ resource "aws_codepipeline" "test" { `, rName, tag1, tag2)) } -// func testAccAWSCodePipelineConfig_multiregion(rName string) string { -// return fmt.Sprintf(` -// resource "aws_s3_bucket" "eu-west-1" { -// bucket = "tf-test-pipeline-eu-west-1-%s" -// acl = "private" -// } - -// resource "aws_s3_bucket" "eu-central-1" { -// bucket = "tf-test-pipeline-eu-central-1-%s" -// acl = "private" -// } - -// resource "aws_iam_role" "codepipeline_role" { -// name = "codepipeline-role-%s" - -// assume_role_policy = < Date: Mon, 30 Mar 2020 10:59:24 -0700 Subject: [PATCH 10/16] Removes "foo" and "bar" --- aws/resource_aws_codepipeline_test.go | 60 +++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 8fa139b22f80..976532de3b38 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -33,7 +33,7 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.foo", "bucket"), + resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "1234"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), @@ -96,19 +96,19 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.name", "Source"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.input_artifacts.#", "0"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.#", "1"), - resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.0", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.output_artifacts.0", "artifacts"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.%", "3"), - resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Owner", "foo-terraform"), - resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Repo", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Owner", "test-terraform"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Repo", "test-repo"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.Branch", "stable"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.#", "1"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.0", "bar"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.input_artifacts.0", "artifacts"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.%", "1"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "foo"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "test"), ), }, }, @@ -397,8 +397,8 @@ resource "aws_iam_role_policy" "codepipeline_policy" { "s3:GetBucketVersioning" ], "Resource": [ - "${aws_s3_bucket.foo.arn}", - "${aws_s3_bucket.foo.arn}/*" + "${aws_s3_bucket.test.arn}", + "${aws_s3_bucket.test.arn}/*" ] }, { @@ -453,8 +453,8 @@ resource "aws_iam_role_policy" "codepipeline_policy" { "s3:GetBucketVersioning" ], "Resource": [ - "${aws_s3_bucket.foo.arn}", - "${aws_s3_bucket.foo.arn}/*" + "${aws_s3_bucket.test.arn}", + "${aws_s3_bucket.test.arn}/*" ] }, { @@ -489,7 +489,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_store { - location = "${aws_s3_bucket.foo.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { @@ -547,7 +547,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_store { - location = "${aws_s3_bucket.foo.bucket}" + location = "${aws_s3_bucket.updated.bucket}" type = "S3" encryption_key { @@ -565,11 +565,11 @@ resource "aws_codepipeline" "test" { owner = "ThirdParty" provider = "GitHub" version = "1" - output_artifacts = ["bar"] + output_artifacts = ["artifacts"] configuration = { - Owner = "foo-terraform" - Repo = "bar" + Owner = "test-terraform" + Repo = "test-repo" Branch = "stable" } } @@ -583,11 +583,11 @@ resource "aws_codepipeline" "test" { category = "Build" owner = "AWS" provider = "CodeBuild" - input_artifacts = ["bar"] + input_artifacts = ["artifacts"] version = "1" configuration = { - ProjectName = "foo" + ProjectName = "test" } } } @@ -605,7 +605,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_store { - location = "${aws_s3_bucket.foo.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" } @@ -688,8 +688,8 @@ resource "aws_iam_role_policy" "codepipeline_action_policy" { "s3:GetBucketVersioning" ], "Resource": [ - "${aws_s3_bucket.foo.arn}", - "${aws_s3_bucket.foo.arn}/*" + "${aws_s3_bucket.test.arn}", + "${aws_s3_bucket.test.arn}/*" ] } ] @@ -710,7 +710,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_store { - location = "${aws_s3_bucket.foo.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { @@ -728,11 +728,11 @@ resource "aws_codepipeline" "test" { owner = "ThirdParty" provider = "GitHub" version = "1" - output_artifacts = ["bar"] + output_artifacts = ["artifacts"] configuration = { - Owner = "foo-terraform" - Repo = "bar" + Owner = "test-terraform" + Repo = "test-repo" Branch = "stable" } } @@ -746,12 +746,12 @@ resource "aws_codepipeline" "test" { category = "Build" owner = "AWS" provider = "CodeBuild" - input_artifacts = ["bar"] - output_artifacts = ["baz"] + input_artifacts = ["artifacts"] + output_artifacts = ["artifacts2"] version = "1" configuration = { - ProjectName = "foo" + ProjectName = "test" } } } @@ -764,7 +764,7 @@ resource "aws_codepipeline" "test" { category = "Deploy" owner = "AWS" provider = "CloudFormation" - input_artifacts = ["baz"] + input_artifacts = ["artifacts2"] role_arn = "${aws_iam_role.codepipeline_action_role.arn}" version = "1" @@ -772,7 +772,7 @@ resource "aws_codepipeline" "test" { ActionMode = "CHANGE_SET_REPLACE" ChangeSetName = "changeset" StackName = "stack" - TemplatePath = "baz::template.yaml" + TemplatePath = "artifacts2::template.yaml" } } } @@ -790,7 +790,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_store { - location = "${aws_s3_bucket.foo.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { From 79de40385b4db08b5e190627d5b3833d868a2648 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 30 Mar 2020 11:05:42 -0700 Subject: [PATCH 11/16] Adds test for changing artifact store location --- aws/resource_aws_codepipeline_test.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 976532de3b38..0a6075c83e7f 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -85,6 +85,7 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), + resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.updated", "bucket"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "4567"), resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), @@ -351,13 +352,17 @@ func testAccPreCheckAWSCodePipeline(t *testing.T) { } } -func testAccAWSCodePipelineS3Bucket(rName string) string { +func testAccAWSCodePipelineS3DefaultBucket(rName string) string { + return testAccAWSCodePipelineS3Bucket("test", rName) +} + +func testAccAWSCodePipelineS3Bucket(bucket, rName string) string { return fmt.Sprintf(` -resource "aws_s3_bucket" "foo" { - bucket = "tf-test-pipeline-%s" +resource "aws_s3_bucket" "%[1]s" { + bucket = "tf-test-pipeline-%[1]s-%[2]s" acl = "private" } -`, rName) +`, bucket, rName) } func testAccAWSCodePipelineServiceIAMRole(rName string) string { @@ -481,7 +486,7 @@ EOF func testAccAWSCodePipelineConfig_basic(rName string) string { return composeConfig( - testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineServiceIAMRole(rName), fmt.Sprintf(` resource "aws_codepipeline" "test" { @@ -539,7 +544,8 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfig_basicUpdated(rName string) string { return composeConfig( - testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineS3DefaultBucket(rName), + testAccAWSCodePipelineS3Bucket("updated", rName), testAccAWSCodePipelineServiceIAMRole(rName), fmt.Sprintf(` resource "aws_codepipeline" "test" { @@ -597,7 +603,7 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfig_emptyArtifacts(rName string) string { return composeConfig( - testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineServiceIAMRole(rName), fmt.Sprintf(` resource "aws_codepipeline" "test" { @@ -701,7 +707,7 @@ EOF func testAccAWSCodePipelineConfig_deployWithServiceRole(rName string) string { return composeConfig( - testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineServiceIAMRoleWithAssumeRole(rName), testAccAWSCodePipelineDeployActionIAMRole(rName), fmt.Sprintf(` @@ -782,7 +788,7 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfigWithTags(rName, tag1, tag2 string) string { return composeConfig( - testAccAWSCodePipelineS3Bucket(rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineServiceIAMRole(rName), fmt.Sprintf(` resource "aws_codepipeline" "test" { From 5cd958cbdccbd71cc27b785300d49c35298b64ee Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 30 Mar 2020 15:00:35 -0700 Subject: [PATCH 12/16] Properly hashes artifact stores --- aws/resource_aws_codepipeline.go | 1 - aws/resource_aws_codepipeline_test.go | 273 ++++++++++++++++++++++---- 2 files changed, 230 insertions(+), 44 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 5b6e46c80d1a..4beb11074469 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -96,7 +96,6 @@ func resourceAwsCodePipeline() *schema.Resource { Elem: &schema.Resource{ Schema: artifactStoreSchema, }, - Set: resourceAwsCodePipelineArtifactStoreHash, }, "stage": { Type: schema.TypeList, diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 0a6075c83e7f..0a14777654ea 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -9,10 +9,10 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/flatmap" ) func TestAccAWSCodePipeline_basic(t *testing.T) { @@ -225,14 +225,6 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { }) } -func testAccCheckAWSCodePipelineHashArtifactStore(region string) int { - return hashcode.String(region) -} - -func testAccCheckAWSCodePipelineHashArtifactStoreKey(region, key string) string { - return fmt.Sprintf("artifact_stores.%d.%s", testAccCheckAWSCodePipelineHashArtifactStore(region), key) -} - func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { var p codepipeline.PipelineDeclaration resourceName := "aws_codepipeline.test" @@ -256,13 +248,11 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "type"), "S3"), - resource.TestCheckResourceAttrPair(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "location"), "aws_s3_bucket.local", "bucket"), - resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetRegion(), "region"), testAccGetRegion()), + testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetRegion(), "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.local", "bucket"), - resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "type"), "S3"), - resource.TestCheckResourceAttrPair(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "location"), "aws_s3_bucket.alternate", "bucket"), - resource.TestCheckResourceAttr(resourceName, testAccCheckAWSCodePipelineHashArtifactStoreKey(testAccGetAlternateRegion(), "region"), testAccGetAlternateRegion()), + testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetAlternateRegion(), "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetAlternateRegion(), "location", "aws_s3_bucket.alternate", "bucket"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), @@ -282,6 +272,61 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { }) } +func testAccCheckAWSCodePipelineArtifactStoresAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + as, ok := p.ArtifactStores[region] + if !ok { + return fmt.Errorf("Artifact Store for region %q not found", region) + } + values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) + + if v, ok := values[key]; !ok || v != value { + if !ok { + return fmt.Errorf("ArtifactStores[%s]: Attribute %q not found", region, key) + } + + return fmt.Errorf( + "ArtifactStores[%s]: Attribute %q expected %#v, got %#v", region, key, value, v) + } + return nil + } +} + +func testAccCheckAWSCodePipelineArtifactStoresAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { + return func(s *terraform.State) error { + as, ok := p.ArtifactStores[region] + if !ok { + return fmt.Errorf("Artifact Store for region %q not found", region) + } + values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) + + isSecond, err := primaryInstanceState(s, nameSecond) + if err != nil { + return err + } + + vFirst, okFirst := values[keyFirst] + vSecond, okSecond := isSecond.Attributes[keySecond] + + if okFirst != okSecond { + if !okFirst { + return fmt.Errorf("ArtifactStores[%s]: Attribute %q not set, but %q is set in %s as %q", region, keyFirst, keySecond, nameSecond, vSecond) + } + return fmt.Errorf("ArtifactStores[%s]: Attribute %q is %q, but %q is not set in %s", region, keyFirst, vFirst, keySecond, nameSecond) + } + if !(okFirst || okSecond) { + // If they both don't exist then they are equally unset, so that's okay. + return nil + } + + if vFirst != vSecond { + return fmt.Errorf("ArtifactStores[%s]: Attribute '%s' expected %#v, got %#v", region, keyFirst, vSecond, vFirst) + } + + return nil + } +} + func testAccCheckAWSCodePipelineExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -352,19 +397,6 @@ func testAccPreCheckAWSCodePipeline(t *testing.T) { } } -func testAccAWSCodePipelineS3DefaultBucket(rName string) string { - return testAccAWSCodePipelineS3Bucket("test", rName) -} - -func testAccAWSCodePipelineS3Bucket(bucket, rName string) string { - return fmt.Sprintf(` -resource "aws_s3_bucket" "%[1]s" { - bucket = "tf-test-pipeline-%[1]s-%[2]s" - acl = "private" -} -`, bucket, rName) -} - func testAccAWSCodePipelineServiceIAMRole(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "codepipeline_role" { @@ -853,18 +885,144 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfig_multiregion(rName string) string { return composeConfig( testAccAlternateRegionProviderConfig(), + testAccAWSCodePipelineS3Bucket("local", rName), + testAccAWSCodePipelineS3BucketWithProvider("alternate", rName, "aws.alternate"), fmt.Sprintf(` -resource "aws_s3_bucket" "local" { - bucket = "tf-test-pipeline-local-%[1]s" - acl = "private" +resource "aws_iam_role" "codepipeline_role" { + name = "codepipeline-role-%[1]s" + + assume_role_policy = < Date: Mon, 30 Mar 2020 15:13:45 -0700 Subject: [PATCH 13/16] Adds tests for updating cross-region CodePipeline actions --- aws/resource_aws_codepipeline_test.go | 154 ++++++++++++++++++-------- 1 file changed, 105 insertions(+), 49 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 0a14777654ea..7183fe99abfa 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -272,59 +272,60 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { }) } -func testAccCheckAWSCodePipelineArtifactStoresAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - as, ok := p.ArtifactStores[region] - if !ok { - return fmt.Errorf("Artifact Store for region %q not found", region) - } - values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) - - if v, ok := values[key]; !ok || v != value { - if !ok { - return fmt.Errorf("ArtifactStores[%s]: Attribute %q not found", region, key) - } - - return fmt.Errorf( - "ArtifactStores[%s]: Attribute %q expected %#v, got %#v", region, key, value, v) - } - return nil - } -} +func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { + var p1, p2 codepipeline.PipelineDeclaration + resourceName := "aws_codepipeline.test" + var providers []*schema.Provider -func testAccCheckAWSCodePipelineArtifactStoresAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { - return func(s *terraform.State) error { - as, ok := p.ArtifactStores[region] - if !ok { - return fmt.Errorf("Artifact Store for region %q not found", region) - } - values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) + name := acctest.RandString(10) - isSecond, err := primaryInstanceState(s, nameSecond) - if err != nil { - return err - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p1), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), - vFirst, okFirst := values[keyFirst] - vSecond, okSecond := isSecond.Attributes[keySecond] + testAccCheckAWSCodePipelineArtifactStoresAttr(&p1, testAccGetRegion(), "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoresAttr(&p1, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), - if okFirst != okSecond { - if !okFirst { - return fmt.Errorf("ArtifactStores[%s]: Attribute %q not set, but %q is set in %s as %q", region, keyFirst, keySecond, nameSecond, vSecond) - } - return fmt.Errorf("ArtifactStores[%s]: Attribute %q is %q, but %q is not set in %s", region, keyFirst, vFirst, keySecond, nameSecond) - } - if !(okFirst || okSecond) { - // If they both don't exist then they are equally unset, so that's okay. - return nil - } + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-Build", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-Build", testAccGetAlternateRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), + ), + }, + { + Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p2), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), - if vFirst != vSecond { - return fmt.Errorf("ArtifactStores[%s]: Attribute '%s' expected %#v, got %#v", region, keyFirst, vSecond, vFirst) - } + testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "4321"), + testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "8765"), - return nil - } + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-BuildUpdated", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-BuildUpdated", testAccGetAlternateRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), + ), + }, + }, + }) } func testAccCheckAWSCodePipelineExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { @@ -1123,7 +1124,7 @@ resource "aws_codepipeline" "test" { action { region = "%[2]s" - name = "%[2]s-Build" + name = "%[2]s-BuildUpdated" category = "Build" owner = "AWS" provider = "CodeBuild" @@ -1136,7 +1137,7 @@ resource "aws_codepipeline" "test" { } action { region = "%[3]s" - name = "%[3]s-Build" + name = "%[3]s-BuildUpdated" category = "Build" owner = "AWS" provider = "CodeBuild" @@ -1174,3 +1175,58 @@ resource "aws_s3_bucket" "%[1]s" { } `, bucket, rName, provider) } + +func testAccCheckAWSCodePipelineArtifactStoresAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + as, ok := p.ArtifactStores[region] + if !ok { + return fmt.Errorf("Artifact Store for region %q not found", region) + } + values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) + + if v, ok := values[key]; !ok || v != value { + if !ok { + return fmt.Errorf("ArtifactStores[%s]: Attribute %q not found", region, key) + } + + return fmt.Errorf( + "ArtifactStores[%s]: Attribute %q expected %#v, got %#v", region, key, value, v) + } + return nil + } +} + +func testAccCheckAWSCodePipelineArtifactStoresAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { + return func(s *terraform.State) error { + as, ok := p.ArtifactStores[region] + if !ok { + return fmt.Errorf("Artifact Store for region %q not found", region) + } + values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) + + isSecond, err := primaryInstanceState(s, nameSecond) + if err != nil { + return err + } + + vFirst, okFirst := values[keyFirst] + vSecond, okSecond := isSecond.Attributes[keySecond] + + if okFirst != okSecond { + if !okFirst { + return fmt.Errorf("ArtifactStores[%s]: Attribute %q not set, but %q is set in %s as %q", region, keyFirst, keySecond, nameSecond, vSecond) + } + return fmt.Errorf("ArtifactStores[%s]: Attribute %q is %q, but %q is not set in %s", region, keyFirst, vFirst, keySecond, nameSecond) + } + if !(okFirst || okSecond) { + // If they both don't exist then they are equally unset, so that's okay. + return nil + } + + if vFirst != vSecond { + return fmt.Errorf("ArtifactStores[%s]: Attribute '%s' expected %#v, got %#v", region, keyFirst, vSecond, vFirst) + } + + return nil + } +} From 2b32b08c12f498bbdb54e2b6a66d6fe723be4a00 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 30 Mar 2020 16:34:20 -0700 Subject: [PATCH 14/16] Adds tests for converting CodePipeline actions from single- to cross-region --- aws/resource_aws_codepipeline_test.go | 107 ++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 16 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 7183fe99abfa..d1b6bb101370 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -249,14 +249,14 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetRegion(), "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.local", "bucket"), + testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.test", "bucket"), testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetAlternateRegion(), "type", "S3"), testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetAlternateRegion(), "location", "aws_s3_bucket.alternate", "bucket"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-Build", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-Build", testAccGetAlternateRegion())), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), @@ -300,7 +300,7 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-Build", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-Build", testAccGetAlternateRegion())), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), @@ -318,7 +318,7 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), - resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", fmt.Sprintf("%s-BuildUpdated", testAccGetRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "BuildUpdated"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-BuildUpdated", testAccGetAlternateRegion())), resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), @@ -328,6 +328,74 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { }) } +func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { + var p1, p2 codepipeline.PipelineDeclaration + resourceName := "aws_codepipeline.test" + var providers []*schema.Provider + + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_basic(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p1), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + + resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", ""), + ), + }, + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p2), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + + testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.name", fmt.Sprintf("%s-Build", testAccGetAlternateRegion())), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), + ), + }, + { + Config: testAccAWSCodePipelineConfig_backToBasic(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p1), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), + resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + + resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), + ), + }, + }, + }) +} + func testAccCheckAWSCodePipelineExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -886,7 +954,7 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfig_multiregion(rName string) string { return composeConfig( testAccAlternateRegionProviderConfig(), - testAccAWSCodePipelineS3Bucket("local", rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineS3BucketWithProvider("alternate", rName, "aws.alternate"), fmt.Sprintf(` resource "aws_iam_role" "codepipeline_role" { @@ -924,8 +992,8 @@ resource "aws_iam_role_policy" "codepipeline_policy" { "s3:GetBucketVersioning" ], "Resource": [ - "${aws_s3_bucket.local.arn}", - "${aws_s3_bucket.local.arn}/*", + "${aws_s3_bucket.test.arn}", + "${aws_s3_bucket.test.arn}/*", "${aws_s3_bucket.alternate.arn}", "${aws_s3_bucket.alternate.arn}/*" ] @@ -948,7 +1016,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_stores { - location = "${aws_s3_bucket.local.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { id = "1234" @@ -989,7 +1057,7 @@ resource "aws_codepipeline" "test" { action { region = "%[2]s" - name = "%[2]s-Build" + name = "Build" category = "Build" owner = "AWS" provider = "CodeBuild" @@ -997,7 +1065,7 @@ resource "aws_codepipeline" "test" { version = "1" configuration = { - ProjectName = "%[2]s-Test" + ProjectName = "Test" } } action { @@ -1021,7 +1089,7 @@ resource "aws_codepipeline" "test" { func testAccAWSCodePipelineConfig_multiregionUpdated(rName string) string { return composeConfig( testAccAlternateRegionProviderConfig(), - testAccAWSCodePipelineS3Bucket("local", rName), + testAccAWSCodePipelineS3DefaultBucket(rName), testAccAWSCodePipelineS3BucketWithProvider("alternate", rName, "aws.alternate"), fmt.Sprintf(` resource "aws_iam_role" "codepipeline_role" { @@ -1059,8 +1127,8 @@ resource "aws_iam_role_policy" "codepipeline_policy" { "s3:GetBucketVersioning" ], "Resource": [ - "${aws_s3_bucket.local.arn}", - "${aws_s3_bucket.local.arn}/*", + "${aws_s3_bucket.test.arn}", + "${aws_s3_bucket.test.arn}/*", "${aws_s3_bucket.alternate.arn}", "${aws_s3_bucket.alternate.arn}/*" ] @@ -1083,7 +1151,7 @@ resource "aws_codepipeline" "test" { role_arn = "${aws_iam_role.codepipeline_role.arn}" artifact_stores { - location = "${aws_s3_bucket.local.bucket}" + location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { id = "4321" @@ -1124,7 +1192,7 @@ resource "aws_codepipeline" "test" { action { region = "%[2]s" - name = "%[2]s-BuildUpdated" + name = "BuildUpdated" category = "Build" owner = "AWS" provider = "CodeBuild" @@ -1132,7 +1200,7 @@ resource "aws_codepipeline" "test" { version = "1" configuration = { - ProjectName = "%[2]s-Test" + ProjectName = "Test" } } action { @@ -1153,6 +1221,13 @@ resource "aws_codepipeline" "test" { `, rName, testAccGetRegion(), testAccGetAlternateRegion())) } +func testAccAWSCodePipelineConfig_backToBasic(rName string) string { + return composeConfig( + testAccAlternateRegionProviderConfig(), + testAccAWSCodePipelineConfig_basic(rName), + ) +} + func testAccAWSCodePipelineS3DefaultBucket(rName string) string { return testAccAWSCodePipelineS3Bucket("test", rName) } From adc0909907d762287f1b72b8bd300f510793b58f Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 31 Mar 2020 16:55:47 -0700 Subject: [PATCH 15/16] Consolidates artifact stores into a single argument --- aws/resource_aws_codepipeline.go | 189 +++++++++++---------- aws/resource_aws_codepipeline_test.go | 236 ++++++++++++++++++++------ website/docs/r/codepipeline.markdown | 7 +- 3 files changed, 285 insertions(+), 147 deletions(-) diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 4beb11074469..880b8d516981 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -1,6 +1,7 @@ package aws import ( + "errors" "fmt" "log" "os" @@ -8,7 +9,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -16,47 +16,6 @@ import ( ) func resourceAwsCodePipeline() *schema.Resource { - var artifactStoreSchema = map[string]*schema.Schema{ - "location": { - Type: schema.TypeString, - Required: true, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.ArtifactStoreTypeS3, - }, false), - }, - - "encryption_key": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.EncryptionKeyTypeKms, - }, false), - }, - }, - }, - }, - "region": { - Type: schema.TypeString, - Optional: true, - }, - } - return &schema.Resource{ Create: resourceAwsCodePipelineCreate, Read: resourceAwsCodePipelineRead, @@ -83,18 +42,47 @@ func resourceAwsCodePipeline() *schema.Resource { Required: true, }, "artifact_store": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: artifactStoreSchema, - }, - }, - "artifact_stores": { Type: schema.TypeSet, - Optional: true, + Required: true, Elem: &schema.Resource{ - Schema: artifactStoreSchema, + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.ArtifactStoreTypeS3, + }, false), + }, + "encryption_key": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.EncryptionKeyTypeKms, + }, false), + }, + }, + }, + }, + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, }, }, "stage": { @@ -187,13 +175,18 @@ func resourceAwsCodePipeline() *schema.Resource { func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).codepipelineconn + + pipeline, err := expandAwsCodePipeline(d) + if err != nil { + return err + } params := &codepipeline.CreatePipelineInput{ - Pipeline: expandAwsCodePipeline(d), + Pipeline: pipeline, Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CodepipelineTags(), } var resp *codepipeline.CreatePipelineOutput - err := resource.Retry(2*time.Minute, func() *resource.RetryError { + err = resource.Retry(2*time.Minute, func() *resource.RetryError { var err error resp, err = conn.CreatePipeline(params) @@ -219,49 +212,57 @@ func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) err return resourceAwsCodePipelineRead(d, meta) } -func expandAwsCodePipeline(d *schema.ResourceData) *codepipeline.PipelineDeclaration { - pipelineStages := expandAwsCodePipelineStages(d) - pipelineArtifactStore := expandAwsCodePipelineArtifactStore(d) - pipelineArtifactStores := expandAwsCodePipelineArtifactStores(d) - +func expandAwsCodePipeline(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { pipeline := codepipeline.PipelineDeclaration{ - Name: aws.String(d.Get("name").(string)), - RoleArn: aws.String(d.Get("role_arn").(string)), - ArtifactStore: pipelineArtifactStore, - ArtifactStores: pipelineArtifactStores, - Stages: pipelineStages, + Name: aws.String(d.Get("name").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + Stages: expandAwsCodePipelineStages(d), } - return &pipeline -} - -func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.ArtifactStore { - configs := d.Get("artifact_store").([]interface{}) - - if len(configs) == 0 { - return nil + pipelineArtifactStores, err := expandAwsCodePipelineArtifactStores(d.Get("artifact_store").(*schema.Set).List()) + if err != nil { + return nil, err + } + if len(pipelineArtifactStores) == 1 { + for _, v := range pipelineArtifactStores { + pipeline.ArtifactStore = v + } + } else { + pipeline.ArtifactStores = pipelineArtifactStores } - _, pipelineArtifactStore := expandAwsCodePipelineArtifactStoreData(configs[0].(map[string]interface{})) - - return pipelineArtifactStore + return &pipeline, nil } -func expandAwsCodePipelineArtifactStores(d *schema.ResourceData) map[string]*codepipeline.ArtifactStore { - configs := d.Get("artifact_stores").(*schema.Set).List() - +func expandAwsCodePipelineArtifactStores(configs []interface{}) (map[string]*codepipeline.ArtifactStore, error) { if len(configs) == 0 { - return nil + return nil, nil } + regions := make([]string, 0, len(configs)) pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) - for _, config := range configs { region, store := expandAwsCodePipelineArtifactStoreData(config.(map[string]interface{})) + regions = append(regions, region) pipelineArtifactStores[region] = store } - return pipelineArtifactStores + if len(regions) == 1 { + if regions[0] != "" { + return nil, errors.New("region cannot be set for a single-region CodePipeline") + } + } else { + for _, v := range regions { + if v == "" { + return nil, errors.New("region must be set for a cross-region CodePipeline") + } + } + if len(configs) != len(pipelineArtifactStores) { + return nil, errors.New("only one Artifact Store can be defined per region for a cross-region CodePipeline") + } + } + + return pipelineArtifactStores, nil } func expandAwsCodePipelineArtifactStoreData(data map[string]interface{}) (string, *codepipeline.ArtifactStore) { @@ -516,11 +517,14 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error metadata := resp.Metadata pipeline := resp.Pipeline - if err := d.Set("artifact_store", flattenAwsCodePipelineArtifactStore(pipeline.ArtifactStore)); err != nil { - return err - } - if err := d.Set("artifact_stores", flattenAwsCodePipelineArtifactStores(pipeline.ArtifactStores)); err != nil { - return err + if pipeline.ArtifactStore != nil { + if err := d.Set("artifact_store", flattenAwsCodePipelineArtifactStore(pipeline.ArtifactStore)); err != nil { + return err + } + } else if pipeline.ArtifactStores != nil { + if err := d.Set("artifact_store", flattenAwsCodePipelineArtifactStores(pipeline.ArtifactStores)); err != nil { + return err + } } if err := d.Set("stage", flattenAwsCodePipelineStages(pipeline.Stages)); err != nil { @@ -548,11 +552,14 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error func resourceAwsCodePipelineUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).codepipelineconn - pipeline := expandAwsCodePipeline(d) + pipeline, err := expandAwsCodePipeline(d) + if err != nil { + return err + } params := &codepipeline.UpdatePipelineInput{ Pipeline: pipeline, } - _, err := conn.UpdatePipeline(params) + _, err = conn.UpdatePipeline(params) if err != nil { return fmt.Errorf( @@ -589,9 +596,3 @@ func resourceAwsCodePipelineDelete(d *schema.ResourceData, meta interface{}) err return err } - -func resourceAwsCodePipelineArtifactStoreHash(v interface{}) int { - m := v.(map[string]interface{}) - - return hashcode.String(m["region"].(string)) -} diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index d1b6bb101370..9a937658be4c 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "regexp" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -32,12 +33,12 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "1234"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.#", "1"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.type", "KMS"), + resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), @@ -84,12 +85,12 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { Config: testAccAWSCodePipelineConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.updated", "bucket"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "4567"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.#", "1"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.type", "KMS"), + resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), @@ -132,8 +133,8 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.#", "0"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p, "", "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p, "", "encryption_key.#", "0"), ), ExpectNonEmptyPlan: true, }, @@ -245,14 +246,13 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { Config: testAccAWSCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p), - resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetRegion(), "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.test", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p, testAccGetRegion(), "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.test", "bucket"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p, testAccGetAlternateRegion(), "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoresAttrPair(&p, testAccGetAlternateRegion(), "location", "aws_s3_bucket.alternate", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p, testAccGetAlternateRegion(), "type", "S3"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p, testAccGetAlternateRegion(), "location", "aws_s3_bucket.alternate", "bucket"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), @@ -292,11 +292,10 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { Config: testAccAWSCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), - resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p1, testAccGetRegion(), "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p1, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, testAccGetRegion(), "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), @@ -310,11 +309,10 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), - resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "4321"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "8765"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "4321"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "8765"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), @@ -349,9 +347,8 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), - resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), @@ -363,11 +360,10 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { Config: testAccAWSCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), - resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "0"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "2"), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoresAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "1234"), + testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), @@ -382,9 +378,8 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - resource.TestCheckResourceAttr(resourceName, "artifact_stores.#", "0"), - resource.TestCheckResourceAttrPair(resourceName, "artifact_store.0.location", "aws_s3_bucket.test", "bucket"), + testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), @@ -1015,7 +1010,7 @@ resource "aws_codepipeline" "test" { name = "test-pipeline-%[1]s" role_arn = "${aws_iam_role.codepipeline_role.arn}" - artifact_stores { + artifact_store { location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { @@ -1024,7 +1019,7 @@ resource "aws_codepipeline" "test" { } region = "%[2]s" } - artifact_stores { + artifact_store { location = "${aws_s3_bucket.alternate.bucket}" type = "S3" encryption_key { @@ -1150,7 +1145,7 @@ resource "aws_codepipeline" "test" { name = "test-pipeline-%[1]s" role_arn = "${aws_iam_role.codepipeline_role.arn}" - artifact_stores { + artifact_store { location = "${aws_s3_bucket.test.bucket}" type = "S3" encryption_key { @@ -1159,7 +1154,7 @@ resource "aws_codepipeline" "test" { } region = "%[2]s" } - artifact_stores { + artifact_store { location = "${aws_s3_bucket.alternate.bucket}" type = "S3" encryption_key { @@ -1251,15 +1246,23 @@ resource "aws_s3_bucket" "%[1]s" { `, bucket, rName, provider) } -func testAccCheckAWSCodePipelineArtifactStoresAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { +func testAccCheckAWSCodePipelineArtifactStoreAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { return func(s *terraform.State) error { - as, ok := p.ArtifactStores[region] - if !ok { - return fmt.Errorf("Artifact Store for region %q not found", region) + values, err := testAccCheckAWSCodePipelineArtifactStoreFlatmap(p, region) + if err != nil { + return err + } + + emptyCheck := false + if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { + emptyCheck = true } - values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) if v, ok := values[key]; !ok || v != value { + if emptyCheck && !ok { + return nil + } + if !ok { return fmt.Errorf("ArtifactStores[%s]: Attribute %q not found", region, key) } @@ -1271,13 +1274,12 @@ func testAccCheckAWSCodePipelineArtifactStoresAttr(p *codepipeline.PipelineDecla } } -func testAccCheckAWSCodePipelineArtifactStoresAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { +func testAccCheckAWSCodePipelineArtifactStoreAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { return func(s *terraform.State) error { - as, ok := p.ArtifactStores[region] - if !ok { - return fmt.Errorf("Artifact Store for region %q not found", region) + values, err := testAccCheckAWSCodePipelineArtifactStoreFlatmap(p, region) + if err != nil { + return err } - values := flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})) isSecond, err := primaryInstanceState(s, nameSecond) if err != nil { @@ -1305,3 +1307,137 @@ func testAccCheckAWSCodePipelineArtifactStoresAttrPair(p *codepipeline.PipelineD return nil } } + +func testAccCheckAWSCodePipelineArtifactStoreFlatmap(p *codepipeline.PipelineDeclaration, region string) (flatmap.Map, error) { + var as *codepipeline.ArtifactStore + if region == "" { + as = p.ArtifactStore + } else { + v, ok := p.ArtifactStores[region] + if !ok { + return nil, fmt.Errorf("Artifact Store for region %q not found", region) + } + as = v + } + return flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})), nil +} + +func TestResourceAWSCodePipelineExpandArtifactStoresValidation(t *testing.T) { + cases := []struct { + Name string + Input []interface{} + ExpectedError string + }{ + { + Name: "Single-region", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "", + }, + }, + }, + { + Name: "Single-region, names region", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-west-2", + }, + }, + ExpectedError: "region cannot be set for a single-region CodePipeline", + }, + { + Name: "Cross-region", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-west-2", + }, + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-east-1", + }, + }, + }, + { + Name: "Cross-region, no regions", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "", + }, + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "", + }, + }, + ExpectedError: "region must be set for a cross-region CodePipeline", + }, + { + Name: "Cross-region, not all regions", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-west-2", + }, + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "", + }, + }, + ExpectedError: "region must be set for a cross-region CodePipeline", + }, + { + Name: "Duplicate regions", + Input: []interface{}{ + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-west-2", + }, + map[string]interface{}{ + "location": "", + "type": "", + "encryption_key": []interface{}{}, + "region": "us-west-2", + }, + }, + ExpectedError: "only one Artifact Store can be defined per region for a cross-region CodePipeline", + }, + } + + for _, tc := range cases { + _, err := expandAwsCodePipelineArtifactStores(tc.Input) + if tc.ExpectedError == "" { + if err != nil { + t.Errorf("%s: Did not expect an error, but got: %q", tc.Name, err) + } + } else { + if err == nil { + t.Errorf("%s: Expected an error, but did not get one", tc.Name) + } else { + if err.Error() != tc.ExpectedError { + t.Errorf("%s: Expected error %q, got %q", tc.Name, tc.ExpectedError, err.Error()) + } + } + } + } +} diff --git a/website/docs/r/codepipeline.markdown b/website/docs/r/codepipeline.markdown index 3bd08d7909bd..77c28db75945 100644 --- a/website/docs/r/codepipeline.markdown +++ b/website/docs/r/codepipeline.markdown @@ -157,16 +157,17 @@ The following arguments are supported: * `name` - (Required) The name of the pipeline. * `role_arn` - (Required) A service role Amazon Resource Name (ARN) that grants AWS CodePipeline permission to make calls to AWS services on your behalf. -* `artifact_store` (Required) An artifact_store block. Artifact stores are documented below. +* `artifact_store` (Required) One or more artifact_store blocks. Artifact stores are documented below. * `stage` (Minimum of at least two `stage` blocks is required) A stage block. Stages are documented below. * `tags` - (Optional) A mapping of tags to assign to the resource. An `artifact_store` block supports the following arguments: -* `location` - (Required) The location where AWS CodePipeline stores artifacts for a pipeline, such as an S3 bucket. +* `location` - (Required) The location where AWS CodePipeline stores artifacts for a pipeline; currently only `S3` is supported. * `type` - (Required) The type of the artifact store, such as Amazon S3 * `encryption_key` - (Optional) The encryption key block AWS CodePipeline uses to encrypt the data in the artifact store, such as an AWS Key Management Service (AWS KMS) key. If you don't specify a key, AWS CodePipeline uses the default key for Amazon Simple Storage Service (Amazon S3). An `encryption_key` block is documented below. +* `region` - (Optional) The region where the artifact store is located. Required for a cross-region CodePipeline, do not provide for a single-region CodePipeline. An `encryption_key` block supports the following arguments: @@ -178,7 +179,7 @@ A `stage` block supports the following arguments: * `name` - (Required) The name of the stage. * `action` - (Required) The action(s) to include in the stage. Defined as an `action` block below -A `action` block supports the following arguments: +An `action` block supports the following arguments: * `category` - (Required) A category defines what kind of action can be taken in the stage, and constrains the provider type for the action. Possible values are `Approval`, `Build`, `Deploy`, `Invoke`, `Source` and `Test`. * `owner` - (Required) The creator of the action being called. Possible values are `AWS`, `Custom` and `ThirdParty`. From 63d187a14b19bb852d7310c686f816b76c5c2316 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 1 Apr 2020 11:53:58 -0700 Subject: [PATCH 16/16] Removes nested resource testing in favour of `ImportStateVerify` and adds missing CodePipeline precheck --- aws/resource_aws_codepipeline_test.go | 131 +++++--------------------- 1 file changed, 21 insertions(+), 110 deletions(-) diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 9a937658be4c..6ef04c76f3bc 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "regexp" - "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -13,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/flatmap" ) func TestAccAWSCodePipeline_basic(t *testing.T) { @@ -33,11 +31,6 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.type", "KMS"), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -85,11 +78,6 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { Config: testAccAWSCodePipelineConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, "", "encryption_key.0.type", "KMS"), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -113,6 +101,11 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "test"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -133,8 +126,6 @@ func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p, "", "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p, "", "encryption_key.#", "0"), ), ExpectNonEmptyPlan: true, }, @@ -238,6 +229,7 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) + testAccPreCheckAWSCodePipeline(t) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, @@ -248,12 +240,6 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p, testAccGetRegion(), "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p, testAccGetRegion(), "location", "aws_s3_bucket.test", "bucket"), - - testAccCheckAWSCodePipelineArtifactStoreAttr(&p, testAccGetAlternateRegion(), "type", "S3"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p, testAccGetAlternateRegion(), "location", "aws_s3_bucket.alternate", "bucket"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), @@ -284,6 +270,7 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) + testAccPreCheckAWSCodePipeline(t) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, @@ -294,9 +281,6 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, testAccGetRegion(), "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p1, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), @@ -311,9 +295,6 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "4321"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "8765"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "BuildUpdated"), @@ -322,6 +303,12 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.1.action.1.region", testAccGetAlternateRegion()), ), }, + { + Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -338,6 +325,7 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) + testAccPreCheckAWSCodePipeline(t) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, @@ -348,8 +336,6 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), @@ -362,9 +348,6 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetRegion(), "encryption_key.0.id", "1234"), - testAccCheckAWSCodePipelineArtifactStoreAttr(&p2, testAccGetAlternateRegion(), "encryption_key.0.id", "5678"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), @@ -379,14 +362,18 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), - testAccCheckAWSCodePipelineArtifactStoreAttrPair(&p1, "", "location", "aws_s3_bucket.test", "bucket"), - resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", testAccGetRegion()), ), }, + { + Config: testAccAWSCodePipelineConfig_backToBasic(name), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -433,7 +420,7 @@ func testAccCheckAWSCodePipelineDestroy(s *terraform.State) error { return fmt.Errorf("Expected AWS CodePipeline to be gone, but was still found") } if isAWSErr(err, "PipelineNotFoundException", "") { - return nil + continue } return err } @@ -1246,82 +1233,6 @@ resource "aws_s3_bucket" "%[1]s" { `, bucket, rName, provider) } -func testAccCheckAWSCodePipelineArtifactStoreAttr(p *codepipeline.PipelineDeclaration, region string, key, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - values, err := testAccCheckAWSCodePipelineArtifactStoreFlatmap(p, region) - if err != nil { - return err - } - - emptyCheck := false - if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { - emptyCheck = true - } - - if v, ok := values[key]; !ok || v != value { - if emptyCheck && !ok { - return nil - } - - if !ok { - return fmt.Errorf("ArtifactStores[%s]: Attribute %q not found", region, key) - } - - return fmt.Errorf( - "ArtifactStores[%s]: Attribute %q expected %#v, got %#v", region, key, value, v) - } - return nil - } -} - -func testAccCheckAWSCodePipelineArtifactStoreAttrPair(p *codepipeline.PipelineDeclaration, region string, keyFirst, nameSecond, keySecond string) resource.TestCheckFunc { - return func(s *terraform.State) error { - values, err := testAccCheckAWSCodePipelineArtifactStoreFlatmap(p, region) - if err != nil { - return err - } - - isSecond, err := primaryInstanceState(s, nameSecond) - if err != nil { - return err - } - - vFirst, okFirst := values[keyFirst] - vSecond, okSecond := isSecond.Attributes[keySecond] - - if okFirst != okSecond { - if !okFirst { - return fmt.Errorf("ArtifactStores[%s]: Attribute %q not set, but %q is set in %s as %q", region, keyFirst, keySecond, nameSecond, vSecond) - } - return fmt.Errorf("ArtifactStores[%s]: Attribute %q is %q, but %q is not set in %s", region, keyFirst, vFirst, keySecond, nameSecond) - } - if !(okFirst || okSecond) { - // If they both don't exist then they are equally unset, so that's okay. - return nil - } - - if vFirst != vSecond { - return fmt.Errorf("ArtifactStores[%s]: Attribute '%s' expected %#v, got %#v", region, keyFirst, vSecond, vFirst) - } - - return nil - } -} - -func testAccCheckAWSCodePipelineArtifactStoreFlatmap(p *codepipeline.PipelineDeclaration, region string) (flatmap.Map, error) { - var as *codepipeline.ArtifactStore - if region == "" { - as = p.ArtifactStore - } else { - v, ok := p.ArtifactStores[region] - if !ok { - return nil, fmt.Errorf("Artifact Store for region %q not found", region) - } - as = v - } - return flatmap.Flatten(flattenAwsCodePipelineArtifactStore(as)[0].(map[string]interface{})), nil -} - func TestResourceAWSCodePipelineExpandArtifactStoresValidation(t *testing.T) { cases := []struct { Name string