diff --git a/aws/resource_aws_codepipeline.go b/aws/resource_aws_codepipeline.go index 854e2b7661f..c0c6eaa0e42 100644 --- a/aws/resource_aws_codepipeline.go +++ b/aws/resource_aws_codepipeline.go @@ -1,10 +1,12 @@ package aws import ( + "crypto/sha256" + "encoding/hex" "errors" "fmt" "log" - "os" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" @@ -15,6 +17,12 @@ import ( iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" ) +const ( + CodePipelineProviderGitHub = "GitHub" + + CodePipelineGitHubActionConfigurationOAuthToken = "OAuthToken" +) + func resourceAwsCodePipeline() *schema.Resource { return &schema.Resource{ Create: resourceAwsCodePipelineCreate, @@ -101,9 +109,10 @@ func resourceAwsCodePipeline() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "configuration": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + DiffSuppressFunc: suppressCodePipelineStageActionConfiguration, }, "category": { Type: schema.TypeString, @@ -209,7 +218,7 @@ func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) err resp, err = conn.CreatePipeline(params) } if err != nil { - return fmt.Errorf("Error creating CodePipeline: %s", err) + return fmt.Errorf("Error creating CodePipeline: %w", err) } if resp.Pipeline == nil { return fmt.Errorf("Error creating CodePipeline: invalid response from AWS") @@ -297,12 +306,12 @@ func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactSto } values := map[string]interface{}{} - values["type"] = *artifactStore.Type - values["location"] = *artifactStore.Location + values["type"] = aws.StringValue(artifactStore.Type) + values["location"] = aws.StringValue(artifactStore.Location) if artifactStore.EncryptionKey != nil { as := map[string]interface{}{ - "id": *artifactStore.EncryptionKey.Id, - "type": *artifactStore.EncryptionKey.Type, + "id": aws.StringValue(artifactStore.EncryptionKey.Id), + "type": aws.StringValue(artifactStore.EncryptionKey.Type), } values["encryption_key"] = []interface{}{as} } @@ -320,10 +329,10 @@ func flattenAwsCodePipelineArtifactStores(artifactStores map[string]*codepipelin } func expandAwsCodePipelineStages(d *schema.ResourceData) []*codepipeline.StageDeclaration { - configs := d.Get("stage").([]interface{}) + stages := d.Get("stage").([]interface{}) pipelineStages := []*codepipeline.StageDeclaration{} - for _, stage := range configs { + for _, stage := range stages { data := stage.(map[string]interface{}) a := data["action"].([]interface{}) actions := expandAwsCodePipelineActions(a) @@ -335,31 +344,23 @@ func expandAwsCodePipelineStages(d *schema.ResourceData) []*codepipeline.StageDe return pipelineStages } -func flattenAwsCodePipelineStages(stages []*codepipeline.StageDeclaration) []interface{} { +func flattenAwsCodePipelineStages(stages []*codepipeline.StageDeclaration, d *schema.ResourceData) []interface{} { stagesList := []interface{}{} - for _, stage := range stages { + for si, stage := range stages { values := map[string]interface{}{} - values["name"] = *stage.Name - values["action"] = flattenAwsCodePipelineStageActions(stage.Actions) + values["name"] = aws.StringValue(stage.Name) + values["action"] = flattenAwsCodePipelineStageActions(si, stage.Actions, d) stagesList = append(stagesList, values) } return stagesList - } -func expandAwsCodePipelineActions(s []interface{}) []*codepipeline.ActionDeclaration { +func expandAwsCodePipelineActions(a []interface{}) []*codepipeline.ActionDeclaration { actions := []*codepipeline.ActionDeclaration{} - for _, config := range s { + for _, config := range a { data := config.(map[string]interface{}) conf := expandAwsCodePipelineStageActionConfiguration(data["configuration"].(map[string]interface{})) - if data["provider"].(string) == "GitHub" { - githubToken := os.Getenv("GITHUB_TOKEN") - if githubToken != "" { - conf["OAuthToken"] = aws.String(githubToken) - } - - } action := codepipeline.ActionDeclaration{ ActionTypeId: &codepipeline.ActionTypeId{ @@ -406,23 +407,29 @@ func expandAwsCodePipelineActions(s []interface{}) []*codepipeline.ActionDeclara return actions } -func flattenAwsCodePipelineStageActions(actions []*codepipeline.ActionDeclaration) []interface{} { +func flattenAwsCodePipelineStageActions(si int, actions []*codepipeline.ActionDeclaration, d *schema.ResourceData) []interface{} { actionsList := []interface{}{} - for _, action := range actions { + for ai, action := range actions { values := map[string]interface{}{ - "category": *action.ActionTypeId.Category, - "owner": *action.ActionTypeId.Owner, - "provider": *action.ActionTypeId.Provider, - "version": *action.ActionTypeId.Version, - "name": *action.Name, + "category": aws.StringValue(action.ActionTypeId.Category), + "owner": aws.StringValue(action.ActionTypeId.Owner), + "provider": aws.StringValue(action.ActionTypeId.Provider), + "version": aws.StringValue(action.ActionTypeId.Version), + "name": aws.StringValue(action.Name), } if action.Configuration != nil { config := flattenAwsCodePipelineStageActionConfiguration(action.Configuration) - _, ok := config["OAuthToken"] - actionProvider := *action.ActionTypeId.Provider - if ok && actionProvider == "GitHub" { - delete(config, "OAuthToken") + + actionProvider := aws.StringValue(action.ActionTypeId.Provider) + if actionProvider == CodePipelineProviderGitHub { + if _, ok := config[CodePipelineGitHubActionConfigurationOAuthToken]; ok { + // The AWS API returns "****" for the OAuthToken value. Pull the value from the configuration. + addr := fmt.Sprintf("stage.%d.action.%d.configuration.OAuthToken", si, ai) + hash := hashCodePipelineGitHubToken(d.Get(addr).(string)) + config[CodePipelineGitHubActionConfigurationOAuthToken] = hash + } } + values["configuration"] = config } @@ -435,15 +442,15 @@ func flattenAwsCodePipelineStageActions(actions []*codepipeline.ActionDeclaratio } if action.RoleArn != nil { - values["role_arn"] = *action.RoleArn + values["role_arn"] = aws.StringValue(action.RoleArn) } if action.RunOrder != nil { - values["run_order"] = int(*action.RunOrder) + values["run_order"] = int(aws.Int64Value(action.RunOrder)) } if action.Region != nil { - values["region"] = *action.Region + values["region"] = aws.StringValue(action.Region) } if action.Namespace != nil { @@ -523,13 +530,13 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error }) if isAWSErr(err, codepipeline.ErrCodePipelineNotFoundException, "") { - log.Printf("[WARN] Codepipeline (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] CodePipeline (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading Codepipeline: %s", err) + return fmt.Errorf("error reading CodePipeline: %w", err) } metadata := resp.Metadata @@ -545,7 +552,7 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error } } - if err := d.Set("stage", flattenAwsCodePipelineStages(pipeline.Stages)); err != nil { + if err := d.Set("stage", flattenAwsCodePipelineStages(pipeline.Stages, d)); err != nil { return err } @@ -557,11 +564,11 @@ func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error tags, err := keyvaluetags.CodepipelineListTags(conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Codepipeline (%s): %s", arn, err) + return fmt.Errorf("error listing tags for CodePipeline (%s): %w", arn, err) } if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %s", err) + return fmt.Errorf("error setting tags for CodePipeline (%s): %w", arn, err) } return nil @@ -580,9 +587,7 @@ func resourceAwsCodePipelineUpdate(d *schema.ResourceData, meta interface{}) err _, err = conn.UpdatePipeline(params) if err != nil { - return fmt.Errorf( - "[ERROR] Error updating CodePipeline (%s): %s", - d.Id(), err) + return fmt.Errorf("[ERROR] Error updating CodePipeline (%s): %w", d.Id(), err) } arn := d.Get("arn").(string) @@ -590,7 +595,7 @@ func resourceAwsCodePipelineUpdate(d *schema.ResourceData, meta interface{}) err o, n := d.GetChange("tags") if err := keyvaluetags.CodepipelineUpdateTags(conn, arn, o, n); err != nil { - return fmt.Errorf("error updating Codepipeline (%s) tags: %s", arn, err) + return fmt.Errorf("error updating CodePipeline (%s) tags: %w", arn, err) } } @@ -609,8 +614,33 @@ func resourceAwsCodePipelineDelete(d *schema.ResourceData, meta interface{}) err } if err != nil { - return fmt.Errorf("error deleting Codepipeline (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting CodePipeline (%s): %w", d.Id(), err) } return err } + +func suppressCodePipelineStageActionConfiguration(k, old, new string, d *schema.ResourceData) bool { + parts := strings.Split(k, ".") + parts = parts[:len(parts)-2] + providerAddr := strings.Join(append(parts, "provider"), ".") + provider := d.Get(providerAddr).(string) + + if provider == CodePipelineProviderGitHub && strings.HasSuffix(k, CodePipelineGitHubActionConfigurationOAuthToken) { + hash := hashCodePipelineGitHubToken(new) + return old == hash + } + + return false +} + +const codePipelineGitHubTokenHashPrefix = "hash-" + +func hashCodePipelineGitHubToken(token string) string { + // Without this check, the value was getting encoded twice + if strings.HasPrefix(token, codePipelineGitHubTokenHashPrefix) { + return token + } + sum := sha256.Sum256([]byte(token)) + return codePipelineGitHubTokenHashPrefix + hex.EncodeToString(sum[:]) +} diff --git a/aws/resource_aws_codepipeline_test.go b/aws/resource_aws_codepipeline_test.go index 6615b740253..1395e1fb8ca 100644 --- a/aws/resource_aws_codepipeline_test.go +++ b/aws/resource_aws_codepipeline_test.go @@ -15,6 +15,8 @@ import ( ) func TestAccAWSCodePipeline_basic(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p1, p2 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -25,7 +27,7 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_basic(name), + Config: testAccAWSCodePipelineConfig_basic(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), @@ -44,10 +46,11 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { 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.%", "4"), 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.configuration.OAuthToken", hashCodePipelineGitHubToken(githubToken)), 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", ""), @@ -73,9 +76,13 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, { - Config: testAccAWSCodePipelineConfig_basicUpdated(name), + Config: testAccAWSCodePipelineConfig_basicUpdated(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), @@ -87,10 +94,11 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { 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.%", "4"), 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.0.action.0.configuration.OAuthToken", hashCodePipelineGitHubToken(githubToken)), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", "1"), @@ -105,12 +113,42 @@ func TestAccAWSCodePipeline_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, + }, + }, + }) +} + +func TestAccAWSCodePipeline_disappears(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + + var p codepipeline.PipelineDeclaration + name := acctest.RandString(10) + resourceName := "aws_codepipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodePipelineConfig_basic(name, githubToken), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodePipelineExists(resourceName, &p), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCodePipeline(), resourceName), + ), + ExpectNonEmptyPlan: true, }, }, }) } func TestAccAWSCodePipeline_emptyStageArtifacts(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -121,7 +159,7 @@ func TestAccAWSCodePipeline_emptyStageArtifacts(t *testing.T) { CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_emptyStageArtifacts(name), + Config: testAccAWSCodePipelineConfig_emptyStageArtifacts(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s$", name))), @@ -142,12 +180,18 @@ func TestAccAWSCodePipeline_emptyStageArtifacts(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) } func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -158,7 +202,7 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_deployWithServiceRole(name), + Config: testAccAWSCodePipelineConfig_deployWithServiceRole(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Deploy"), @@ -170,12 +214,18 @@ func TestAccAWSCodePipeline_deployWithServiceRole(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) } func TestAccAWSCodePipeline_tags(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p1, p2, p3 codepipeline.PipelineDeclaration name := acctest.RandString(10) resourceName := "aws_codepipeline.test" @@ -186,7 +236,7 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfigWithTags(name, "tag1value", "tag2value"), + Config: testAccAWSCodePipelineConfigWithTags(name, githubToken, "tag1value", "tag2value"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), @@ -199,9 +249,13 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, { - Config: testAccAWSCodePipelineConfigWithTags(name, "tag1valueUpdate", "tag2valueUpdate"), + Config: testAccAWSCodePipelineConfigWithTags(name, githubToken, "tag1valueUpdate", "tag2valueUpdate"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), @@ -214,9 +268,13 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, { - Config: testAccAWSCodePipelineConfig_basic(name), + Config: testAccAWSCodePipelineConfig_basic(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -227,6 +285,8 @@ func TestAccAWSCodePipeline_tags(t *testing.T) { } func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p codepipeline.PipelineDeclaration resourceName := "aws_codepipeline.test" var providers []*schema.Provider @@ -238,13 +298,13 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) - testAccPreCheckAWSCodePipeline(t) + testAccPreCheckAWSCodePipeline(t, testAccGetAlternateRegion()) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_multiregion(name), + Config: testAccAWSCodePipelineConfig_multiregion(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), @@ -258,16 +318,22 @@ func TestAccAWSCodePipeline_multiregion_basic(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_multiregion(name), + Config: testAccAWSCodePipelineConfig_multiregion(name, githubToken), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) } func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p1, p2 codepipeline.PipelineDeclaration resourceName := "aws_codepipeline.test" var providers []*schema.Provider @@ -279,13 +345,13 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) - testAccPreCheckAWSCodePipeline(t) + testAccPreCheckAWSCodePipeline(t, testAccGetAlternateRegion()) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_multiregion(name), + Config: testAccAWSCodePipelineConfig_multiregion(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), @@ -299,7 +365,7 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), + Config: testAccAWSCodePipelineConfig_multiregionUpdated(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), @@ -313,16 +379,22 @@ func TestAccAWSCodePipeline_multiregion_Update(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_multiregionUpdated(name), + Config: testAccAWSCodePipelineConfig_multiregionUpdated(name, githubToken), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) } func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { + githubToken := os.Getenv("GITHUB_TOKEN") + var p1, p2 codepipeline.PipelineDeclaration resourceName := "aws_codepipeline.test" var providers []*schema.Provider @@ -334,13 +406,13 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { testAccPreCheck(t) testAccMultipleRegionsPreCheck(t) testAccAlternateRegionPreCheck(t) - testAccPreCheckAWSCodePipeline(t) + testAccPreCheckAWSCodePipeline(t, testAccGetAlternateRegion()) }, ProviderFactories: testAccProviderFactories(&providers), CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfig_basic(name), + Config: testAccAWSCodePipelineConfig_basic(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), @@ -352,7 +424,7 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_multiregion(name), + Config: testAccAWSCodePipelineConfig_multiregion(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), @@ -366,7 +438,7 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_backToBasic(name), + Config: testAccAWSCodePipelineConfig_backToBasic(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), @@ -378,19 +450,21 @@ func TestAccAWSCodePipeline_multiregion_ConvertSingleRegion(t *testing.T) { ), }, { - Config: testAccAWSCodePipelineConfig_backToBasic(name), + Config: testAccAWSCodePipelineConfig_backToBasic(name, githubToken), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) } func TestAccAWSCodePipeline_WithNamespace(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Environment variable GITHUB_TOKEN is not set") - } + githubToken := os.Getenv("GITHUB_TOKEN") var p1 codepipeline.PipelineDeclaration name := acctest.RandString(10) @@ -402,7 +476,7 @@ func TestAccAWSCodePipeline_WithNamespace(t *testing.T) { CheckDestroy: testAccCheckAWSCodePipelineDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodePipelineConfigWithNamespace(name), + Config: testAccAWSCodePipelineConfigWithNamespace(name, githubToken), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodePipelineExists(resourceName, &p1), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), @@ -413,6 +487,10 @@ func TestAccAWSCodePipeline_WithNamespace(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "stage.0.action.0.configuration.%", + "stage.0.action.0.configuration.OAuthToken", + }, }, }, }) @@ -468,30 +546,39 @@ func testAccCheckAWSCodePipelineDestroy(s *terraform.State) error { return nil } -func testAccPreCheckAWSCodePipeline(t *testing.T) { +func testAccPreCheckAWSCodePipeline(t *testing.T, regions ...string) { if os.Getenv("GITHUB_TOKEN") == "" { t.Skip("Environment variable GITHUB_TOKEN is not set") } - conn := testAccProvider.Meta().(*AWSClient).codepipelineconn - - input := &codepipeline.ListPipelinesInput{} + regions = append(regions, testAccGetRegion()) + for _, region := range regions { + conf := &Config{ + Region: region, + } + client, err := conf.Client() + if err != nil { + t.Fatalf("error getting AWS client for region %s", region) + } + conn := client.(*AWSClient).codepipelineconn - _, err := conn.ListPipelines(input) + input := &codepipeline.ListPipelinesInput{} + _, err = conn.ListPipelines(input) - if testAccPreCheckSkipError(err) { - t.Skipf("skipping acceptance testing: %s", err) - } + if testAccPreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } - if err != nil { - t.Fatalf("unexpected PreCheck error: %s", err) + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } } } func testAccAWSCodePipelineServiceIAMRole(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "codepipeline_role" { - name = "codepipeline-role-%s" + name = "codepipeline-role-%[1]s" assume_role_policy = < terraform apply +``` + +```hcl +resource "aws_codepipeline" "example" { + # ... other configuration ... + + stage { + name = "Source" + + action { + name = "Source" + category = "Source" + owner = "ThirdParty" + provider = "GitHub" + version = "1" + output_artifacts = ["example"] + + configuration = { + Owner = "lifesum-terraform" + Repo = "example" + Branch = "main" + } + } + } +} +``` + +```bash +$ TF_VAR_github_token= terraform apply +``` + +```hcl +variable "github_token" {} + +resource "aws_codepipeline" "example" { + # ... other configuration ... + + stage { + name = "Source" + + action { + name = "Source" + category = "Source" + owner = "ThirdParty" + provider = "GitHub" + version = "1" + output_artifacts = ["example"] + + configuration = { + Owner = "lifesum-terraform" + Repo = "example" + Branch = "main" + OAuthToken = var.github_token + } + } + } +} +``` + ## Resource: aws_cognito_user_pool ### Removal of admin_create_user_config.unused_account_validity_days Argument diff --git a/website/docs/r/codepipeline.markdown b/website/docs/r/codepipeline.markdown index a888a083e8c..377580c1567 100644 --- a/website/docs/r/codepipeline.markdown +++ b/website/docs/r/codepipeline.markdown @@ -10,73 +10,9 @@ description: |- Provides a CodePipeline. -~> **NOTE on `aws_codepipeline`:** - the `GITHUB_TOKEN` environment variable must be set if the GitHub provider is specified. - ## Example Usage ```hcl -resource "aws_s3_bucket" "codepipeline_bucket" { - bucket = "test-bucket" - acl = "private" -} - -resource "aws_iam_role" "codepipeline_role" { - name = "test-role" - - assume_role_policy = <