diff --git a/aws/provider.go b/aws/provider.go index e8e439536f5..bc49427d325 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -479,6 +479,7 @@ func Provider() *schema.Provider { "aws_codecommit_repository": resourceAwsCodeCommitRepository(), "aws_codecommit_trigger": resourceAwsCodeCommitTrigger(), "aws_codebuild_project": resourceAwsCodeBuildProject(), + "aws_codebuild_report_group": resourceAwsCodeBuildReportGroup(), "aws_codebuild_source_credential": resourceAwsCodeBuildSourceCredential(), "aws_codebuild_webhook": resourceAwsCodeBuildWebhook(), "aws_codepipeline": resourceAwsCodePipeline(), diff --git a/aws/resource_aws_codebuild_report_group.go b/aws/resource_aws_codebuild_report_group.go new file mode 100644 index 00000000000..0d86b56170c --- /dev/null +++ b/aws/resource_aws_codebuild_report_group.go @@ -0,0 +1,277 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/codebuild" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsCodeBuildReportGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCodeBuildReportGroupCreate, + Read: resourceAwsCodeBuildReportGroupRead, + Update: resourceAwsCodeBuildReportGroupUpdate, + Delete: resourceAwsCodeBuildReportGroupDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(2, 128), + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(codebuild.ReportType_Values(), false), + }, + "export_config": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(codebuild.ReportExportConfigType_Values(), false), + }, + "s3_destination": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + }, + "encryption_disabled": { + Type: schema.TypeBool, + Optional: true, + }, + "encryption_key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "packaging": { + Type: schema.TypeString, + Optional: true, + Default: codebuild.ReportPackagingTypeNone, + ValidateFunc: validation.StringInSlice(codebuild.ReportPackagingType_Values(), false), + }, + "path": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsCodeBuildReportGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codebuildconn + createOpts := &codebuild.CreateReportGroupInput{ + Name: aws.String(d.Get("name").(string)), + Type: aws.String(d.Get("type").(string)), + ExportConfig: expandAwsCodeBuildReportGroupExportConfig(d.Get("export_config").([]interface{})), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CodebuildTags(), + } + + resp, err := conn.CreateReportGroup(createOpts) + if err != nil { + return fmt.Errorf("error creating CodeBuild Report Groups: %w", err) + } + + d.SetId(aws.StringValue(resp.ReportGroup.Arn)) + + return resourceAwsCodeBuildReportGroupRead(d, meta) +} + +func resourceAwsCodeBuildReportGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codebuildconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + resp, err := conn.BatchGetReportGroups(&codebuild.BatchGetReportGroupsInput{ + ReportGroupArns: aws.StringSlice([]string{d.Id()}), + }) + if err != nil { + return fmt.Errorf("error Listing CodeBuild Report Groups: %w", err) + } + + if len(resp.ReportGroups) == 0 { + log.Printf("[WARN] CodeBuild Report Groups (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + reportGroup := resp.ReportGroups[0] + + if reportGroup == nil { + log.Printf("[WARN] CodeBuild Report Groups (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("arn", reportGroup.Arn) + d.Set("type", reportGroup.Type) + d.Set("name", reportGroup.Name) + + if err := d.Set("created", reportGroup.Created.Format(time.RFC3339)); err != nil { + return fmt.Errorf("error setting created: %w", err) + } + + if err := d.Set("export_config", flattenAwsCodeBuildReportGroupExportConfig(reportGroup.ExportConfig)); err != nil { + return fmt.Errorf("error setting export config: %w", err) + } + + if err := d.Set("tags", keyvaluetags.CodebuildKeyValueTags(reportGroup.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + return nil +} + +func resourceAwsCodeBuildReportGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codebuildconn + + input := &codebuild.UpdateReportGroupInput{ + Arn: aws.String(d.Id()), + } + + if d.HasChange("export_config") { + input.ExportConfig = expandAwsCodeBuildReportGroupExportConfig(d.Get("export_config").([]interface{})) + } + + if d.HasChange("tags") { + input.Tags = keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CodebuildTags() + } + + _, err := conn.UpdateReportGroup(input) + if err != nil { + return fmt.Errorf("error updating CodeBuild Report Groups: %w", err) + } + + return resourceAwsCodeBuildReportGroupRead(d, meta) +} + +func resourceAwsCodeBuildReportGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codebuildconn + + deleteOpts := &codebuild.DeleteReportGroupInput{ + Arn: aws.String(d.Id()), + } + + if _, err := conn.DeleteReportGroup(deleteOpts); err != nil { + return fmt.Errorf("error deleting CodeBuild Report Groups(%s): %w", d.Id(), err) + } + + return nil +} + +func expandAwsCodeBuildReportGroupExportConfig(config []interface{}) *codebuild.ReportExportConfig { + if len(config) == 0 { + return nil + } + + s := config[0].(map[string]interface{}) + exportConfig := &codebuild.ReportExportConfig{} + + if v, ok := s["type"]; ok { + exportConfig.ExportConfigType = aws.String(v.(string)) + } + + if v, ok := s["s3_destination"]; ok { + exportConfig.S3Destination = expandAwsCodeBuildReportGroupS3ReportExportConfig(v.([]interface{})) + } + + return exportConfig +} + +func flattenAwsCodeBuildReportGroupExportConfig(config *codebuild.ReportExportConfig) []map[string]interface{} { + settings := make(map[string]interface{}) + + if config == nil { + return nil + } + + settings["s3_destination"] = flattenAwsCodeBuildReportGroupS3ReportExportConfig(config.S3Destination) + settings["type"] = aws.StringValue(config.ExportConfigType) + + return []map[string]interface{}{settings} +} + +func expandAwsCodeBuildReportGroupS3ReportExportConfig(config []interface{}) *codebuild.S3ReportExportConfig { + if len(config) == 0 { + return nil + } + + s := config[0].(map[string]interface{}) + s3ReportExportConfig := &codebuild.S3ReportExportConfig{} + + if v, ok := s["bucket"]; ok { + s3ReportExportConfig.Bucket = aws.String(v.(string)) + } + if v, ok := s["encryption_disabled"]; ok { + s3ReportExportConfig.EncryptionDisabled = aws.Bool(v.(bool)) + } + + if v, ok := s["encryption_key"]; ok { + s3ReportExportConfig.EncryptionKey = aws.String(v.(string)) + } + + if v, ok := s["packaging"]; ok { + s3ReportExportConfig.Packaging = aws.String(v.(string)) + } + + if v, ok := s["path"]; ok { + s3ReportExportConfig.Path = aws.String(v.(string)) + } + + return s3ReportExportConfig +} + +func flattenAwsCodeBuildReportGroupS3ReportExportConfig(config *codebuild.S3ReportExportConfig) []map[string]interface{} { + settings := make(map[string]interface{}) + + if config == nil { + return nil + } + + settings["path"] = aws.StringValue(config.Path) + settings["bucket"] = aws.StringValue(config.Bucket) + settings["packaging"] = aws.StringValue(config.Packaging) + settings["encryption_disabled"] = aws.BoolValue(config.EncryptionDisabled) + + if config.EncryptionKey != nil { + settings["encryption_key"] = aws.StringValue(config.EncryptionKey) + } + + return []map[string]interface{}{settings} +} diff --git a/aws/resource_aws_codebuild_report_group_test.go b/aws/resource_aws_codebuild_report_group_test.go new file mode 100644 index 00000000000..c888712d55b --- /dev/null +++ b/aws/resource_aws_codebuild_report_group_test.go @@ -0,0 +1,335 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/codebuild" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAWSCodeBuildReportGroup_basic(t *testing.T) { + var reportGroup codebuild.ReportGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_codebuild_report_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodeBuild(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeBuildReportGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodeBuildReportGroupBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "export_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.type", "NO_EXPORT"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "codebuild", fmt.Sprintf("report-group/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCodeBuildReportGroup_export_s3(t *testing.T) { + var reportGroup codebuild.ReportGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_codebuild_report_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodeBuild(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeBuildReportGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodeBuildReportGroupS3ExportConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "export_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.type", "S3"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.packaging", "NONE"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.encryption_disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.path", "/some"), + resource.TestCheckResourceAttrPair(resourceName, "export_config.0.s3_destination.0.encryption_key", "aws_kms_key.test", "arn"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "codebuild", fmt.Sprintf("report-group/%s", rName)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCodeBuildReportGroupS3ExportUpdatedConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "export_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.type", "S3"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.packaging", "ZIP"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.encryption_disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "export_config.0.s3_destination.0.path", "/some2"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "codebuild", fmt.Sprintf("report-group/%s", rName)), + ), + }, + }, + }) +} + +func TestAccAWSCodeBuildReportGroup_tags(t *testing.T) { + var reportGroup codebuild.ReportGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_codebuild_report_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodeBuild(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeBuildReportGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodeBuildReportGroupConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCodeBuildReportGroupConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSCodeBuildReportGroupConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccAWSCodeBuildReportGroup_disappears(t *testing.T) { + var reportGroup codebuild.ReportGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_codebuild_report_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodeBuild(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeBuildReportGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCodeBuildReportGroupBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeBuildReportGroupExists(resourceName, &reportGroup), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCodeBuildReportGroup(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSCodeBuildReportGroupDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).codebuildconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_codebuild_report_group" { + continue + } + + resp, err := conn.BatchGetReportGroups(&codebuild.BatchGetReportGroupsInput{ + ReportGroupArns: aws.StringSlice([]string{rs.Primary.ID}), + }) + if err != nil { + return err + } + + if len(resp.ReportGroups) == 0 { + return nil + } + + for _, reportGroup := range resp.ReportGroups { + if rs.Primary.ID == aws.StringValue(reportGroup.Arn) { + return fmt.Errorf("Found Report Groups %s", rs.Primary.ID) + } + } + } + return nil +} + +func testAccCheckAWSCodeBuildReportGroupExists(name string, reportGroup *codebuild.ReportGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).codebuildconn + + resp, err := conn.BatchGetReportGroups(&codebuild.BatchGetReportGroupsInput{ + ReportGroupArns: aws.StringSlice([]string{rs.Primary.ID}), + }) + if err != nil { + return err + } + + if len(resp.ReportGroups) != 1 || + aws.StringValue(resp.ReportGroups[0].Arn) != rs.Primary.ID { + return fmt.Errorf("Report Group %s not found", rs.Primary.ID) + } + + *reportGroup = *resp.ReportGroups[0] + + return nil + } +} + +func testAccAWSCodeBuildReportGroupBasicConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_report_group" "test" { + name = %[1]q + type = "TEST" + + export_config { + type = "NO_EXPORT" + } +} +`, rName) +} + +func testAccAWSCodeBuildReportGroupBasicConfigS3ExportBase(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 + + policy = <