diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 0234732184eb..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" @@ -40,18 +41,15 @@ func resourceAwsCodePipeline() *schema.Resource { Type: schema.TypeString, Required: true, }, - "artifact_store": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "location": { Type: schema.TypeString, Required: true, }, - "type": { Type: schema.TypeString, Required: true, @@ -59,7 +57,6 @@ func resourceAwsCodePipeline() *schema.Resource { codepipeline.ArtifactStoreTypeS3, }, false), }, - "encryption_key": { Type: schema.TypeList, MaxItems: 1, @@ -70,7 +67,6 @@ func resourceAwsCodePipeline() *schema.Resource { Type: schema.TypeString, Required: true, }, - "type": { Type: schema.TypeString, Required: true, @@ -81,6 +77,11 @@ func resourceAwsCodePipeline() *schema.Resource { }, }, }, + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, }, }, @@ -102,6 +103,7 @@ func resourceAwsCodePipeline() *schema.Resource { "configuration": { Type: schema.TypeMap, Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, "category": { Type: schema.TypeString, @@ -155,6 +157,11 @@ func resourceAwsCodePipeline() *schema.Resource { Optional: true, Computed: true, }, + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, }, }, @@ -168,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) @@ -200,21 +212,60 @@ func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) err return resourceAwsCodePipelineRead(d, meta) } -func expandAwsCodePipeline(d *schema.ResourceData) *codepipeline.PipelineDeclaration { - pipelineArtifactStore := expandAwsCodePipelineArtifactStore(d) - pipelineStages := expandAwsCodePipelineStages(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, - Stages: pipelineStages, + Name: aws.String(d.Get("name").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + Stages: expandAwsCodePipelineStages(d), + } + + 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 + } + + return &pipeline, nil +} + +func expandAwsCodePipelineArtifactStores(configs []interface{}) (map[string]*codepipeline.ArtifactStore, error) { + if len(configs) == 0 { + 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 + } + + 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 &pipeline + + return pipelineArtifactStores, nil } -func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.ArtifactStore { - configs := d.Get("artifact_store").([]interface{}) - data := configs[0].(map[string]interface{}) + +func expandAwsCodePipelineArtifactStoreData(data map[string]interface{}) (string, *codepipeline.ArtifactStore) { pipelineArtifactStore := codepipeline.ArtifactStore{ Location: aws.String(data["location"].(string)), Type: aws.String(data["type"].(string)), @@ -228,10 +279,15 @@ func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.Ar } pipelineArtifactStore.EncryptionKey = &ek } - return &pipelineArtifactStore + + 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 @@ -245,6 +301,16 @@ func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactSto return []interface{}{values} } +func flattenAwsCodePipelineArtifactStores(artifactStores map[string]*codepipeline.ArtifactStore) []interface{} { + values := []interface{}{} + for region, artifactStore := range artifactStores { + store := flattenAwsCodePipelineArtifactStore(artifactStore)[0].(map[string]interface{}) + store["region"] = region + values = append(values, store) + } + return values +} + func expandAwsCodePipelineStages(d *schema.ResourceData) []*codepipeline.StageDeclaration { configs := d.Get("stage").([]interface{}) pipelineStages := []*codepipeline.StageDeclaration{} @@ -319,6 +385,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 +430,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 @@ -443,8 +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 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 { @@ -472,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( diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 22bb36b486a1..6ef04c76f3bc 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -10,14 +10,12 @@ import ( "github.com/aws/aws-sdk-go/service/codepipeline" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "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) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - + var p1, p2 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -29,12 +27,46 @@ 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.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "1234"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), + 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, "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", ""), ), }, { @@ -45,21 +77,41 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { { Config: testAccAWSCodePipelineConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.type", "S3"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.id", "4567"), - resource.TestCheckResourceAttr(resourceName, "artifact_store.0.encryption_key.0.type", "KMS"), + testAccCheckAWSCodePipelineExists(resourceName, &p2), + + 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", "artifacts"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.%", "3"), + 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", "artifacts"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.%", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.configuration.ProjectName", "test"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } func TestAccAWSCodePipeline_emptyArtifacts(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } - + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -71,18 +123,9 @@ 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))), - 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"), + testAccCheckAWSCodePipelineExists(resourceName, &p), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), ), ExpectNonEmptyPlan: true, }, @@ -96,10 +139,7 @@ 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") - } - + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -111,12 +151,10 @@ 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.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 +167,7 @@ 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") - } - + var p1, p2, p3 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -144,7 +179,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"), @@ -159,7 +194,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"), @@ -174,7 +209,7 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { { Config: testAccAWSCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCodePipelineExists(resourceName), + testAccCheckAWSCodePipelineExists(resourceName, &p3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -182,7 +217,168 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { }) } -func testAccCheckAWSCodePipelineExists(n string) resource.TestCheckFunc { +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) + testAccPreCheckAWSCodePipeline(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), + + 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_multiregion(name), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCodePipeline_multiregion_Update(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) + testAccPreCheckAWSCodePipeline(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_multiregion(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p1), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), + + 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_multiregionUpdated(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p2), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), + + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "2"), + 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()), + ), + }, + { + Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +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) + testAccPreCheckAWSCodePipeline(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, "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.#", "2"), + + 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, "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, + }, + }, + }) +} + +func testAccCheckAWSCodePipelineExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -195,11 +391,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 } } @@ -218,13 +419,20 @@ 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", "") { + continue + } + return err } - return fmt.Errorf("Default error in CodePipeline Test") + return nil } 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{} @@ -240,13 +448,8 @@ func testAccPreCheckAWSCodePipeline(t *testing.T) { } } -func testAccAWSCodePipelineConfig_basic(rName string) string { +func testAccAWSCodePipelineServiceIAMRole(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" @@ -282,8 +485,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}/*" ] }, { @@ -298,67 +501,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" @@ -394,8 +541,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}/*" ] }, { @@ -405,22 +552,36 @@ 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( + testAccAWSCodePipelineS3DefaultBucket(rName), + testAccAWSCodePipelineServiceIAMRole(rName), + fmt.Sprintf(` resource "aws_codepipeline" "test" { name = "test-pipeline-%s" 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 { - id = "4567" + id = "1234" type = "KMS" } } @@ -434,12 +595,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" } } } @@ -452,83 +613,88 @@ 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( + testAccAWSCodePipelineS3DefaultBucket(rName), + testAccAWSCodePipelineS3Bucket("updated", 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.updated.bucket}" + type = "S3" - assume_role_policy = < **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.