From 139dc1072562a9c2abbaf5fb3011706e1c2b7ecc Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Fri, 8 Mar 2019 10:22:05 +0000 Subject: [PATCH 1/5] 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 9bd8a0d46a4..96460f80f67 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, + }, }, }, }, @@ -313,6 +318,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 @@ -354,6 +363,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 2a41ea747be..17806028743 100644 --- a/website/docs/r/codepipeline.markdown +++ b/website/docs/r/codepipeline.markdown @@ -166,6 +166,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 8ec376ae8f64ab09936cd4363beba2d23ce30224 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Fri, 8 Mar 2019 17:28:05 +0000 Subject: [PATCH 2/5] 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 96460f80f67..de7a3576e41 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, @@ -200,19 +252,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)), @@ -230,6 +291,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 8587bb73d6d6883b41f6fa14df24c487c8fd3c10 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Wed, 19 Feb 2020 23:50:00 +0000 Subject: [PATCH 3/5] 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 de7a3576e41..a0ca4cded1e 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 f445ee4837db1442afe2e54217e6cd1e4a085602 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Thu, 20 Feb 2020 00:05:49 +0000 Subject: [PATCH 4/5] 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 a0ca4cded1e..c9953528a0b 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -246,7 +246,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 @@ -254,23 +254,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 4162fc228ec970bb1675ef0471fce23679a11022 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Thu, 20 Feb 2020 17:51:16 +0000 Subject: [PATCH 5/5] [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 5ab7d5c3038..6dde6c38877 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -135,6 +135,34 @@ func TestAccAWSCodePipeline_deployWithServiceRole(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] @@ -695,3 +723,147 @@ resource "aws_codepipeline" "bar" { } `, rName, rName, rName, rName) } + +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 = <