diff --git a/.changelog/36316.txt b/.changelog/36316.txt new file mode 100644 index 00000000000..284cc709c5d --- /dev/null +++ b/.changelog/36316.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_codepipeline: Add `timeout_in_minutes` argument to the `action` configuration block +``` + +```release-note:bug +resource/aws_codepipeline: Mark `trigger` as Computed +``` \ No newline at end of file diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 10e28ab9b26..09b7c639f9d 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -198,6 +198,11 @@ func resourcePipeline() *schema.Resource { Computed: true, ValidateFunc: validation.IntBetween(1, 999), }, + "timeout_in_minutes": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(5, 86400), + }, names.AttrVersion: { Type: schema.TypeString, Required: true, @@ -225,6 +230,7 @@ func resourcePipeline() *schema.Resource { "trigger": { Type: schema.TypeList, Optional: true, + Computed: true, MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -658,7 +664,7 @@ func expandPipelineDeclaration(d *schema.ResourceData) (*types.PipelineDeclarati case 1: for region, v := range artifactStores { if region != "" { - return nil, errors.New("region cannot be set for a single-region CodePipeline") + return nil, errors.New("region cannot be set for a single-region CodePipeline Pipeline") } v := v apiObject.ArtifactStore = &v @@ -667,11 +673,11 @@ func expandPipelineDeclaration(d *schema.ResourceData) (*types.PipelineDeclarati default: for region := range artifactStores { if region == "" { - return nil, errors.New("region must be set for a cross-region CodePipeline") + return nil, errors.New("region must be set for a cross-region CodePipeline Pipeline") } } if n != v.(*schema.Set).Len() { - return nil, errors.New("only one Artifact Store can be defined per region for a cross-region CodePipeline") + return nil, errors.New("only one Artifact Store can be defined per region for a cross-region CodePipeline Pipeline") } apiObject.ArtifactStores = artifactStores } @@ -877,6 +883,10 @@ func expandActionDeclaration(tfMap map[string]interface{}) *types.ActionDeclarat apiObject.RunOrder = aws.Int32(int32(v)) } + if v, ok := tfMap["timeout_in_minutes"].(int); ok && v != 0 { + apiObject.TimeoutInMinutes = aws.Int32(int32(v)) + } + if v, ok := tfMap[names.AttrVersion].(string); ok && v != "" { apiObject.ActionTypeId.Version = aws.String(v) } @@ -1357,6 +1367,10 @@ func flattenActionDeclaration(d *schema.ResourceData, i, j int, apiObject types. tfMap["run_order"] = aws.ToInt32(v) } + if v := apiObject.TimeoutInMinutes; v != nil { + tfMap["timeout_in_minutes"] = aws.ToInt32(v) + } + return tfMap } diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index 942f67d2dbf..cb2a5a0d63c 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -613,7 +613,7 @@ func TestAccCodePipeline_pipelinetype(t *testing.T) { CheckDestroy: testAccCheckPipelineDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCodePipelineConfig_pipelinetype(rName), + Config: testAccCodePipelineConfig_pipelinetype(rName, "V1"), Check: resource.ComposeTestCheckFunc( testAccCheckPipelineExists(ctx, resourceName, &p), resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.codepipeline_role", names.AttrARN), @@ -908,7 +908,7 @@ func TestAccCodePipeline_pipelinetype(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variable.1.name", "test_var2"), resource.TestCheckResourceAttr(resourceName, "variable.1.description", "This is test pipeline variable 2."), resource.TestCheckResourceAttr(resourceName, "variable.1.default_value", acctest.CtValue2), - resource.TestCheckResourceAttr(resourceName, "trigger.0.git_configuration.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "trigger.0.git_configuration.#", acctest.Ct1), ), }, { @@ -921,14 +921,14 @@ func TestAccCodePipeline_pipelinetype(t *testing.T) { }, }, { - Config: testAccCodePipelineConfig_pipelinetype(rName), + Config: testAccCodePipelineConfig_pipelinetype(rName, "V2"), Check: resource.ComposeTestCheckFunc( testAccCheckPipelineExists(ctx, resourceName, &p), resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.codepipeline_role", names.AttrARN), acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "codepipeline", regexache.MustCompile(fmt.Sprintf("test-pipeline-%s", rName))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "execution_mode", string(types.ExecutionModeSuperseded)), - resource.TestCheckResourceAttr(resourceName, "pipeline_type", string(types.PipelineTypeV1)), + resource.TestCheckResourceAttr(resourceName, "pipeline_type", string(types.PipelineTypeV2)), resource.TestCheckResourceAttr(resourceName, "stage.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", acctest.Ct1), @@ -962,7 +962,74 @@ func TestAccCodePipeline_pipelinetype(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.role_arn", ""), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.run_order", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.region", ""), - resource.TestCheckResourceAttr(resourceName, "trigger.0.git_configuration.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "trigger.0.git_configuration.#", acctest.Ct1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCodePipeline_manualApprovalTimeoutInMinutes(t *testing.T) { + ctx := acctest.Context(t) + var p types.PipelineDeclaration + rName := sdkacctest.RandString(10) + resourceName := "aws_codepipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CodePipelineServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPipelineDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCodePipelineConfig_manualApprovalTimeoutInMinutes(rName, 5), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckPipelineExists(ctx, resourceName, &p), + resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.codepipeline_role", names.AttrARN), + acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "codepipeline", regexache.MustCompile(fmt.Sprintf("test-pipeline-%s", rName))), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.#", acctest.Ct3), + resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.timeout_in_minutes", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Approval"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.timeout_in_minutes", "5"), + resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.2.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.2.action.0.timeout_in_minutes", acctest.Ct0), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCodePipelineConfig_manualApprovalNoTimeoutInMinutes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckPipelineExists(ctx, resourceName, &p), + resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.codepipeline_role", names.AttrARN), + acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "codepipeline", regexache.MustCompile(fmt.Sprintf("test-pipeline-%s", rName))), + resource.TestCheckResourceAttr(resourceName, "artifact_store.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.#", acctest.Ct3), + resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.timeout_in_minutes", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Approval"), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.1.action.0.timeout_in_minutes", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Build"), + resource.TestCheckResourceAttr(resourceName, "stage.2.action.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "stage.2.action.0.timeout_in_minutes", acctest.Ct0), ), }, { @@ -1291,7 +1358,7 @@ resource "aws_codestarconnections_connection" "test" { `, rName)) } -func testAccCodePipelineConfig_pipelinetype(rName string) string { // nosemgrep:ci.codepipeline-in-func-name +func testAccCodePipelineConfig_pipelinetype(rName, pipelineType string) string { // nosemgrep:ci.codepipeline-in-func-name return acctest.ConfigCompose( testAccS3DefaultBucket(rName), testAccServiceIAMRole(rName), @@ -1346,14 +1413,14 @@ resource "aws_codepipeline" "test" { } } - pipeline_type = "V1" + pipeline_type = %[2]q } resource "aws_codestarconnections_connection" "test" { name = %[1]q provider_type = "GitHub" } -`, rName)) +`, rName, pipelineType)) } func testAccCodePipelineConfig_pipelinetypeUpdated1(rName string) string { // nosemgrep:ci.codepipeline-in-func-name @@ -2449,3 +2516,154 @@ resource "aws_codepipeline" "test" { } `, rName)) } + +func testAccCodePipelineConfig_manualApprovalTimeoutInMinutes(rName string, timeoutInMinutes int) string { // nosemgrep:ci.codepipeline-in-func-name + return acctest.ConfigCompose( + testAccS3DefaultBucket(rName), + testAccServiceIAMRole(rName), + fmt.Sprintf(` +resource "aws_codepipeline" "test" { + name = "test-pipeline-%[1]s" + role_arn = aws_iam_role.codepipeline_role.arn + + artifact_store { + location = aws_s3_bucket.test.bucket + type = "S3" + + encryption_key { + id = "1234" + type = "KMS" + } + } + + stage { + name = "Source" + + action { + name = "Source" + category = "Source" + owner = "AWS" + provider = "CodeStarSourceConnection" + version = "1" + output_artifacts = ["test"] + + configuration = { + ConnectionArn = aws_codestarconnections_connection.test.arn + FullRepositoryId = "lifesum-terraform/test" + BranchName = "main" + } + } + } + + stage { + name = "Approval" + + action { + name = "Approval" + category = "Approval" + owner = "AWS" + provider = "Manual" + version = "1" + timeout_in_minutes = %[2]d + } + } + + stage { + name = "Build" + + action { + name = "Build" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + input_artifacts = ["test"] + version = "1" + + configuration = { + ProjectName = "test" + } + } + } +} + +resource "aws_codestarconnections_connection" "test" { + name = %[1]q + provider_type = "GitHub" +} +`, rName, timeoutInMinutes)) +} + +func testAccCodePipelineConfig_manualApprovalNoTimeoutInMinutes(rName string) string { // nosemgrep:ci.codepipeline-in-func-name + return acctest.ConfigCompose( + testAccS3DefaultBucket(rName), + testAccServiceIAMRole(rName), + fmt.Sprintf(` +resource "aws_codepipeline" "test" { + name = "test-pipeline-%[1]s" + role_arn = aws_iam_role.codepipeline_role.arn + + artifact_store { + location = aws_s3_bucket.test.bucket + type = "S3" + + encryption_key { + id = "1234" + type = "KMS" + } + } + + stage { + name = "Source" + + action { + name = "Source" + category = "Source" + owner = "AWS" + provider = "CodeStarSourceConnection" + version = "1" + output_artifacts = ["test"] + + configuration = { + ConnectionArn = aws_codestarconnections_connection.test.arn + FullRepositoryId = "lifesum-terraform/test" + BranchName = "main" + } + } + } + + stage { + name = "Approval" + + action { + name = "Approval" + category = "Approval" + owner = "AWS" + provider = "Manual" + version = "1" + } + } + + stage { + name = "Build" + + action { + name = "Build" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + input_artifacts = ["test"] + version = "1" + + configuration = { + ProjectName = "test" + } + } + } +} + +resource "aws_codestarconnections_connection" "test" { + name = %[1]q + provider_type = "GitHub" +} +`, rName)) +} diff --git a/website/docs/cdktf/python/r/codepipeline.markdown b/website/docs/cdktf/python/r/codepipeline.markdown index 7c0067e51b0..f58acef5217 100644 --- a/website/docs/cdktf/python/r/codepipeline.markdown +++ b/website/docs/cdktf/python/r/codepipeline.markdown @@ -195,6 +195,7 @@ An `action` block supports the following arguments: * `run_order` - (Optional) The order in which actions are run. * `region` - (Optional) The region in which to run the action. * `namespace` - (Optional) The namespace all output variables will be accessed from. +* `timeout_in_minutes` - (Optional) A timeout duration in minutes that can be applied against the action type's default timeout value specified in [Quotas for AWS CodePipeline](https://docs.aws.amazon.com/codepipeline/latest/userguide/limits.html). This argument is available only to the manual approval action type. ~> **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.