From fa3d9104356b26625a6967d11e4368e3367570bd Mon Sep 17 00:00:00 2001 From: Ryan Moore Date: Tue, 5 Dec 2017 14:48:04 +0000 Subject: [PATCH 1/2] Add VPC config block to aws_codebuild_project resource Documentation for VPC block within aws_codebuild_project resource Update basic codebuild test to support VPC block. Currently contains IAM race condition Add further error catching to CodeBuild resource creation to deal with more IAM eventual consistency. Remove unnecessary depends_on clause from CodeBuild basic acceptance test. Formatting fix for codepipline environment variables documentation --- aws/resource_aws_codebuild_project.go | 105 ++++++++++++++++++ aws/resource_aws_codebuild_project_test.go | 69 ++++++++++-- .../docs/r/codebuild_project.html.markdown | 22 +++- 3 files changed, 187 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_codebuild_project.go b/aws/resource_aws_codebuild_project.go index 8539022836a..ad00a188c2d 100644 --- a/aws/resource_aws_codebuild_project.go +++ b/aws/resource_aws_codebuild_project.go @@ -181,6 +181,31 @@ func resourceAwsCodeBuildProject() *schema.Resource { ValidateFunc: validateAwsCodeBuildTimeout, }, "tags": tagsSchema(), + "vpc_config": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vpc_id": { + Type: schema.TypeString, + Required: true, + }, + "subnets": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MaxItems: 16, + }, + "security_group_ids": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MaxItems: 5, + }, + }, + }, + }, }, } } @@ -215,6 +240,11 @@ func resourceAwsCodeBuildProjectCreate(d *schema.ResourceData, meta interface{}) params.TimeoutInMinutes = aws.Int64(int64(v.(int))) } + if _, ok := d.GetOk("vpc_config"); ok { + vpcConfig := expandVpcConfig(d) + params.VpcConfig = vpcConfig + } + if v, ok := d.GetOk("tags"); ok { params.Tags = tagsFromMapCodeBuild(v.(map[string]interface{})) } @@ -230,6 +260,10 @@ func resourceAwsCodeBuildProjectCreate(d *schema.ResourceData, meta interface{}) return resource.RetryableError(err) } + if isAWSErr(err, "InvalidInputException", "Not authorized to perform DescribeSecurityGroups") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) } @@ -326,6 +360,29 @@ func expandProjectEnvironment(d *schema.ResourceData) *codebuild.ProjectEnvironm return projectEnv } +func expandVpcConfig(d *schema.ResourceData) *codebuild.VpcConfig { + configs := d.Get("vpc_config").(*schema.Set).List() + data := configs[0].(map[string]interface{}) + + vpcConfig := codebuild.VpcConfig{ + VpcId: aws.String(data["vpc_id"].(string)), + } + + var vpcConfigSubnets []*string + for _, v := range data["subnets"].([]interface{}) { + vpcConfigSubnets = append(vpcConfigSubnets, aws.String(v.(string))) + } + vpcConfig.Subnets = vpcConfigSubnets + + var vpcSecurityGroupIds []*string + for _, s := range data["security_group_ids"].([]interface{}) { + vpcSecurityGroupIds = append(vpcSecurityGroupIds, aws.String(s.(string))) + } + vpcConfig.SecurityGroupIds = vpcSecurityGroupIds + + return &vpcConfig +} + func expandProjectSource(d *schema.ResourceData) codebuild.ProjectSource { configs := d.Get("source").(*schema.Set).List() projectSource := codebuild.ProjectSource{} @@ -392,6 +449,10 @@ func resourceAwsCodeBuildProjectRead(d *schema.ResourceData, meta interface{}) e return err } + if err := d.Set("vpc_config", schema.NewSet(resourceAwsCodeBuildVpcConfigHash, flattenAwsCodebuildVpcConfig(project.VpcConfig))); err != nil { + return err + } + d.Set("description", project.Description) d.Set("encryption_key", project.EncryptionKey) d.Set("name", project.Name) @@ -427,6 +488,11 @@ func resourceAwsCodeBuildProjectUpdate(d *schema.ResourceData, meta interface{}) params.Artifacts = &projectArtifacts } + if d.HasChange("vpc_config") { + vpcConfig := expandVpcConfig(d) + params.VpcConfig = vpcConfig + } + if d.HasChange("description") { params.Description = aws.String(d.Get("description").(string)) } @@ -548,6 +614,26 @@ func flattenAwsCodebuildProjectSource(source *codebuild.ProjectSource) []interfa return l } +func flattenAwsCodebuildVpcConfig(vpcConfig *codebuild.VpcConfig) []interface{} { + values := map[string]interface{}{} + + values["vpc_id"] = *vpcConfig.VpcId + + var subnets []string + for _, s := range vpcConfig.Subnets { + subnets = append(subnets, *s) + } + values["subnets"] = subnets + + var securityGroupIds []string + for _, s := range vpcConfig.SecurityGroupIds { + securityGroupIds = append(securityGroupIds, *s) + } + values["security_group_ids"] = securityGroupIds + + return []interface{}{values} +} + func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -559,6 +645,25 @@ func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { return hashcode.String(buf.String()) } +func resourceAwsCodeBuildVpcConfigHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["vpc_id"].(string))) + + for _, s := range m["subnets"].([]string) { + buf.WriteString(fmt.Sprintf("%s-", s)) + } + + if m["security_group_ids"] != nil { + for _, s := range m["security_group_ids"].([]string) { + buf.WriteString(fmt.Sprintf("%s-", s)) + } + } + + return hashcode.String(buf.String()) +} + func resourceAwsCodeBuildProjectEnvironmentHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) diff --git a/aws/resource_aws_codebuild_project_test.go b/aws/resource_aws_codebuild_project_test.go index dd22dbe8b8f..645a8581443 100644 --- a/aws/resource_aws_codebuild_project_test.go +++ b/aws/resource_aws_codebuild_project_test.go @@ -352,6 +352,19 @@ func testAccCheckAWSCodeBuildProjectDestroy(s *terraform.State) error { func testAccAWSCodeBuildProjectConfig_basic(rName string) string { return fmt.Sprintf(` +resource "aws_vpc" "codebuild_vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "codebuild_subnet" { + vpc_id = "${aws_vpc.codebuild_vpc.id}" + cidr_block = "10.0.0.0/24" +} + +resource "aws_security_group" "codebuild_security_group" { + vpc_id = "${aws_vpc.codebuild_vpc.id}" +} + resource "aws_iam_role" "codebuild_role" { name = "codebuild-role-%s" assume_role_policy = < Date: Sat, 10 Feb 2018 23:08:15 -0800 Subject: [PATCH 2/2] adding and separating tests for vpc_config codebuild projects changes and removal --- aws/resource_aws_codebuild_project.go | 100 ++++++----------- aws/resource_aws_codebuild_project_test.go | 121 +++++++++++++-------- 2 files changed, 111 insertions(+), 110 deletions(-) diff --git a/aws/resource_aws_codebuild_project.go b/aws/resource_aws_codebuild_project.go index ad00a188c2d..eb6afb70b6d 100644 --- a/aws/resource_aws_codebuild_project.go +++ b/aws/resource_aws_codebuild_project.go @@ -182,7 +182,7 @@ func resourceAwsCodeBuildProject() *schema.Resource { }, "tags": tagsSchema(), "vpc_config": { - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ @@ -192,15 +192,17 @@ func resourceAwsCodeBuildProject() *schema.Resource { Required: true, }, "subnets": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, + // Set: schema.HashString, MaxItems: 16, }, "security_group_ids": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, + // Set: schema.HashString, MaxItems: 5, }, }, @@ -240,9 +242,8 @@ func resourceAwsCodeBuildProjectCreate(d *schema.ResourceData, meta interface{}) params.TimeoutInMinutes = aws.Int64(int64(v.(int))) } - if _, ok := d.GetOk("vpc_config"); ok { - vpcConfig := expandVpcConfig(d) - params.VpcConfig = vpcConfig + if v, ok := d.GetOk("vpc_config"); ok { + params.VpcConfig = expandCodeBuildVpcConfig(v.([]interface{})) } if v, ok := d.GetOk("tags"); ok { @@ -360,27 +361,19 @@ func expandProjectEnvironment(d *schema.ResourceData) *codebuild.ProjectEnvironm return projectEnv } -func expandVpcConfig(d *schema.ResourceData) *codebuild.VpcConfig { - configs := d.Get("vpc_config").(*schema.Set).List() - data := configs[0].(map[string]interface{}) +func expandCodeBuildVpcConfig(rawVpcConfig []interface{}) *codebuild.VpcConfig { + vpcConfig := codebuild.VpcConfig{} + if len(rawVpcConfig) == 0 { + return &vpcConfig + } else { - vpcConfig := codebuild.VpcConfig{ - VpcId: aws.String(data["vpc_id"].(string)), - } - - var vpcConfigSubnets []*string - for _, v := range data["subnets"].([]interface{}) { - vpcConfigSubnets = append(vpcConfigSubnets, aws.String(v.(string))) - } - vpcConfig.Subnets = vpcConfigSubnets + data := rawVpcConfig[0].(map[string]interface{}) + vpcConfig.VpcId = aws.String(data["vpc_id"].(string)) + vpcConfig.Subnets = expandStringList(data["subnets"].(*schema.Set).List()) + vpcConfig.SecurityGroupIds = expandStringList(data["security_group_ids"].(*schema.Set).List()) - var vpcSecurityGroupIds []*string - for _, s := range data["security_group_ids"].([]interface{}) { - vpcSecurityGroupIds = append(vpcSecurityGroupIds, aws.String(s.(string))) + return &vpcConfig } - vpcConfig.SecurityGroupIds = vpcSecurityGroupIds - - return &vpcConfig } func expandProjectSource(d *schema.ResourceData) codebuild.ProjectSource { @@ -437,19 +430,19 @@ func resourceAwsCodeBuildProjectRead(d *schema.ResourceData, meta interface{}) e project := resp.Projects[0] - if err := d.Set("artifacts", flattenAwsCodebuildProjectArtifacts(project.Artifacts)); err != nil { + if err := d.Set("artifacts", flattenAwsCodeBuildProjectArtifacts(project.Artifacts)); err != nil { return err } - if err := d.Set("environment", schema.NewSet(resourceAwsCodeBuildProjectEnvironmentHash, flattenAwsCodebuildProjectEnvironment(project.Environment))); err != nil { + if err := d.Set("environment", schema.NewSet(resourceAwsCodeBuildProjectEnvironmentHash, flattenAwsCodeBuildProjectEnvironment(project.Environment))); err != nil { return err } - if err := d.Set("source", flattenAwsCodebuildProjectSource(project.Source)); err != nil { + if err := d.Set("source", flattenAwsCodeBuildProjectSource(project.Source)); err != nil { return err } - if err := d.Set("vpc_config", schema.NewSet(resourceAwsCodeBuildVpcConfigHash, flattenAwsCodebuildVpcConfig(project.VpcConfig))); err != nil { + if err := d.Set("vpc_config", flattenAwsCodeBuildVpcConfig(project.VpcConfig)); err != nil { return err } @@ -489,8 +482,7 @@ func resourceAwsCodeBuildProjectUpdate(d *schema.ResourceData, meta interface{}) } if d.HasChange("vpc_config") { - vpcConfig := expandVpcConfig(d) - params.VpcConfig = vpcConfig + params.VpcConfig = expandCodeBuildVpcConfig(d.Get("vpc_config").([]interface{})) } if d.HasChange("description") { @@ -540,7 +532,7 @@ func resourceAwsCodeBuildProjectDelete(d *schema.ResourceData, meta interface{}) return nil } -func flattenAwsCodebuildProjectArtifacts(artifacts *codebuild.ProjectArtifacts) *schema.Set { +func flattenAwsCodeBuildProjectArtifacts(artifacts *codebuild.ProjectArtifacts) *schema.Set { artifactSet := schema.Set{ F: resourceAwsCodeBuildProjectArtifactsHash, @@ -575,7 +567,7 @@ func flattenAwsCodebuildProjectArtifacts(artifacts *codebuild.ProjectArtifacts) return &artifactSet } -func flattenAwsCodebuildProjectEnvironment(environment *codebuild.ProjectEnvironment) []interface{} { +func flattenAwsCodeBuildProjectEnvironment(environment *codebuild.ProjectEnvironment) []interface{} { envConfig := map[string]interface{}{} envConfig["type"] = *environment.Type @@ -591,7 +583,7 @@ func flattenAwsCodebuildProjectEnvironment(environment *codebuild.ProjectEnviron } -func flattenAwsCodebuildProjectSource(source *codebuild.ProjectSource) []interface{} { +func flattenAwsCodeBuildProjectSource(source *codebuild.ProjectSource) []interface{} { l := make([]interface{}, 1) m := map[string]interface{}{} @@ -614,24 +606,17 @@ func flattenAwsCodebuildProjectSource(source *codebuild.ProjectSource) []interfa return l } -func flattenAwsCodebuildVpcConfig(vpcConfig *codebuild.VpcConfig) []interface{} { - values := map[string]interface{}{} - - values["vpc_id"] = *vpcConfig.VpcId +func flattenAwsCodeBuildVpcConfig(vpcConfig *codebuild.VpcConfig) []interface{} { + if vpcConfig != nil { + values := map[string]interface{}{} - var subnets []string - for _, s := range vpcConfig.Subnets { - subnets = append(subnets, *s) - } - values["subnets"] = subnets + values["vpc_id"] = *vpcConfig.VpcId + values["subnets"] = schema.NewSet(schema.HashString, flattenStringList(vpcConfig.Subnets)) + values["security_group_ids"] = schema.NewSet(schema.HashString, flattenStringList(vpcConfig.SecurityGroupIds)) - var securityGroupIds []string - for _, s := range vpcConfig.SecurityGroupIds { - securityGroupIds = append(securityGroupIds, *s) + return []interface{}{values} } - values["security_group_ids"] = securityGroupIds - - return []interface{}{values} + return nil } func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { @@ -645,25 +630,6 @@ func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { return hashcode.String(buf.String()) } -func resourceAwsCodeBuildVpcConfigHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - buf.WriteString(fmt.Sprintf("%s-", m["vpc_id"].(string))) - - for _, s := range m["subnets"].([]string) { - buf.WriteString(fmt.Sprintf("%s-", s)) - } - - if m["security_group_ids"] != nil { - for _, s := range m["security_group_ids"].([]string) { - buf.WriteString(fmt.Sprintf("%s-", s)) - } - } - - return hashcode.String(buf.String()) -} - func resourceAwsCodeBuildProjectEnvironmentHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) diff --git a/aws/resource_aws_codebuild_project_test.go b/aws/resource_aws_codebuild_project_test.go index 645a8581443..4e7bb2ab99d 100644 --- a/aws/resource_aws_codebuild_project_test.go +++ b/aws/resource_aws_codebuild_project_test.go @@ -23,19 +23,55 @@ func TestAccAWSCodeBuildProject_basic(t *testing.T) { CheckDestroy: testAccCheckAWSCodeBuildProjectDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCodeBuildProjectConfig_basic(name), + Config: testAccAWSCodeBuildProjectConfig_basic(name, "", ""), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodeBuildProjectExists("aws_codebuild_project.foo"), resource.TestCheckResourceAttr( "aws_codebuild_project.foo", "build_timeout", "5"), ), }, + }, + }) +} + +func TestAccAWSCodeBuildProject_vpc(t *testing.T) { + name := acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeBuildProjectDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodeBuildProjectConfig_basic(name, + hclVpcConfig("\"${aws_subnet.codebuild_subnet.id}\",\"${aws_subnet.codebuild_subnet_2.id}\""), hclVpcResources()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildProjectExists("aws_codebuild_project.foo"), + resource.TestCheckResourceAttr( + "aws_codebuild_project.foo", "build_timeout", "5"), + resource.TestCheckResourceAttrSet("aws_codebuild_project.foo", "vpc_config.0.vpc_id"), + resource.TestCheckResourceAttr("aws_codebuild_project.foo", "vpc_config.0.subnets.#", "2"), + resource.TestCheckResourceAttr("aws_codebuild_project.foo", "vpc_config.0.security_group_ids.#", "1"), + ), + }, + { + Config: testAccAWSCodeBuildProjectConfig_basic(name, hclVpcConfig("\"${aws_subnet.codebuild_subnet.id}\""), hclVpcResources()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildProjectExists("aws_codebuild_project.foo"), + resource.TestCheckResourceAttr( + "aws_codebuild_project.foo", "build_timeout", "5"), + resource.TestCheckResourceAttrSet("aws_codebuild_project.foo", "vpc_config.0.vpc_id"), + resource.TestCheckResourceAttr("aws_codebuild_project.foo", "vpc_config.0.subnets.#", "1"), + resource.TestCheckResourceAttr("aws_codebuild_project.foo", "vpc_config.0.security_group_ids.#", "1"), + ), + }, { Config: testAccAWSCodeBuildProjectConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodeBuildProjectExists("aws_codebuild_project.foo"), resource.TestCheckResourceAttr( "aws_codebuild_project.foo", "build_timeout", "50"), + resource.TestCheckNoResourceAttr("aws_codebuild_project.foo", "vpc_config"), ), }, }, @@ -350,21 +386,8 @@ func testAccCheckAWSCodeBuildProjectDestroy(s *terraform.State) error { return fmt.Errorf("Default error in CodeBuild Test") } -func testAccAWSCodeBuildProjectConfig_basic(rName string) string { +func testAccAWSCodeBuildProjectConfig_basic(rName, vpcConfig, vpcResources string) string { return fmt.Sprintf(` -resource "aws_vpc" "codebuild_vpc" { - cidr_block = "10.0.0.0/16" -} - -resource "aws_subnet" "codebuild_subnet" { - vpc_id = "${aws_vpc.codebuild_vpc.id}" - cidr_block = "10.0.0.0/24" -} - -resource "aws_security_group" "codebuild_security_group" { - vpc_id = "${aws_vpc.codebuild_vpc.id}" -} - resource "aws_iam_role" "codebuild_role" { name = "codebuild-role-%s" assume_role_policy = <