From b756b54bb2db83b1f417e9abea23374a2bd668c8 Mon Sep 17 00:00:00 2001 From: Atsushi Ishibashi Date: Sat, 30 Mar 2019 00:28:43 +0900 Subject: [PATCH 01/23] New Resource: aws_codepipeline_custom_action_type --- aws/provider.go | 1 + ...rce_aws_codepipeline_custom_action_type.go | 427 ++++++++++++++++++ ...ws_codepipeline_custom_action_type_test.go | 217 +++++++++ website/aws.erb | 3 + ...epipeline_custom_action_type.html.markdown | 87 ++++ 5 files changed, 735 insertions(+) create mode 100644 aws/resource_aws_codepipeline_custom_action_type.go create mode 100644 aws/resource_aws_codepipeline_custom_action_type_test.go create mode 100644 website/docs/r/codepipeline_custom_action_type.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 99d3ff2c48a7..eb6913d59731 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -373,6 +373,7 @@ func Provider() terraform.ResourceProvider { "aws_codebuild_project": resourceAwsCodeBuildProject(), "aws_codebuild_webhook": resourceAwsCodeBuildWebhook(), "aws_codepipeline": resourceAwsCodePipeline(), + "aws_codepipeline_custom_action_type": resourceAwsCodePipelineCustomActionType(), "aws_codepipeline_webhook": resourceAwsCodePipelineWebhook(), "aws_cur_report_definition": resourceAwsCurReportDefinition(), "aws_customer_gateway": resourceAwsCustomerGateway(), diff --git a/aws/resource_aws_codepipeline_custom_action_type.go b/aws/resource_aws_codepipeline_custom_action_type.go new file mode 100644 index 000000000000..b75357e54180 --- /dev/null +++ b/aws/resource_aws_codepipeline_custom_action_type.go @@ -0,0 +1,427 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsCodePipelineCustomActionType() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCodePipelineCustomActionTypeCreate, + Read: resourceAwsCodePipelineCustomActionTypeRead, + Delete: resourceAwsCodePipelineCustomActionTypeDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "category": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.ActionCategorySource, + codepipeline.ActionCategoryBuild, + codepipeline.ActionCategoryDeploy, + codepipeline.ActionCategoryTest, + codepipeline.ActionCategoryInvoke, + codepipeline.ActionCategoryApproval, + }, false), + }, + "configuration_properties": { + Type: schema.TypeList, + MaxItems: 10, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "key": { + Type: schema.TypeBool, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "queryable": { + Type: schema.TypeBool, + Optional: true, + }, + "required": { + Type: schema.TypeBool, + Required: true, + }, + "secret": { + Type: schema.TypeBool, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + codepipeline.ActionConfigurationPropertyTypeString, + codepipeline.ActionConfigurationPropertyTypeNumber, + codepipeline.ActionConfigurationPropertyTypeBoolean, + }, false), + }, + }, + }, + }, + "input_artifact_details": { + Type: schema.TypeList, + ForceNew: true, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "maximum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + "minimum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + }, + }, + }, + "output_artifact_details": { + Type: schema.TypeList, + ForceNew: true, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "maximum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + "minimum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + }, + }, + }, + "provider_name": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 25), + }, + "settings": { + Type: schema.TypeList, + ForceNew: true, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "entity_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "execution_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "revision_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "third_party_configuration_url": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "version": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 9), + }, + "owner": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsCodePipelineCustomActionTypeCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codepipelineconn + + input := &codepipeline.CreateCustomActionTypeInput{ + Category: aws.String(d.Get("category").(string)), + InputArtifactDetails: expandAwsCodePipelineArtifactDetails(d.Get("input_artifact_details").([]interface{})[0].(map[string]interface{})), + OutputArtifactDetails: expandAwsCodePipelineArtifactDetails(d.Get("output_artifact_details").([]interface{})[0].(map[string]interface{})), + Provider: aws.String(d.Get("provider_name").(string)), + Version: aws.String(d.Get("version").(string)), + } + + confProps := d.Get("configuration_properties").([]interface{}) + if len(confProps) > 0 { + input.ConfigurationProperties = expandAwsCodePipelineActionConfigurationProperty(confProps) + } + + settings := d.Get("settings").([]interface{}) + if len(settings) > 0 { + input.Settings = expandAwsCodePipelineActionTypeSettings(settings[0].(map[string]interface{})) + } + + resp, err := conn.CreateCustomActionType(input) + if err != nil { + return fmt.Errorf("Error creating CodePipeline CustomActionType: %s", err) + } + + if resp.ActionType == nil || resp.ActionType.Id == nil || + resp.ActionType.Id.Owner == nil || resp.ActionType.Id.Category == nil || resp.ActionType.Id.Provider == nil || resp.ActionType.Id.Version == nil { + return errors.New("Error creating CodePipeline CustomActionType: invalid response from AWS") + } + d.SetId(fmt.Sprintf("%s:%s:%s:%s", *resp.ActionType.Id.Owner, *resp.ActionType.Id.Category, *resp.ActionType.Id.Provider, *resp.ActionType.Id.Version)) + return resourceAwsCodePipelineCustomActionTypeRead(d, meta) +} + +func resourceAwsCodePipelineCustomActionTypeRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codepipelineconn + owner, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(d.Id()) + if err != nil { + return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) + } + + actionType, err := lookAwsCodePipelineCustomActionType(conn, d.Id()) + if err != nil { + return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) + } + if actionType == nil { + log.Printf("[INFO] Codepipeline CustomActionType %q not found", d.Id()) + d.SetId("") + return nil + } + + d.Set("owner", owner) + d.Set("category", category) + d.Set("provider_name", provider) + d.Set("version", version) + + if err := d.Set("configuration_properties", flattenAwsCodePipelineActionConfigurationProperty(actionType.ActionConfigurationProperties)); err != nil { + return fmt.Errorf("error setting configuration_properties: %s", err) + } + + if err := d.Set("input_artifact_details", flattenAwsCodePipelineArtifactDetails(actionType.InputArtifactDetails)); err != nil { + return fmt.Errorf("error setting input_artifact_details: %s", err) + } + + if err := d.Set("output_artifact_details", flattenAwsCodePipelineArtifactDetails(actionType.OutputArtifactDetails)); err != nil { + return fmt.Errorf("error setting output_artifact_details: %s", err) + } + + if err := d.Set("settings", flattenAwsCodePipelineActionTypeSettings(actionType.Settings)); err != nil { + return fmt.Errorf("error setting settings: %s", err) + } + + return nil +} + +func resourceAwsCodePipelineCustomActionTypeDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).codepipelineconn + + _, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(d.Id()) + if err != nil { + return fmt.Errorf("Error deleting CodePipeline CustomActionType: %s", err) + } + + input := &codepipeline.DeleteCustomActionTypeInput{ + Category: aws.String(category), + Provider: aws.String(provider), + Version: aws.String(version), + } + + _, err = conn.DeleteCustomActionType(input) + if err != nil { + if isAWSErr(err, codepipeline.ErrCodeActionTypeNotFoundException, "") { + return nil + } + return fmt.Errorf("error deleting CodePipeline CustomActionType (%s): %s", d.Id(), err) + } + + return nil +} + +func lookAwsCodePipelineCustomActionType(conn *codepipeline.CodePipeline, id string) (*codepipeline.ActionType, error) { + owner, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(id) + if err != nil { + return nil, fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) + } + + var actionType *codepipeline.ActionType + + input := &codepipeline.ListActionTypesInput{ + ActionOwnerFilter: aws.String(owner), + } + for { + resp, err := conn.ListActionTypes(input) + if err != nil { + return nil, fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) + } + + for _, v := range resp.ActionTypes { + if atid := v.Id; atid != nil { + if atid.Category == nil || atid.Provider == nil || atid.Version == nil { + continue + } + if *atid.Category == category && *atid.Provider == provider && *atid.Version == version { + actionType = v + break + } + } + } + + if actionType != nil { + break + } + + if resp.NextToken == nil { + break + } else { + input.NextToken = resp.NextToken + } + } + + return actionType, nil +} + +func decodeAwsCodePipelineCustomActionTypeId(id string) (owner string, category string, provider string, version string, e error) { + ss := strings.Split(id, ":") + if len(ss) != 4 { + e = fmt.Errorf("invalid AwsCodePipelineCustomActionType ID: %s", id) + return + } + owner, category, provider, version = ss[0], ss[1], ss[2], ss[3] + return +} + +func expandAwsCodePipelineArtifactDetails(d map[string]interface{}) *codepipeline.ArtifactDetails { + return &codepipeline.ArtifactDetails{ + MaximumCount: aws.Int64(int64(d["maximum_count"].(int))), + MinimumCount: aws.Int64(int64(d["minimum_count"].(int))), + } +} + +func flattenAwsCodePipelineArtifactDetails(ad *codepipeline.ArtifactDetails) []map[string]interface{} { + m := make(map[string]interface{}) + + m["maximum_count"] = aws.Int64Value(ad.MaximumCount) + m["minimum_count"] = aws.Int64Value(ad.MinimumCount) + + return []map[string]interface{}{m} +} + +func expandAwsCodePipelineActionConfigurationProperty(d []interface{}) []*codepipeline.ActionConfigurationProperty { + if len(d) == 0 { + return nil + } + result := make([]*codepipeline.ActionConfigurationProperty, 0, len(d)) + + for _, v := range d { + m := v.(map[string]interface{}) + acp := &codepipeline.ActionConfigurationProperty{ + Key: aws.Bool(m["key"].(bool)), + Name: aws.String(m["name"].(string)), + Required: aws.Bool(m["required"].(bool)), + Secret: aws.Bool(m["secret"].(bool)), + } + if raw, ok := m["description"]; ok && raw.(string) != "" { + acp.Description = aws.String(raw.(string)) + } + if raw, ok := m["queryable"]; ok { + acp.Queryable = aws.Bool(raw.(bool)) + } + if raw, ok := m["type"]; ok && raw.(string) != "" { + acp.Type = aws.String(raw.(string)) + } + result = append(result, acp) + } + + return result +} + +func flattenAwsCodePipelineActionConfigurationProperty(acps []*codepipeline.ActionConfigurationProperty) []interface{} { + result := make([]interface{}, 0, len(acps)) + + for _, acp := range acps { + m := map[string]interface{}{} + m["description"] = aws.StringValue(acp.Description) + m["key"] = aws.BoolValue(acp.Key) + m["name"] = aws.StringValue(acp.Name) + m["queryable"] = aws.BoolValue(acp.Queryable) + m["required"] = aws.BoolValue(acp.Required) + m["secret"] = aws.BoolValue(acp.Secret) + m["type"] = aws.StringValue(acp.Type) + result = append(result, m) + } + return result +} + +func expandAwsCodePipelineActionTypeSettings(d map[string]interface{}) *codepipeline.ActionTypeSettings { + if len(d) == 0 { + return nil + } + result := &codepipeline.ActionTypeSettings{} + + if raw, ok := d["entity_url_template"]; ok && raw.(string) != "" { + result.EntityUrlTemplate = aws.String(raw.(string)) + } + if raw, ok := d["execution_url_template"]; ok && raw.(string) != "" { + result.ExecutionUrlTemplate = aws.String(raw.(string)) + } + if raw, ok := d["revision_url_template"]; ok && raw.(string) != "" { + result.RevisionUrlTemplate = aws.String(raw.(string)) + } + if raw, ok := d["third_party_configuration_url"]; ok && raw.(string) != "" { + result.ThirdPartyConfigurationUrl = aws.String(raw.(string)) + } + + return result +} + +func flattenAwsCodePipelineActionTypeSettings(settings *codepipeline.ActionTypeSettings) []map[string]interface{} { + m := make(map[string]interface{}) + + if settings.EntityUrlTemplate != nil { + m["entity_url_template"] = aws.StringValue(settings.EntityUrlTemplate) + } + if settings.ExecutionUrlTemplate != nil { + m["execution_url_template"] = aws.StringValue(settings.ExecutionUrlTemplate) + } + if settings.RevisionUrlTemplate != nil { + m["revision_url_template"] = aws.StringValue(settings.RevisionUrlTemplate) + } + if settings.ThirdPartyConfigurationUrl != nil { + m["third_party_configuration_url"] = aws.StringValue(settings.ThirdPartyConfigurationUrl) + } + + if len(m) == 0 { + return nil + } + return []map[string]interface{}{m} +} diff --git a/aws/resource_aws_codepipeline_custom_action_type_test.go b/aws/resource_aws_codepipeline_custom_action_type_test.go new file mode 100644 index 000000000000..5a135af865f5 --- /dev/null +++ b/aws/resource_aws_codepipeline_custom_action_type_test.go @@ -0,0 +1,217 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsCodePipelineCustomActionType_basic(t *testing.T) { + resourceName := "aws_codepipeline_custom_action_type.test" + rName := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCodePipelineCustomActionType_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "category", "Build"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.maximum_count", "1"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "0"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.maximum_count", "1"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "0"), + resource.TestCheckResourceAttr(resourceName, "provider_name", fmt.Sprintf("tf-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "version", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsCodePipelineCustomActionType_settings(t *testing.T) { + resourceName := "aws_codepipeline_custom_action_type.test" + rName := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCodePipelineCustomActionType_settings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "settings.0.entity_url_template", "http://example.com"), + resource.TestCheckResourceAttr(resourceName, "settings.0.execution_url_template", "http://example.com"), + resource.TestCheckResourceAttr(resourceName, "settings.0.revision_url_template", "http://example.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsCodePipelineCustomActionType_configurationProperties(t *testing.T) { + resourceName := "aws_codepipeline_custom_action_type.test" + rName := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCodePipelineCustomActionType_configurationProperties(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.description", "tf-test"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.key", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.name", fmt.Sprintf("tf-test-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.queryable", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.required", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.secret", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.type", "String"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsCodePipelineCustomActionTypeExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No CodePipeline CustomActionType is set as ID") + } + + conn := testAccProvider.Meta().(*AWSClient).codepipelineconn + + actionType, err := lookAwsCodePipelineCustomActionType(conn, rs.Primary.ID) + if err != nil { + return err + } + if actionType == nil { + return fmt.Errorf("Not found CodePipeline CustomActionType: %s", rs.Primary.ID) + } + return nil + } +} + +func testAccCheckAwsCodePipelineCustomActionTypeDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).codepipelineconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_codepipeline_custom_action_type" { + continue + } + + actionType, err := lookAwsCodePipelineCustomActionType(conn, rs.Primary.ID) + if err != nil { + return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) + } + if actionType != nil { + return fmt.Errorf("CodePipeline CustomActionType still exists: %s", rs.Primary.ID) + } + + return err + } + + return nil +} + +func testAccAwsCodePipelineCustomActionType_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Build" + input_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + output_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + provider_name = "tf-%s" + version = "1" +} +`, rName) +} + +func testAccAwsCodePipelineCustomActionType_settings(rName string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Build" + input_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + output_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + provider_name = "tf-%s" + version = "1" + settings { + entity_url_template = "http://example.com" + execution_url_template = "http://example.com" + revision_url_template = "http://example.com" + } +} +`, rName) +} + +func testAccAwsCodePipelineCustomActionType_configurationProperties(rName string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Build" + input_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + output_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + provider_name = "tf-%s" + version = "1" + configuration_properties { + description = "tf-test" + key = true + name = "tf-test-%s" + queryable = true + required = true + secret = false + type = "String" + } +} +`, rName, rName) +} diff --git a/website/aws.erb b/website/aws.erb index 850275707776..2d489216e14e 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -781,6 +781,9 @@ aws_codepipeline + > + aws_codepipeline_custom_action_type + > aws_codepipeline_webhook diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown new file mode 100644 index 000000000000..9e0454c5f70d --- /dev/null +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -0,0 +1,87 @@ +--- +layout: "aws" +page_title: "AWS: aws_codepipeline_custom_action_type" +sidebar_current: "docs-aws-resource-codepipeline-webhook" +description: |- + Provides a CodePipeline CustomActionType. +--- + +# aws_codepipeline_custom_action_type + +Provides a CodeDeploy CustomActionType + +## Example Usage + +```hcl +resource "aws_codepipeline_custom_action_type" "example" { + category = "Build" + input_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + output_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + provider_name = "example" + version = "1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `category` - (Required) The category of the custom action. Valid values: `Source`, `Build`, `Deploy`, `Test`, `Invoke`, `Approval` +* `configuration_properties` - (Optional) The configuration properties for the custom action. Max 10 items. + +The `configuration_properties` object supports the following: + +* `description` - (Optional) The description of the action configuration property. +* `key` - (Required) Whether the configuration property is a key. +* `name` - (Required) The name of the action configuration property. +* `queryable` - (Optional) Indicates that the property will be used in conjunction with PollForJobs. +* `required` - (Required) Whether the configuration property is a required value. +* `secret`- (Required) Whether the configuration property is secret. +* `type`- (Optional) The type of the configuration property. Valid values: `String`, `Number`, `Boolean` + +* `input_artifact_details` - (Required) The details of the input artifact for the action. + +The `input_artifact_details` object supports the following: + +* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 +* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 + +* `output_artifact_details` - (Required) The details of the output artifact of the action. + +The `output_artifact_details` object supports the following: + +* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 +* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 + +* `provider_name` - (Required) The provider of the service used in the custom action +* `settings` - (Optional) The settings for an action type. + +The `settings` object supports the following: + +* `entity_url_template` - (Optional) The URL returned to the AWS CodePipeline console that provides a deep link to the resources of the external system. +* `execution_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the top-level landing page for the external system. +* `revision_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the page where customers can update or change the configuration of the external action. +* `third_party_configuration_url` - (Optional) The URL of a sign-up page where users can sign up for an external service and perform initial configuration of the action provided by that service. + +* `version` - (Required) The version identifier of the custom action. + +## Attribute Reference + +The following arguments are exported: + +* `id` - Composed of owner, category, provider and version. For example, `Custom:Build:terraform:1` +* `owner` - The creator of the action being called. + +## Import + +CodeDeploy CustomActionType can be imported using the `id`, e.g. + +``` +$ terraform import aws_codepipeline_custom_action_type.example Custom:Build:terraform:1 +``` From d0bcda3ec78318f2d4d94471d3b7258731852ce1 Mon Sep 17 00:00:00 2001 From: Atsushi Ishibashi Date: Sat, 30 Mar 2019 00:54:42 +0900 Subject: [PATCH 02/23] fix format, modify acctest --- ...rce_aws_codepipeline_custom_action_type.go | 5 ++- ...ws_codepipeline_custom_action_type_test.go | 34 +++++++++---------- ...epipeline_custom_action_type.html.markdown | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/aws/resource_aws_codepipeline_custom_action_type.go b/aws/resource_aws_codepipeline_custom_action_type.go index b75357e54180..c89a0089625b 100644 --- a/aws/resource_aws_codepipeline_custom_action_type.go +++ b/aws/resource_aws_codepipeline_custom_action_type.go @@ -376,7 +376,10 @@ func flattenAwsCodePipelineActionConfigurationProperty(acps []*codepipeline.Acti m["queryable"] = aws.BoolValue(acp.Queryable) m["required"] = aws.BoolValue(acp.Required) m["secret"] = aws.BoolValue(acp.Secret) - m["type"] = aws.StringValue(acp.Type) + // Currently AWS doesn't return type + if acp.Type != nil { + m["type"] = aws.StringValue(acp.Type) + } result = append(result, m) } return result diff --git a/aws/resource_aws_codepipeline_custom_action_type_test.go b/aws/resource_aws_codepipeline_custom_action_type_test.go index 5a135af865f5..63d680a447b4 100644 --- a/aws/resource_aws_codepipeline_custom_action_type_test.go +++ b/aws/resource_aws_codepipeline_custom_action_type_test.go @@ -90,7 +90,6 @@ func TestAccAwsCodePipelineCustomActionType_configurationProperties(t *testing.T resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.queryable", "true"), resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.required", "true"), resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.secret", "false"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.type", "String"), ), }, { @@ -160,7 +159,7 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" } `, rName) @@ -178,13 +177,13 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" - settings { - entity_url_template = "http://example.com" - execution_url_template = "http://example.com" - revision_url_template = "http://example.com" - } + settings { + entity_url_template = "http://example.com" + execution_url_template = "http://example.com" + revision_url_template = "http://example.com" + } } `, rName) } @@ -201,17 +200,16 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" - configuration_properties { - description = "tf-test" - key = true - name = "tf-test-%s" - queryable = true - required = true - secret = false - type = "String" - } + configuration_properties { + description = "tf-test" + key = true + name = "tf-test-%s" + queryable = true + required = true + secret = false + } } `, rName, rName) } diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index 9e0454c5f70d..386203615dae 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -23,7 +23,7 @@ resource "aws_codepipeline_custom_action_type" "example" { maximum_count = 1 minimum_count = 0 } - provider_name = "example" + provider_name = "example" version = "1" } ``` From f2e1f62c50ba6f1a4358c18d12f41b22103fb185 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 7 Oct 2022 16:38:39 -0400 Subject: [PATCH 03/23] r/aws_codepipeline: Alphabetize attributes. --- internal/service/codepipeline/codepipeline.go | 135 +++++++++--------- 1 file changed, 67 insertions(+), 68 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 7c4abc205b59..821ff880b8f3 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -37,6 +37,7 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f Read: resourceRead, Update: resourceUpdate, Delete: resourceDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -46,36 +47,11 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f Type: schema.TypeString, Computed: true, }, - - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 100), - validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), - ), - }, - - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, "artifact_store": { Type: schema.TypeSet, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "location": { - Type: schema.TypeString, - Required: true, - }, - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(codepipeline.ArtifactStoreType_Values(), false), - }, "encryption_key": { Type: schema.TypeList, MaxItems: 1, @@ -94,33 +70,53 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f }, }, }, + "location": { + Type: schema.TypeString, + Required: true, + }, "region": { Type: schema.TypeString, Optional: true, Computed: true, }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(codepipeline.ArtifactStoreType_Values(), false), + }, }, }, }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), + ), + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, "stage": { Type: schema.TypeList, MinItems: 2, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 100), - validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), - ), - }, "action": { Type: schema.TypeList, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "category": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(codepipeline.ActionCategory_Values(), false), + }, "configuration": { Type: schema.TypeMap, Optional: true, @@ -131,46 +127,46 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f Elem: &schema.Schema{Type: schema.TypeString}, DiffSuppressFunc: suppressStageActionConfiguration, }, - "category": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(codepipeline.ActionCategory_Values(), false), - }, - "owner": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(codepipeline.ActionOwner_Values(), false), - }, - "provider": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: resourceValidateActionProvider, + "input_artifacts": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - "version": { + "name": { Type: schema.TypeString, Required: true, ValidateFunc: validation.All( - validation.StringLenBetween(1, 9), - validation.StringMatch(regexp.MustCompile(`[0-9A-Za-z_-]+`), ""), + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), ), }, - "input_artifacts": { - Type: schema.TypeList, + "namespace": { + Type: schema.TypeString, Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9@\-_]+`), ""), + ), }, "output_artifacts": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "name": { + "owner": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(codepipeline.ActionOwner_Values(), false), + }, + "provider": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: resourceValidateActionProvider, + }, + "region": { Type: schema.TypeString, - Required: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 100), - validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), - ), + Optional: true, + Computed: true, }, "role_arn": { Type: schema.TypeString, @@ -183,22 +179,25 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f Computed: true, ValidateFunc: validation.IntBetween(1, 999), }, - "region": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "namespace": { + "version": { Type: schema.TypeString, - Optional: true, + Required: true, ValidateFunc: validation.All( - validation.StringLenBetween(1, 100), - validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9@\-_]+`), ""), + validation.StringLenBetween(1, 9), + validation.StringMatch(regexp.MustCompile(`[0-9A-Za-z_-]+`), ""), ), }, }, }, }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""), + ), + }, }, }, }, From 36ff9c54a19ac91cb6295c13063ab280df2a3246 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 7 Oct 2022 16:54:51 -0400 Subject: [PATCH 04/23] r/aws_codepipeline: 'WithoutTimeout' CRUD handlers (#15090). --- internal/service/codepipeline/codepipeline.go | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 821ff880b8f3..d8944b807c29 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -1,10 +1,12 @@ package codepipeline import ( + "context" "crypto/sha256" "encoding/hex" "errors" "fmt" + "log" "regexp" "strings" @@ -17,12 +19,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" - "github.com/hashicorp/terraform-provider-aws/names" ) const ( @@ -33,10 +33,10 @@ const ( func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-func-name return &schema.Resource{ - Create: resourceCreate, - Read: resourceRead, - Update: resourceUpdate, - Delete: resourceDelete, + CreateWithoutTimeout: resourceCreate, + ReadWithoutTimeout: resourceRead, + UpdateWithoutTimeout: resourceUpdate, + DeleteWithoutTimeout: resourceDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -209,15 +209,18 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f } } -func resourceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) pipeline, err := expand(d) + if err != nil { - return err + return diag.FromErr(err) } + params := &codepipeline.CreatePipelineInput{ Pipeline: pipeline, Tags: Tags(tags.IgnoreAWS()), @@ -227,7 +230,7 @@ func resourceCreate(d *schema.ResourceData, meta interface{}) error { err = resource.Retry(propagationTimeout, func() *resource.RetryError { var err error - resp, err = conn.CreatePipeline(params) + resp, err = conn.CreatePipelineWithContext(ctx, params) if tfawserr.ErrMessageContains(err, codepipeline.ErrCodeInvalidStructureException, "not authorized") { return resource.RetryableError(err) @@ -240,18 +243,18 @@ func resourceCreate(d *schema.ResourceData, meta interface{}) error { return nil }) if tfresource.TimedOut(err) { - resp, err = conn.CreatePipeline(params) + resp, err = conn.CreatePipelineWithContext(ctx, params) } if err != nil { - return fmt.Errorf("Error creating CodePipeline: %w", err) + return diag.Errorf("creating CodePipeline (%s): %s", name, err) } if resp.Pipeline == nil { - return fmt.Errorf("Error creating CodePipeline: invalid response from AWS") + return diag.Errorf("creating CodePipeline: invalid response from AWS") } d.SetId(aws.StringValue(resp.Pipeline.Name)) - return resourceRead(d, meta) + return resourceRead(ctx, d, meta) } func expand(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { @@ -528,23 +531,23 @@ func flattenActionsInputArtifacts(artifacts []*codepipeline.InputArtifact) []str return values } -func resourceRead(d *schema.ResourceData, meta interface{}) error { +func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - resp, err := conn.GetPipeline(&codepipeline.GetPipelineInput{ + resp, err := conn.GetPipelineWithContext(ctx, &codepipeline.GetPipelineInput{ Name: aws.String(d.Id()), }) if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { - create.LogNotFoundRemoveState(names.CodePipeline, create.ErrActionReading, ResNamePipeline, d.Id()) + log.Printf("[WARN] CodePipeline %s not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return create.Error(names.CodePipeline, create.ErrActionReading, ResNamePipeline, d.Id(), err) + return diag.Errorf("reading CodePipeline (%s): %s", d.Id(), err) } metadata := resp.Metadata @@ -552,16 +555,16 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error { if pipeline.ArtifactStore != nil { if err := d.Set("artifact_store", flattenArtifactStore(pipeline.ArtifactStore)); err != nil { - return err + return diag.Errorf("setting artifact_store: %s", err) } } else if pipeline.ArtifactStores != nil { if err := d.Set("artifact_store", flattenArtifactStores(pipeline.ArtifactStores)); err != nil { - return err + return diag.Errorf("setting artifact_store: %s", err) } } if err := d.Set("stage", flattenStages(pipeline.Stages, d)); err != nil { - return err + return diag.Errorf("setting stage: %s", err) } arn := aws.StringValue(metadata.PipelineArn) @@ -569,41 +572,43 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error { d.Set("name", pipeline.Name) d.Set("role_arn", pipeline.RoleArn) - tags, err := ListTags(conn, arn) + tags, err := ListTagsWithContext(ctx, conn, arn) if err != nil { - return fmt.Errorf("error listing tags for CodePipeline (%s): %w", arn, err) + return diag.Errorf("listing tags for CodePipeline (%s): %s", arn, err) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn if d.HasChangesExcept("tags", "tags_all") { pipeline, err := expand(d) + if err != nil { - return err + return diag.FromErr(err) } + params := &codepipeline.UpdatePipelineInput{ Pipeline: pipeline, } - _, err = conn.UpdatePipeline(params) + _, err = conn.UpdatePipelineWithContext(ctx, params) if err != nil { - return fmt.Errorf("[ERROR] Error updating CodePipeline (%s): %w", d.Id(), err) + return diag.Errorf("updating CodePipeline (%s): %s", d.Id(), err) } } @@ -611,18 +616,19 @@ func resourceUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, arn, o, n); err != nil { - return fmt.Errorf("error updating CodePipeline (%s) tags: %w", arn, err) + if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { + return diag.Errorf("updating CodePipeline (%s) tags: %s", arn, err) } } - return resourceRead(d, meta) + return resourceRead(ctx, d, meta) } -func resourceDelete(d *schema.ResourceData, meta interface{}) error { +func resourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn - _, err := conn.DeletePipeline(&codepipeline.DeletePipelineInput{ + log.Printf("[INFO] Deleting CodePipeline: %s", d.Id()) + _, err := conn.DeletePipelineWithContext(ctx, &codepipeline.DeletePipelineInput{ Name: aws.String(d.Id()), }) @@ -631,10 +637,10 @@ func resourceDelete(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return fmt.Errorf("error deleting CodePipeline (%s): %w", d.Id(), err) + return diag.Errorf("deleting CodePipeline (%s): %s", d.Id(), err) } - return err + return nil } func resourceValidateActionProvider(i interface{}, path cty.Path) diag.Diagnostics { From 47a9c822acb85357fda3e09c5d48ec8cb695bf1c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 7 Oct 2022 16:55:48 -0400 Subject: [PATCH 05/23] r/aws_codepipeline: Group CRUD handlers. --- internal/service/codepipeline/codepipeline.go | 224 +++++++++--------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index d8944b807c29..c04dde424406 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -257,6 +257,118 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{ return resourceRead(ctx, d, meta) } +func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + resp, err := conn.GetPipelineWithContext(ctx, &codepipeline.GetPipelineInput{ + Name: aws.String(d.Id()), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { + log.Printf("[WARN] CodePipeline %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading CodePipeline (%s): %s", d.Id(), err) + } + + metadata := resp.Metadata + pipeline := resp.Pipeline + + if pipeline.ArtifactStore != nil { + if err := d.Set("artifact_store", flattenArtifactStore(pipeline.ArtifactStore)); err != nil { + return diag.Errorf("setting artifact_store: %s", err) + } + } else if pipeline.ArtifactStores != nil { + if err := d.Set("artifact_store", flattenArtifactStores(pipeline.ArtifactStores)); err != nil { + return diag.Errorf("setting artifact_store: %s", err) + } + } + + if err := d.Set("stage", flattenStages(pipeline.Stages, d)); err != nil { + return diag.Errorf("setting stage: %s", err) + } + + arn := aws.StringValue(metadata.PipelineArn) + d.Set("arn", arn) + d.Set("name", pipeline.Name) + d.Set("role_arn", pipeline.RoleArn) + + tags, err := ListTagsWithContext(ctx, conn, arn) + + if err != nil { + return diag.Errorf("listing tags for CodePipeline (%s): %s", arn, err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("setting tags_all: %s", err) + } + + return nil +} + +func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + + if d.HasChangesExcept("tags", "tags_all") { + pipeline, err := expand(d) + + if err != nil { + return diag.FromErr(err) + } + + params := &codepipeline.UpdatePipelineInput{ + Pipeline: pipeline, + } + _, err = conn.UpdatePipelineWithContext(ctx, params) + + if err != nil { + return diag.Errorf("updating CodePipeline (%s): %s", d.Id(), err) + } + } + + arn := d.Get("arn").(string) + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { + return diag.Errorf("updating CodePipeline (%s) tags: %s", arn, err) + } + } + + return resourceRead(ctx, d, meta) +} + +func resourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + + log.Printf("[INFO] Deleting CodePipeline: %s", d.Id()) + _, err := conn.DeletePipelineWithContext(ctx, &codepipeline.DeletePipelineInput{ + Name: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("deleting CodePipeline (%s): %s", d.Id(), err) + } + + return nil +} + func expand(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { pipeline := codepipeline.PipelineDeclaration{ Name: aws.String(d.Get("name").(string)), @@ -531,118 +643,6 @@ func flattenActionsInputArtifacts(artifacts []*codepipeline.InputArtifact) []str return values } -func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).CodePipelineConn - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - - resp, err := conn.GetPipelineWithContext(ctx, &codepipeline.GetPipelineInput{ - Name: aws.String(d.Id()), - }) - - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { - log.Printf("[WARN] CodePipeline %s not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return diag.Errorf("reading CodePipeline (%s): %s", d.Id(), err) - } - - metadata := resp.Metadata - pipeline := resp.Pipeline - - if pipeline.ArtifactStore != nil { - if err := d.Set("artifact_store", flattenArtifactStore(pipeline.ArtifactStore)); err != nil { - return diag.Errorf("setting artifact_store: %s", err) - } - } else if pipeline.ArtifactStores != nil { - if err := d.Set("artifact_store", flattenArtifactStores(pipeline.ArtifactStores)); err != nil { - return diag.Errorf("setting artifact_store: %s", err) - } - } - - if err := d.Set("stage", flattenStages(pipeline.Stages, d)); err != nil { - return diag.Errorf("setting stage: %s", err) - } - - arn := aws.StringValue(metadata.PipelineArn) - d.Set("arn", arn) - d.Set("name", pipeline.Name) - d.Set("role_arn", pipeline.RoleArn) - - tags, err := ListTagsWithContext(ctx, conn, arn) - - if err != nil { - return diag.Errorf("listing tags for CodePipeline (%s): %s", arn, err) - } - - tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - - //lintignore:AWSR002 - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return diag.Errorf("setting tags: %s", err) - } - - if err := d.Set("tags_all", tags.Map()); err != nil { - return diag.Errorf("setting tags_all: %s", err) - } - - return nil -} - -func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).CodePipelineConn - - if d.HasChangesExcept("tags", "tags_all") { - pipeline, err := expand(d) - - if err != nil { - return diag.FromErr(err) - } - - params := &codepipeline.UpdatePipelineInput{ - Pipeline: pipeline, - } - _, err = conn.UpdatePipelineWithContext(ctx, params) - - if err != nil { - return diag.Errorf("updating CodePipeline (%s): %s", d.Id(), err) - } - } - - arn := d.Get("arn").(string) - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - - if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { - return diag.Errorf("updating CodePipeline (%s) tags: %s", arn, err) - } - } - - return resourceRead(ctx, d, meta) -} - -func resourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).CodePipelineConn - - log.Printf("[INFO] Deleting CodePipeline: %s", d.Id()) - _, err := conn.DeletePipelineWithContext(ctx, &codepipeline.DeletePipelineInput{ - Name: aws.String(d.Id()), - }) - - if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { - return nil - } - - if err != nil { - return diag.Errorf("deleting CodePipeline (%s): %s", d.Id(), err) - } - - return nil -} - func resourceValidateActionProvider(i interface{}, path cty.Path) diag.Diagnostics { v, ok := i.(string) if !ok { From 353c96dfd4f4a120e97894bf56b6d3f7a4716205 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 08:08:27 -0400 Subject: [PATCH 06/23] r/aws_codepipeline: Correct function names. --- internal/provider/provider.go | 2 +- internal/service/codepipeline/codepipeline.go | 22 ++++++------- .../service/codepipeline/codepipeline_test.go | 2 +- internal/service/codepipeline/sweep.go | 31 +++++++++---------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 795aada52711..09e020303d80 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1172,7 +1172,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_codedeploy_deployment_config": deploy.ResourceDeploymentConfig(), "aws_codedeploy_deployment_group": deploy.ResourceDeploymentGroup(), - "aws_codepipeline": codepipeline.ResourceCodePipeline(), + "aws_codepipeline": codepipeline.ResourcePipeline(), "aws_codepipeline_webhook": codepipeline.ResourceWebhook(), "aws_codestarconnections_connection": codestarconnections.ResourceConnection(), diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index c04dde424406..81bc60f0e9a9 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -31,12 +31,12 @@ const ( gitHubActionConfigurationOAuthToken = "OAuthToken" ) -func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-func-name +func ResourcePipeline() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceCreate, - ReadWithoutTimeout: resourceRead, - UpdateWithoutTimeout: resourceUpdate, - DeleteWithoutTimeout: resourceDelete, + CreateWithoutTimeout: resourcePipelineCreate, + ReadWithoutTimeout: resourcePipelineRead, + UpdateWithoutTimeout: resourcePipelineUpdate, + DeleteWithoutTimeout: resourcePipelineDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -209,7 +209,7 @@ func ResourceCodePipeline() *schema.Resource { // nosemgrep:ci.codepipeline-in-f } } -func resourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourcePipelineCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) @@ -254,10 +254,10 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{ d.SetId(aws.StringValue(resp.Pipeline.Name)) - return resourceRead(ctx, d, meta) + return resourcePipelineRead(ctx, d, meta) } -func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourcePipelineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig @@ -318,7 +318,7 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) return nil } -func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourcePipelineUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn if d.HasChangesExcept("tags", "tags_all") { @@ -347,10 +347,10 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{ } } - return resourceRead(ctx, d, meta) + return resourcePipelineRead(ctx, d, meta) } -func resourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourcePipelineDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn log.Printf("[INFO] Deleting CodePipeline: %s", d.Id()) diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index f9c83ddda656..ae9eac36215e 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -143,7 +143,7 @@ func TestAccCodePipeline_disappears(t *testing.T) { Config: testAccCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( testAccCheckExists(resourceName, &p), - acctest.CheckResourceDisappears(acctest.Provider, tfcodepipeline.ResourceCodePipeline(), resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfcodepipeline.ResourcePipeline(), resourceName), ), ExpectNonEmptyPlan: true, }, diff --git a/internal/service/codepipeline/sweep.go b/internal/service/codepipeline/sweep.go index 08696ae883cd..1161ff68b95b 100644 --- a/internal/service/codepipeline/sweep.go +++ b/internal/service/codepipeline/sweep.go @@ -9,7 +9,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" - "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/sweep" @@ -24,27 +23,23 @@ func init() { func sweepPipelines(region string) error { client, err := sweep.SharedRegionalSweepClient(region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - + input := &codepipeline.ListPipelinesInput{} conn := client.(*conns.AWSClient).CodePipelineConn sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - - input := &codepipeline.ListPipelinesInput{} err = conn.ListPipelinesPages(input, func(page *codepipeline.ListPipelinesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, pipeline := range page.Pipelines { - r := ResourceCodePipeline() + for _, v := range page.Pipelines { + r := ResourcePipeline() d := r.Data(nil) - d.SetId(aws.StringValue(pipeline.Name)) + d.SetId(aws.StringValue(v.Name)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -52,18 +47,20 @@ func sweepPipelines(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing Codepipeline Pipeline for %s: %w", region, err)) + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Codepipeline Pipeline sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping Codepipeline Pipeline for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing Codepipeline Pipelines (%s): %w", region, err) } - if sweep.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping Codepipeline Pipeline sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Codepipeline Pipelines (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } From 4f72b2f8045cdf35d79d261c82848adf82c4accf Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 08:23:12 -0400 Subject: [PATCH 07/23] Add 'FindPipelineByName'. --- internal/service/codepipeline/codepipeline.go | 35 +++++++++++++++---- .../service/codepipeline/codepipeline_test.go | 28 +++++++-------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 81bc60f0e9a9..cab3cbd380c0 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -262,11 +262,9 @@ func resourcePipelineRead(ctx context.Context, d *schema.ResourceData, meta inte defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - resp, err := conn.GetPipelineWithContext(ctx, &codepipeline.GetPipelineInput{ - Name: aws.String(d.Id()), - }) + output, err := FindPipelineByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CodePipeline %s not found, removing from state", d.Id()) d.SetId("") return nil @@ -276,8 +274,8 @@ func resourcePipelineRead(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("reading CodePipeline (%s): %s", d.Id(), err) } - metadata := resp.Metadata - pipeline := resp.Pipeline + metadata := output.Metadata + pipeline := output.Pipeline if pipeline.ArtifactStore != nil { if err := d.Set("artifact_store", flattenArtifactStore(pipeline.ArtifactStore)); err != nil { @@ -369,6 +367,31 @@ func resourcePipelineDelete(ctx context.Context, d *schema.ResourceData, meta in return nil } +func FindPipelineByName(ctx context.Context, conn *codepipeline.CodePipeline, name string) (*codepipeline.GetPipelineOutput, error) { + input := &codepipeline.GetPipelineInput{ + Name: aws.String(name), + } + + output, err := conn.GetPipelineWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodePipelineNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Metadata == nil || output.Pipeline == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + func expand(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { pipeline := codepipeline.PipelineDeclaration{ Name: aws.String(d.Get("name").(string)), diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index ae9eac36215e..9f9d0cc7ab74 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -6,16 +6,15 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/codepipeline" "github.com/aws/aws-sdk-go/service/codestarconnections" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfcodepipeline "github.com/hashicorp/terraform-provider-aws/internal/service/codepipeline" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccCodePipeline_basic(t *testing.T) { @@ -551,7 +550,7 @@ func TestAccCodePipeline_withGitHubV1SourceAction(t *testing.T) { }) } -func testAccCheckExists(n string, pipeline *codepipeline.PipelineDeclaration) resource.TestCheckFunc { +func testAccCheckExists(n string, v *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -564,14 +563,13 @@ func testAccCheckExists(n string, pipeline *codepipeline.PipelineDeclaration) re conn := acctest.Provider.Meta().(*conns.AWSClient).CodePipelineConn - out, err := conn.GetPipeline(&codepipeline.GetPipelineInput{ - Name: aws.String(rs.Primary.ID), - }) + output, err := tfcodepipeline.FindPipelineByName(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *pipeline = *out.Pipeline + *v = *output.Pipeline return nil } @@ -585,17 +583,17 @@ func testAccCheckDestroy(s *terraform.State) error { continue } - _, err := conn.GetPipeline(&codepipeline.GetPipelineInput{ - Name: aws.String(rs.Primary.ID), - }) + _, err := tfcodepipeline.FindPipelineByName(context.Background(), conn, rs.Primary.ID) - if err == nil { - return fmt.Errorf("Expected AWS CodePipeline to be gone, but was still found") - } - if tfawserr.ErrCodeEquals(err, "PipelineNotFoundException") { + if tfresource.NotFound(err) { continue } - return err + + if err != nil { + return err + } + + return fmt.Errorf("CodePipeline %s still exists", rs.Primary.ID) } return nil From 8f281a0c5e9441c0fb0d09afb16ce498f2f6e829 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 09:41:16 -0400 Subject: [PATCH 08/23] r/aws_codepipeline: Tidy up expanders. --- internal/service/codepipeline/codepipeline.go | 500 +++++++++++------- .../service/codepipeline/codepipeline_test.go | 121 ----- 2 files changed, 306 insertions(+), 315 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index cab3cbd380c0..9102d16a1941 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -214,45 +214,30 @@ func resourcePipelineCreate(ctx context.Context, d *schema.ResourceData, meta in defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - name := d.Get("name").(string) - pipeline, err := expand(d) + pipeline, err := expandPipelineDeclaration(d) if err != nil { return diag.FromErr(err) } - params := &codepipeline.CreatePipelineInput{ + name := d.Get("name").(string) + input := &codepipeline.CreatePipelineInput{ Pipeline: pipeline, - Tags: Tags(tags.IgnoreAWS()), } - var resp *codepipeline.CreatePipelineOutput - err = resource.Retry(propagationTimeout, func() *resource.RetryError { - var err error - - resp, err = conn.CreatePipelineWithContext(ctx, params) - - if tfawserr.ErrMessageContains(err, codepipeline.ErrCodeInvalidStructureException, "not authorized") { - return resource.RetryableError(err) - } + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } - if err != nil { - return resource.NonRetryableError(err) - } + outputRaw, err := tfresource.RetryWhenAWSErrMessageContainsContext(ctx, propagationTimeout, func() (interface{}, error) { + return conn.CreatePipelineWithContext(ctx, input) + }, codepipeline.ErrCodeInvalidStructureException, "not authorized") - return nil - }) - if tfresource.TimedOut(err) { - resp, err = conn.CreatePipelineWithContext(ctx, params) - } if err != nil { return diag.Errorf("creating CodePipeline (%s): %s", name, err) } - if resp.Pipeline == nil { - return diag.Errorf("creating CodePipeline: invalid response from AWS") - } - d.SetId(aws.StringValue(resp.Pipeline.Name)) + d.SetId(aws.StringValue(outputRaw.(*codepipeline.CreatePipelineOutput).Pipeline.Name)) return resourcePipelineRead(ctx, d, meta) } @@ -320,16 +305,15 @@ func resourcePipelineUpdate(ctx context.Context, d *schema.ResourceData, meta in conn := meta.(*conns.AWSClient).CodePipelineConn if d.HasChangesExcept("tags", "tags_all") { - pipeline, err := expand(d) + pipeline, err := expandPipelineDeclaration(d) if err != nil { return diag.FromErr(err) } - params := &codepipeline.UpdatePipelineInput{ + _, err = conn.UpdatePipelineWithContext(ctx, &codepipeline.UpdatePipelineInput{ Pipeline: pipeline, - } - _, err = conn.UpdatePipelineWithContext(ctx, params) + }) if err != nil { return diag.Errorf("updating CodePipeline (%s): %s", d.Id(), err) @@ -392,77 +376,6 @@ func FindPipelineByName(ctx context.Context, conn *codepipeline.CodePipeline, na return output, nil } -func expand(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { - pipeline := codepipeline.PipelineDeclaration{ - Name: aws.String(d.Get("name").(string)), - RoleArn: aws.String(d.Get("role_arn").(string)), - Stages: expandStages(d), - } - - pipelineArtifactStores, err := ExpandArtifactStores(d.Get("artifact_store").(*schema.Set).List()) - if err != nil { - return nil, err - } - if len(pipelineArtifactStores) == 1 { - for _, v := range pipelineArtifactStores { - pipeline.ArtifactStore = v - } - } else { - pipeline.ArtifactStores = pipelineArtifactStores - } - - return &pipeline, nil -} - -func ExpandArtifactStores(configs []interface{}) (map[string]*codepipeline.ArtifactStore, error) { - if len(configs) == 0 { - return nil, nil - } - - regions := make([]string, 0, len(configs)) - pipelineArtifactStores := make(map[string]*codepipeline.ArtifactStore) - for _, config := range configs { - region, store := expandArtifactStoreData(config.(map[string]interface{})) - regions = append(regions, region) - pipelineArtifactStores[region] = store - } - - if len(regions) == 1 { - if regions[0] != "" { - return nil, errors.New("region cannot be set for a single-region CodePipeline") - } - } else { - for _, v := range regions { - if v == "" { - return nil, errors.New("region must be set for a cross-region CodePipeline") - } - } - if len(configs) != len(pipelineArtifactStores) { - return nil, errors.New("only one Artifact Store can be defined per region for a cross-region CodePipeline") - } - } - - return pipelineArtifactStores, nil -} - -func expandArtifactStoreData(data map[string]interface{}) (string, *codepipeline.ArtifactStore) { - pipelineArtifactStore := 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)), - } - pipelineArtifactStore.EncryptionKey = &ek - } - - return data["region"].(string), &pipelineArtifactStore -} - func flattenArtifactStore(artifactStore *codepipeline.ArtifactStore) []interface{} { if artifactStore == nil { return []interface{}{} @@ -491,22 +404,6 @@ func flattenArtifactStores(artifactStores map[string]*codepipeline.ArtifactStore return values } -func expandStages(d *schema.ResourceData) []*codepipeline.StageDeclaration { - stages := d.Get("stage").([]interface{}) - pipelineStages := []*codepipeline.StageDeclaration{} - - for _, stage := range stages { - data := stage.(map[string]interface{}) - a := data["action"].([]interface{}) - actions := expandActions(a) - pipelineStages = append(pipelineStages, &codepipeline.StageDeclaration{ - Name: aws.String(data["name"].(string)), - Actions: actions, - }) - } - return pipelineStages -} - func flattenStages(stages []*codepipeline.StageDeclaration, d *schema.ResourceData) []interface{} { stagesList := []interface{}{} for si, stage := range stages { @@ -518,58 +415,6 @@ func flattenStages(stages []*codepipeline.StageDeclaration, d *schema.ResourceDa return stagesList } -func expandActions(a []interface{}) []*codepipeline.ActionDeclaration { - actions := []*codepipeline.ActionDeclaration{} - for _, config := range a { - data := config.(map[string]interface{}) - - conf := flex.ExpandStringMap(data["configuration"].(map[string]interface{})) - - action := codepipeline.ActionDeclaration{ - ActionTypeId: &codepipeline.ActionTypeId{ - Category: aws.String(data["category"].(string)), - Owner: aws.String(data["owner"].(string)), - - Provider: aws.String(data["provider"].(string)), - Version: aws.String(data["version"].(string)), - }, - Name: aws.String(data["name"].(string)), - Configuration: conf, - } - - oa := data["output_artifacts"].([]interface{}) - if len(oa) > 0 { - outputArtifacts := expandActionsOutputArtifacts(oa) - action.OutputArtifacts = outputArtifacts - - } - ia := data["input_artifacts"].([]interface{}) - if len(ia) > 0 { - inputArtifacts := expandActionsInputArtifacts(ia) - action.InputArtifacts = inputArtifacts - - } - ra := data["role_arn"].(string) - if ra != "" { - action.RoleArn = aws.String(ra) - } - ro := data["run_order"].(int) - if ro > 0 { - action.RunOrder = aws.Int64(int64(ro)) - } - r := data["region"].(string) - if r != "" { - action.Region = aws.String(r) - } - ns := data["namespace"].(string) - if len(ns) > 0 { - action.Namespace = aws.String(ns) - } - actions = append(actions, &action) - } - return actions -} - func flattenStageActions(si int, actions []*codepipeline.ActionDeclaration, d *schema.ResourceData) []interface{} { actionsList := []interface{}{} for ai, action := range actions { @@ -624,19 +469,6 @@ func flattenStageActions(si int, actions []*codepipeline.ActionDeclaration, d *s return actionsList } -func expandActionsOutputArtifacts(s []interface{}) []*codepipeline.OutputArtifact { - outputArtifacts := []*codepipeline.OutputArtifact{} - for _, artifact := range s { - if artifact == nil { - continue - } - outputArtifacts = append(outputArtifacts, &codepipeline.OutputArtifact{ - Name: aws.String(artifact.(string)), - }) - } - return outputArtifacts -} - func flattenActionsOutputArtifacts(artifacts []*codepipeline.OutputArtifact) []string { values := []string{} for _, artifact := range artifacts { @@ -645,19 +477,6 @@ func flattenActionsOutputArtifacts(artifacts []*codepipeline.OutputArtifact) []s return values } -func expandActionsInputArtifacts(s []interface{}) []*codepipeline.InputArtifact { - outputArtifacts := []*codepipeline.InputArtifact{} - for _, artifact := range s { - if artifact == nil { - continue - } - outputArtifacts = append(outputArtifacts, &codepipeline.InputArtifact{ - Name: aws.String(artifact.(string)), - }) - } - return outputArtifacts -} - func flattenActionsInputArtifacts(artifacts []*codepipeline.InputArtifact) []string { values := []string{} for _, artifact := range artifacts { @@ -709,3 +528,296 @@ func hashGitHubToken(token string) string { sum := sha256.Sum256([]byte(token)) return gitHubTokenHashPrefix + hex.EncodeToString(sum[:]) } + +func expandPipelineDeclaration(d *schema.ResourceData) (*codepipeline.PipelineDeclaration, error) { + apiObject := &codepipeline.PipelineDeclaration{} + + if v, ok := d.GetOk("artifact_store"); ok && v.(*schema.Set).Len() > 0 { + artifactStores := expandArtifactStores(v.(*schema.Set).List()) + + switch n := len(artifactStores); n { + case 1: + for region, v := range artifactStores { + if region != "" { + return nil, errors.New("region cannot be set for a single-region CodePipeline") + } + apiObject.ArtifactStore = v + } + + default: + for region := range artifactStores { + if region == "" { + return nil, errors.New("region must be set for a cross-region CodePipeline") + } + } + if n != v.(*schema.Set).Len() { + return nil, errors.New("only one Artifact Store can be defined per region for a cross-region CodePipeline") + } + apiObject.ArtifactStores = artifactStores + } + } + + if v, ok := d.GetOk("name"); ok { + apiObject.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("role_arn"); ok { + apiObject.RoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("stage"); ok && len(v.([]interface{})) > 0 { + apiObject.Stages = expandStageDeclarations(v.([]interface{})) + } + + return apiObject, nil +} + +func expandArtifactStore(tfMap map[string]interface{}) *codepipeline.ArtifactStore { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.ArtifactStore{} + + if v, ok := tfMap["encryption_key"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.EncryptionKey = expandEncryptionKey(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["location"].(string); ok && v != "" { + apiObject.Location = aws.String(v) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + apiObject.Type = aws.String(v) + } + + return apiObject +} + +func expandArtifactStores(tfList []interface{}) map[string]*codepipeline.ArtifactStore { + if len(tfList) == 0 { + return nil + } + + apiObjects := make(map[string]*codepipeline.ArtifactStore, 0) + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandArtifactStore(tfMap) + + if apiObject == nil { + continue + } + + var region string + + if v, ok := tfMap["region"].(string); ok && v != "" { + region = v + } + + apiObjects[region] = apiObject + } + + return apiObjects +} + +func expandEncryptionKey(tfMap map[string]interface{}) *codepipeline.EncryptionKey { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.EncryptionKey{} + + if v, ok := tfMap["id"].(string); ok && v != "" { + apiObject.Id = aws.String(v) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + apiObject.Type = aws.String(v) + } + + return apiObject +} + +func expandStageDeclaration(tfMap map[string]interface{}) *codepipeline.StageDeclaration { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.StageDeclaration{} + + if v, ok := tfMap["action"].([]interface{}); ok && len(v) > 0 { + apiObject.Actions = expandActionDeclarations(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + return apiObject +} + +func expandStageDeclarations(tfList []interface{}) []*codepipeline.StageDeclaration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*codepipeline.StageDeclaration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandStageDeclaration(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandActionDeclaration(tfMap map[string]interface{}) *codepipeline.ActionDeclaration { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.ActionDeclaration{ + ActionTypeId: &codepipeline.ActionTypeId{}, + } + + if v, ok := tfMap["category"].(string); ok && v != "" { + apiObject.ActionTypeId.Category = aws.String(v) + } + + if v, ok := tfMap["configuration"].(map[string]interface{}); ok && len(v) > 0 { + apiObject.Configuration = flex.ExpandStringMap(v) + } + + if v, ok := tfMap["input_artifacts"].([]interface{}); ok && len(v) > 0 { + apiObject.InputArtifacts = expandInputArtifacts(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + if v, ok := tfMap["namespace"].(string); ok && v != "" { + apiObject.Namespace = aws.String(v) + } + + if v, ok := tfMap["output_artifacts"].([]interface{}); ok && len(v) > 0 { + apiObject.OutputArtifacts = expandOutputArtifacts(v) + } + + if v, ok := tfMap["owner"].(string); ok && v != "" { + apiObject.ActionTypeId.Owner = aws.String(v) + } + + if v, ok := tfMap["provider"].(string); ok && v != "" { + apiObject.ActionTypeId.Provider = aws.String(v) + } + + if v, ok := tfMap["region"].(string); ok && v != "" { + apiObject.Region = aws.String(v) + } + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + if v, ok := tfMap["run_order"].(int); ok && v != 0 { + apiObject.RunOrder = aws.Int64(int64(v)) + } + + if v, ok := tfMap["version"].(string); ok && v != "" { + apiObject.ActionTypeId.Version = aws.String(v) + } + + return apiObject +} + +func expandActionDeclarations(tfList []interface{}) []*codepipeline.ActionDeclaration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*codepipeline.ActionDeclaration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandActionDeclaration(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandInputArtifacts(tfList []interface{}) []*codepipeline.InputArtifact { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*codepipeline.InputArtifact + + for _, v := range tfList { + v, ok := v.(string) + + if !ok { + continue + } + + apiObject := &codepipeline.InputArtifact{ + Name: aws.String(v), + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandOutputArtifacts(tfList []interface{}) []*codepipeline.OutputArtifact { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*codepipeline.OutputArtifact + + for _, v := range tfList { + v, ok := v.(string) + + if !ok { + continue + } + + apiObject := &codepipeline.OutputArtifact{ + Name: aws.String(v), + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index 9f9d0cc7ab74..67946b15cedb 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -1553,124 +1553,3 @@ resource "aws_codepipeline" "test" { } `, rName, githubToken)) } - -func TestExpandArtifactStoresValidation(t *testing.T) { - cases := []struct { - Name string - Input []interface{} - ExpectedError string - }{ - { - Name: "Single-region", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "", - }, - }, - }, - { - Name: "Single-region, names region", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-west-2", //lintignore:AWSAT003 - }, - }, - ExpectedError: "region cannot be set for a single-region CodePipeline", - }, - { - Name: "Cross-region", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-west-2", //lintignore:AWSAT003 - }, - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-east-1", //lintignore:AWSAT003 - }, - }, - }, - { - Name: "Cross-region, no regions", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "", - }, - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "", - }, - }, - ExpectedError: "region must be set for a cross-region CodePipeline", - }, - { - Name: "Cross-region, not all regions", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-west-2", //lintignore:AWSAT003 - }, - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "", - }, - }, - ExpectedError: "region must be set for a cross-region CodePipeline", - }, - { - Name: "Duplicate regions", - Input: []interface{}{ - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-west-2", //lintignore:AWSAT003 - }, - map[string]interface{}{ - "location": "", - "type": "", - "encryption_key": []interface{}{}, - "region": "us-west-2", //lintignore:AWSAT003 - }, - }, - ExpectedError: "only one Artifact Store can be defined per region for a cross-region CodePipeline", - }, - } - - for _, tc := range cases { - tc := tc - _, err := tfcodepipeline.ExpandArtifactStores(tc.Input) - if tc.ExpectedError == "" { - if err != nil { - t.Errorf("%s: Did not expect an error, but got: %s", tc.Name, err) - } - } else { - if err == nil { - t.Errorf("%s: Expected an error, but did not get one", tc.Name) - } else { - if err.Error() != tc.ExpectedError { - t.Errorf("%s: Expected error %q, got %s", tc.Name, tc.ExpectedError, err) - } - } - } - } -} From 9e76ae1c6b0333e5fa37fd79d9628388cbacdbb1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 11:58:06 -0400 Subject: [PATCH 09/23] r/aws_codepipeline: Tidy up flatteners. --- internal/service/codepipeline/codepipeline.go | 349 ++++++++++++------ 1 file changed, 232 insertions(+), 117 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 9102d16a1941..3f7833d0cf11 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -125,7 +125,7 @@ func ResourcePipeline() *schema.Resource { validation.MapKeyLenBetween(1, 1000), ), Elem: &schema.Schema{Type: schema.TypeString}, - DiffSuppressFunc: suppressStageActionConfiguration, + DiffSuppressFunc: pipelineSuppressStageActionConfigurationDiff, }, "input_artifacts": { Type: schema.TypeList, @@ -161,7 +161,7 @@ func ResourcePipeline() *schema.Resource { "provider": { Type: schema.TypeString, Required: true, - ValidateDiagFunc: resourceValidateActionProvider, + ValidateDiagFunc: pipelineValidateActionProvider, }, "region": { Type: schema.TypeString, @@ -263,7 +263,7 @@ func resourcePipelineRead(ctx context.Context, d *schema.ResourceData, meta inte pipeline := output.Pipeline if pipeline.ArtifactStore != nil { - if err := d.Set("artifact_store", flattenArtifactStore(pipeline.ArtifactStore)); err != nil { + if err := d.Set("artifact_store", []interface{}{flattenArtifactStore(pipeline.ArtifactStore)}); err != nil { return diag.Errorf("setting artifact_store: %s", err) } } else if pipeline.ArtifactStores != nil { @@ -272,7 +272,7 @@ func resourcePipelineRead(ctx context.Context, d *schema.ResourceData, meta inte } } - if err := d.Set("stage", flattenStages(pipeline.Stages, d)); err != nil { + if err := d.Set("stage", flattenStageDeclarations(d, pipeline.Stages)); err != nil { return diag.Errorf("setting stage: %s", err) } @@ -376,116 +376,7 @@ func FindPipelineByName(ctx context.Context, conn *codepipeline.CodePipeline, na return output, nil } -func flattenArtifactStore(artifactStore *codepipeline.ArtifactStore) []interface{} { - if artifactStore == nil { - return []interface{}{} - } - - values := map[string]interface{}{} - values["type"] = aws.StringValue(artifactStore.Type) - values["location"] = aws.StringValue(artifactStore.Location) - if artifactStore.EncryptionKey != nil { - as := map[string]interface{}{ - "id": aws.StringValue(artifactStore.EncryptionKey.Id), - "type": aws.StringValue(artifactStore.EncryptionKey.Type), - } - values["encryption_key"] = []interface{}{as} - } - return []interface{}{values} -} - -func flattenArtifactStores(artifactStores map[string]*codepipeline.ArtifactStore) []interface{} { - values := []interface{}{} - for region, artifactStore := range artifactStores { - store := flattenArtifactStore(artifactStore)[0].(map[string]interface{}) - store["region"] = region - values = append(values, store) - } - return values -} - -func flattenStages(stages []*codepipeline.StageDeclaration, d *schema.ResourceData) []interface{} { - stagesList := []interface{}{} - for si, stage := range stages { - values := map[string]interface{}{} - values["name"] = aws.StringValue(stage.Name) - values["action"] = flattenStageActions(si, stage.Actions, d) - stagesList = append(stagesList, values) - } - return stagesList -} - -func flattenStageActions(si int, actions []*codepipeline.ActionDeclaration, d *schema.ResourceData) []interface{} { - actionsList := []interface{}{} - for ai, action := range actions { - values := map[string]interface{}{ - "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 := aws.StringValueMap(action.Configuration) - - actionProvider := aws.StringValue(action.ActionTypeId.Provider) - if actionProvider == providerGitHub { - if _, ok := config[gitHubActionConfigurationOAuthToken]; 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) - config[gitHubActionConfigurationOAuthToken] = d.Get(addr).(string) - } - } - - values["configuration"] = config - } - - if len(action.OutputArtifacts) > 0 { - values["output_artifacts"] = flattenActionsOutputArtifacts(action.OutputArtifacts) - } - - if len(action.InputArtifacts) > 0 { - values["input_artifacts"] = flattenActionsInputArtifacts(action.InputArtifacts) - } - - if action.RoleArn != nil { - values["role_arn"] = aws.StringValue(action.RoleArn) - } - - if action.RunOrder != nil { - values["run_order"] = int(aws.Int64Value(action.RunOrder)) - } - - if action.Region != nil { - values["region"] = aws.StringValue(action.Region) - } - - if action.Namespace != nil { - values["namespace"] = aws.StringValue(action.Namespace) - } - - actionsList = append(actionsList, values) - } - return actionsList -} - -func flattenActionsOutputArtifacts(artifacts []*codepipeline.OutputArtifact) []string { - values := []string{} - for _, artifact := range artifacts { - values = append(values, aws.StringValue(artifact.Name)) - } - return values -} - -func flattenActionsInputArtifacts(artifacts []*codepipeline.InputArtifact) []string { - values := []string{} - for _, artifact := range artifacts { - values = append(values, aws.StringValue(artifact.Name)) - } - return values -} - -func resourceValidateActionProvider(i interface{}, path cty.Path) diag.Diagnostics { +func pipelineValidateActionProvider(i interface{}, path cty.Path) diag.Diagnostics { v, ok := i.(string) if !ok { return diag.Errorf("expected type to be string") @@ -504,7 +395,7 @@ func resourceValidateActionProvider(i interface{}, path cty.Path) diag.Diagnosti return nil } -func suppressStageActionConfiguration(k, old, new string, d *schema.ResourceData) bool { +func pipelineSuppressStageActionConfigurationDiff(k, old, new string, d *schema.ResourceData) bool { parts := strings.Split(k, ".") parts = parts[:len(parts)-2] providerAddr := strings.Join(append(parts, "provider"), ".") @@ -518,13 +409,14 @@ func suppressStageActionConfiguration(k, old, new string, d *schema.ResourceData return false } -const gitHubTokenHashPrefix = "hash-" - func hashGitHubToken(token string) string { + const gitHubTokenHashPrefix = "hash-" + // Without this check, the value was getting encoded twice if strings.HasPrefix(token, gitHubTokenHashPrefix) { return token } + sum := sha256.Sum256([]byte(token)) return gitHubTokenHashPrefix + hex.EncodeToString(sum[:]) } @@ -821,3 +713,226 @@ func expandOutputArtifacts(tfList []interface{}) []*codepipeline.OutputArtifact return apiObjects } + +func flattenArtifactStore(apiObject *codepipeline.ArtifactStore) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.EncryptionKey; v != nil { + tfMap["encryption_key"] = []interface{}{flattenEncryptionKey(v)} + } + + if v := apiObject.Location; v != nil { + tfMap["location"] = aws.StringValue(v) + } + + if v := apiObject.Type; v != nil { + tfMap["type"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenArtifactStores(apiObjects map[string]*codepipeline.ArtifactStore) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for region, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfMap := flattenArtifactStore(apiObject) + tfMap["region"] = region + + tfList = append(tfList, tfMap) + } + + return tfList +} + +func flattenEncryptionKey(apiObject *codepipeline.EncryptionKey) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Id; v != nil { + tfMap["id"] = aws.StringValue(v) + } + + if v := apiObject.Type; v != nil { + tfMap["type"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenStageDeclaration(d *schema.ResourceData, i int, apiObject *codepipeline.StageDeclaration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Actions; v != nil { + tfMap["action"] = flattenActionDeclarations(d, i, v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenStageDeclarations(d *schema.ResourceData, apiObjects []*codepipeline.StageDeclaration) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for i, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenStageDeclaration(d, i, apiObject)) + } + + return tfList +} + +func flattenActionDeclaration(d *schema.ResourceData, i, j int, apiObject *codepipeline.ActionDeclaration) map[string]interface{} { + if apiObject == nil { + return nil + } + + var actionProvider string + tfMap := map[string]interface{}{} + + if apiObject := apiObject.ActionTypeId; apiObject != nil { + if v := apiObject.Category; v != nil { + tfMap["category"] = aws.StringValue(v) + } + + if v := apiObject.Owner; v != nil { + tfMap["owner"] = aws.StringValue(v) + } + + if v := apiObject.Provider; v != nil { + actionProvider = aws.StringValue(v) + tfMap["provider"] = actionProvider + } + + if v := apiObject.Version; v != nil { + tfMap["version"] = aws.StringValue(v) + } + } + + if v := apiObject.Configuration; v != nil { + v := aws.StringValueMap(v) + + // The AWS API returns "****" for the OAuthToken value. Copy the value from the configuration. + if actionProvider == providerGitHub { + if _, ok := v[gitHubActionConfigurationOAuthToken]; ok { + key := fmt.Sprintf("stage.%d.action.%d.configuration.OAuthToken", i, j) + v[gitHubActionConfigurationOAuthToken] = d.Get(key).(string) + } + } + + tfMap["configuration"] = v + } + + if v := apiObject.InputArtifacts; len(v) > 0 { + tfMap["input_artifacts"] = flattenInputArtifacts(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.Namespace; v != nil { + tfMap["namespace"] = aws.StringValue(v) + } + + if v := apiObject.OutputArtifacts; len(v) > 0 { + tfMap["output_artifacts"] = flattenOutputArtifacts(v) + } + + if v := apiObject.Region; v != nil { + tfMap["region"] = aws.StringValue(v) + } + + if v := apiObject.RoleArn; v != nil { + tfMap["role_arn"] = aws.StringValue(v) + } + + if v := apiObject.RunOrder; v != nil { + tfMap["run_order"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenActionDeclarations(d *schema.ResourceData, i int, apiObjects []*codepipeline.ActionDeclaration) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for j, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenActionDeclaration(d, i, j, apiObject)) + } + + return tfList +} + +func flattenInputArtifacts(apiObjects []*codepipeline.InputArtifact) []string { + if len(apiObjects) == 0 { + return nil + } + + var tfList []*string + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, apiObject.Name) + } + + return aws.StringValueSlice(tfList) +} + +func flattenOutputArtifacts(apiObjects []*codepipeline.OutputArtifact) []string { + if len(apiObjects) == 0 { + return nil + } + + var tfList []*string + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, apiObject.Name) + } + + return aws.StringValueSlice(tfList) +} From c441033c5620bb5ad96c8bf6ff51bf8e34534861 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 12:24:59 -0400 Subject: [PATCH 10/23] Add 'TestAccCodePipeline_ecr'. --- .../service/codepipeline/codepipeline_test.go | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index 67946b15cedb..2c15fb4d126f 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -550,6 +550,54 @@ func TestAccCodePipeline_withGitHubV1SourceAction(t *testing.T) { }) } +func TestAccCodePipeline_ecr(t *testing.T) { + var p codepipeline.PipelineDeclaration + name := sdkacctest.RandString(10) + resourceName := "aws_codepipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + testAccPreCheckSupported(t) + acctest.PreCheckPartitionHasService(codestarconnections.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCodePipelineConfig_ecr(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckExists(resourceName, &p), + resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.name", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.category", "Source"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.owner", "AWS"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.provider", "ECR"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.version", "1"), + 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.%", "2"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.RepositoryName", "my-image-repo"), + resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.configuration.ImageTag", "latest"), + 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", ""), + resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckExists(n string, v *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1553,3 +1601,60 @@ resource "aws_codepipeline" "test" { } `, rName, githubToken)) } + +func testAccCodePipelineConfig_ecr(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 = "ECR" + version = "1" + output_artifacts = ["test"] + + configuration = { + RepositoryName = "my-image-repo" + ImageTag = "latest" + } + } + } + + stage { + name = "Build" + + action { + name = "Build" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + input_artifacts = ["test"] + version = "1" + + configuration = { + ProjectName = "test" + } + } + } +} +`, rName)) +} From 0c5d06a66a53436bb99fc9f7b37f10f18d51e138 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 12:26:43 -0400 Subject: [PATCH 11/23] Revert "fix format, modify acctest" This reverts commit d0bcda3ec78318f2d4d94471d3b7258731852ce1. --- ...rce_aws_codepipeline_custom_action_type.go | 5 +-- ...ws_codepipeline_custom_action_type_test.go | 34 ++++++++++--------- ...epipeline_custom_action_type.html.markdown | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/aws/resource_aws_codepipeline_custom_action_type.go b/aws/resource_aws_codepipeline_custom_action_type.go index c89a0089625b..b75357e54180 100644 --- a/aws/resource_aws_codepipeline_custom_action_type.go +++ b/aws/resource_aws_codepipeline_custom_action_type.go @@ -376,10 +376,7 @@ func flattenAwsCodePipelineActionConfigurationProperty(acps []*codepipeline.Acti m["queryable"] = aws.BoolValue(acp.Queryable) m["required"] = aws.BoolValue(acp.Required) m["secret"] = aws.BoolValue(acp.Secret) - // Currently AWS doesn't return type - if acp.Type != nil { - m["type"] = aws.StringValue(acp.Type) - } + m["type"] = aws.StringValue(acp.Type) result = append(result, m) } return result diff --git a/aws/resource_aws_codepipeline_custom_action_type_test.go b/aws/resource_aws_codepipeline_custom_action_type_test.go index 63d680a447b4..5a135af865f5 100644 --- a/aws/resource_aws_codepipeline_custom_action_type_test.go +++ b/aws/resource_aws_codepipeline_custom_action_type_test.go @@ -90,6 +90,7 @@ func TestAccAwsCodePipelineCustomActionType_configurationProperties(t *testing.T resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.queryable", "true"), resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.required", "true"), resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.secret", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.type", "String"), ), }, { @@ -159,7 +160,7 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" } `, rName) @@ -177,13 +178,13 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" - settings { - entity_url_template = "http://example.com" - execution_url_template = "http://example.com" - revision_url_template = "http://example.com" - } + settings { + entity_url_template = "http://example.com" + execution_url_template = "http://example.com" + revision_url_template = "http://example.com" + } } `, rName) } @@ -200,16 +201,17 @@ resource "aws_codepipeline_custom_action_type" "test" { maximum_count = 1 minimum_count = 0 } - provider_name = "tf-%s" + provider_name = "tf-%s" version = "1" - configuration_properties { - description = "tf-test" - key = true - name = "tf-test-%s" - queryable = true - required = true - secret = false - } + configuration_properties { + description = "tf-test" + key = true + name = "tf-test-%s" + queryable = true + required = true + secret = false + type = "String" + } } `, rName, rName) } diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index 386203615dae..9e0454c5f70d 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -23,7 +23,7 @@ resource "aws_codepipeline_custom_action_type" "example" { maximum_count = 1 minimum_count = 0 } - provider_name = "example" + provider_name = "example" version = "1" } ``` From ec20037310c268a9321ba564426c1ca41f6978c1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 12:26:53 -0400 Subject: [PATCH 12/23] Revert "New Resource: aws_codepipeline_custom_action_type" This reverts commit b756b54bb2db83b1f417e9abea23374a2bd668c8. --- aws/provider.go | 1 - ...rce_aws_codepipeline_custom_action_type.go | 427 ------------------ ...ws_codepipeline_custom_action_type_test.go | 217 --------- website/aws.erb | 3 - ...epipeline_custom_action_type.html.markdown | 87 ---- 5 files changed, 735 deletions(-) delete mode 100644 aws/resource_aws_codepipeline_custom_action_type.go delete mode 100644 aws/resource_aws_codepipeline_custom_action_type_test.go delete mode 100644 website/docs/r/codepipeline_custom_action_type.html.markdown diff --git a/aws/provider.go b/aws/provider.go index eb6913d59731..99d3ff2c48a7 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -373,7 +373,6 @@ func Provider() terraform.ResourceProvider { "aws_codebuild_project": resourceAwsCodeBuildProject(), "aws_codebuild_webhook": resourceAwsCodeBuildWebhook(), "aws_codepipeline": resourceAwsCodePipeline(), - "aws_codepipeline_custom_action_type": resourceAwsCodePipelineCustomActionType(), "aws_codepipeline_webhook": resourceAwsCodePipelineWebhook(), "aws_cur_report_definition": resourceAwsCurReportDefinition(), "aws_customer_gateway": resourceAwsCustomerGateway(), diff --git a/aws/resource_aws_codepipeline_custom_action_type.go b/aws/resource_aws_codepipeline_custom_action_type.go deleted file mode 100644 index b75357e54180..000000000000 --- a/aws/resource_aws_codepipeline_custom_action_type.go +++ /dev/null @@ -1,427 +0,0 @@ -package aws - -import ( - "errors" - "fmt" - "log" - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/codepipeline" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" -) - -func resourceAwsCodePipelineCustomActionType() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsCodePipelineCustomActionTypeCreate, - Read: resourceAwsCodePipelineCustomActionTypeRead, - Delete: resourceAwsCodePipelineCustomActionTypeDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "category": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.ActionCategorySource, - codepipeline.ActionCategoryBuild, - codepipeline.ActionCategoryDeploy, - codepipeline.ActionCategoryTest, - codepipeline.ActionCategoryInvoke, - codepipeline.ActionCategoryApproval, - }, false), - }, - "configuration_properties": { - Type: schema.TypeList, - MaxItems: 10, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "description": { - Type: schema.TypeString, - Optional: true, - }, - "key": { - Type: schema.TypeBool, - Required: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - }, - "queryable": { - Type: schema.TypeBool, - Optional: true, - }, - "required": { - Type: schema.TypeBool, - Required: true, - }, - "secret": { - Type: schema.TypeBool, - Required: true, - }, - "type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - codepipeline.ActionConfigurationPropertyTypeString, - codepipeline.ActionConfigurationPropertyTypeNumber, - codepipeline.ActionConfigurationPropertyTypeBoolean, - }, false), - }, - }, - }, - }, - "input_artifact_details": { - Type: schema.TypeList, - ForceNew: true, - Required: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum_count": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(0, 5), - }, - "minimum_count": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(0, 5), - }, - }, - }, - }, - "output_artifact_details": { - Type: schema.TypeList, - ForceNew: true, - Required: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum_count": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(0, 5), - }, - "minimum_count": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(0, 5), - }, - }, - }, - }, - "provider_name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringLenBetween(1, 25), - }, - "settings": { - Type: schema.TypeList, - ForceNew: true, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "entity_url_template": { - Type: schema.TypeString, - Optional: true, - }, - "execution_url_template": { - Type: schema.TypeString, - Optional: true, - }, - "revision_url_template": { - Type: schema.TypeString, - Optional: true, - }, - "third_party_configuration_url": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "version": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringLenBetween(1, 9), - }, - "owner": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func resourceAwsCodePipelineCustomActionTypeCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).codepipelineconn - - input := &codepipeline.CreateCustomActionTypeInput{ - Category: aws.String(d.Get("category").(string)), - InputArtifactDetails: expandAwsCodePipelineArtifactDetails(d.Get("input_artifact_details").([]interface{})[0].(map[string]interface{})), - OutputArtifactDetails: expandAwsCodePipelineArtifactDetails(d.Get("output_artifact_details").([]interface{})[0].(map[string]interface{})), - Provider: aws.String(d.Get("provider_name").(string)), - Version: aws.String(d.Get("version").(string)), - } - - confProps := d.Get("configuration_properties").([]interface{}) - if len(confProps) > 0 { - input.ConfigurationProperties = expandAwsCodePipelineActionConfigurationProperty(confProps) - } - - settings := d.Get("settings").([]interface{}) - if len(settings) > 0 { - input.Settings = expandAwsCodePipelineActionTypeSettings(settings[0].(map[string]interface{})) - } - - resp, err := conn.CreateCustomActionType(input) - if err != nil { - return fmt.Errorf("Error creating CodePipeline CustomActionType: %s", err) - } - - if resp.ActionType == nil || resp.ActionType.Id == nil || - resp.ActionType.Id.Owner == nil || resp.ActionType.Id.Category == nil || resp.ActionType.Id.Provider == nil || resp.ActionType.Id.Version == nil { - return errors.New("Error creating CodePipeline CustomActionType: invalid response from AWS") - } - d.SetId(fmt.Sprintf("%s:%s:%s:%s", *resp.ActionType.Id.Owner, *resp.ActionType.Id.Category, *resp.ActionType.Id.Provider, *resp.ActionType.Id.Version)) - return resourceAwsCodePipelineCustomActionTypeRead(d, meta) -} - -func resourceAwsCodePipelineCustomActionTypeRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).codepipelineconn - owner, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(d.Id()) - if err != nil { - return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) - } - - actionType, err := lookAwsCodePipelineCustomActionType(conn, d.Id()) - if err != nil { - return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) - } - if actionType == nil { - log.Printf("[INFO] Codepipeline CustomActionType %q not found", d.Id()) - d.SetId("") - return nil - } - - d.Set("owner", owner) - d.Set("category", category) - d.Set("provider_name", provider) - d.Set("version", version) - - if err := d.Set("configuration_properties", flattenAwsCodePipelineActionConfigurationProperty(actionType.ActionConfigurationProperties)); err != nil { - return fmt.Errorf("error setting configuration_properties: %s", err) - } - - if err := d.Set("input_artifact_details", flattenAwsCodePipelineArtifactDetails(actionType.InputArtifactDetails)); err != nil { - return fmt.Errorf("error setting input_artifact_details: %s", err) - } - - if err := d.Set("output_artifact_details", flattenAwsCodePipelineArtifactDetails(actionType.OutputArtifactDetails)); err != nil { - return fmt.Errorf("error setting output_artifact_details: %s", err) - } - - if err := d.Set("settings", flattenAwsCodePipelineActionTypeSettings(actionType.Settings)); err != nil { - return fmt.Errorf("error setting settings: %s", err) - } - - return nil -} - -func resourceAwsCodePipelineCustomActionTypeDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).codepipelineconn - - _, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(d.Id()) - if err != nil { - return fmt.Errorf("Error deleting CodePipeline CustomActionType: %s", err) - } - - input := &codepipeline.DeleteCustomActionTypeInput{ - Category: aws.String(category), - Provider: aws.String(provider), - Version: aws.String(version), - } - - _, err = conn.DeleteCustomActionType(input) - if err != nil { - if isAWSErr(err, codepipeline.ErrCodeActionTypeNotFoundException, "") { - return nil - } - return fmt.Errorf("error deleting CodePipeline CustomActionType (%s): %s", d.Id(), err) - } - - return nil -} - -func lookAwsCodePipelineCustomActionType(conn *codepipeline.CodePipeline, id string) (*codepipeline.ActionType, error) { - owner, category, provider, version, err := decodeAwsCodePipelineCustomActionTypeId(id) - if err != nil { - return nil, fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) - } - - var actionType *codepipeline.ActionType - - input := &codepipeline.ListActionTypesInput{ - ActionOwnerFilter: aws.String(owner), - } - for { - resp, err := conn.ListActionTypes(input) - if err != nil { - return nil, fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) - } - - for _, v := range resp.ActionTypes { - if atid := v.Id; atid != nil { - if atid.Category == nil || atid.Provider == nil || atid.Version == nil { - continue - } - if *atid.Category == category && *atid.Provider == provider && *atid.Version == version { - actionType = v - break - } - } - } - - if actionType != nil { - break - } - - if resp.NextToken == nil { - break - } else { - input.NextToken = resp.NextToken - } - } - - return actionType, nil -} - -func decodeAwsCodePipelineCustomActionTypeId(id string) (owner string, category string, provider string, version string, e error) { - ss := strings.Split(id, ":") - if len(ss) != 4 { - e = fmt.Errorf("invalid AwsCodePipelineCustomActionType ID: %s", id) - return - } - owner, category, provider, version = ss[0], ss[1], ss[2], ss[3] - return -} - -func expandAwsCodePipelineArtifactDetails(d map[string]interface{}) *codepipeline.ArtifactDetails { - return &codepipeline.ArtifactDetails{ - MaximumCount: aws.Int64(int64(d["maximum_count"].(int))), - MinimumCount: aws.Int64(int64(d["minimum_count"].(int))), - } -} - -func flattenAwsCodePipelineArtifactDetails(ad *codepipeline.ArtifactDetails) []map[string]interface{} { - m := make(map[string]interface{}) - - m["maximum_count"] = aws.Int64Value(ad.MaximumCount) - m["minimum_count"] = aws.Int64Value(ad.MinimumCount) - - return []map[string]interface{}{m} -} - -func expandAwsCodePipelineActionConfigurationProperty(d []interface{}) []*codepipeline.ActionConfigurationProperty { - if len(d) == 0 { - return nil - } - result := make([]*codepipeline.ActionConfigurationProperty, 0, len(d)) - - for _, v := range d { - m := v.(map[string]interface{}) - acp := &codepipeline.ActionConfigurationProperty{ - Key: aws.Bool(m["key"].(bool)), - Name: aws.String(m["name"].(string)), - Required: aws.Bool(m["required"].(bool)), - Secret: aws.Bool(m["secret"].(bool)), - } - if raw, ok := m["description"]; ok && raw.(string) != "" { - acp.Description = aws.String(raw.(string)) - } - if raw, ok := m["queryable"]; ok { - acp.Queryable = aws.Bool(raw.(bool)) - } - if raw, ok := m["type"]; ok && raw.(string) != "" { - acp.Type = aws.String(raw.(string)) - } - result = append(result, acp) - } - - return result -} - -func flattenAwsCodePipelineActionConfigurationProperty(acps []*codepipeline.ActionConfigurationProperty) []interface{} { - result := make([]interface{}, 0, len(acps)) - - for _, acp := range acps { - m := map[string]interface{}{} - m["description"] = aws.StringValue(acp.Description) - m["key"] = aws.BoolValue(acp.Key) - m["name"] = aws.StringValue(acp.Name) - m["queryable"] = aws.BoolValue(acp.Queryable) - m["required"] = aws.BoolValue(acp.Required) - m["secret"] = aws.BoolValue(acp.Secret) - m["type"] = aws.StringValue(acp.Type) - result = append(result, m) - } - return result -} - -func expandAwsCodePipelineActionTypeSettings(d map[string]interface{}) *codepipeline.ActionTypeSettings { - if len(d) == 0 { - return nil - } - result := &codepipeline.ActionTypeSettings{} - - if raw, ok := d["entity_url_template"]; ok && raw.(string) != "" { - result.EntityUrlTemplate = aws.String(raw.(string)) - } - if raw, ok := d["execution_url_template"]; ok && raw.(string) != "" { - result.ExecutionUrlTemplate = aws.String(raw.(string)) - } - if raw, ok := d["revision_url_template"]; ok && raw.(string) != "" { - result.RevisionUrlTemplate = aws.String(raw.(string)) - } - if raw, ok := d["third_party_configuration_url"]; ok && raw.(string) != "" { - result.ThirdPartyConfigurationUrl = aws.String(raw.(string)) - } - - return result -} - -func flattenAwsCodePipelineActionTypeSettings(settings *codepipeline.ActionTypeSettings) []map[string]interface{} { - m := make(map[string]interface{}) - - if settings.EntityUrlTemplate != nil { - m["entity_url_template"] = aws.StringValue(settings.EntityUrlTemplate) - } - if settings.ExecutionUrlTemplate != nil { - m["execution_url_template"] = aws.StringValue(settings.ExecutionUrlTemplate) - } - if settings.RevisionUrlTemplate != nil { - m["revision_url_template"] = aws.StringValue(settings.RevisionUrlTemplate) - } - if settings.ThirdPartyConfigurationUrl != nil { - m["third_party_configuration_url"] = aws.StringValue(settings.ThirdPartyConfigurationUrl) - } - - if len(m) == 0 { - return nil - } - return []map[string]interface{}{m} -} diff --git a/aws/resource_aws_codepipeline_custom_action_type_test.go b/aws/resource_aws_codepipeline_custom_action_type_test.go deleted file mode 100644 index 5a135af865f5..000000000000 --- a/aws/resource_aws_codepipeline_custom_action_type_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccAwsCodePipelineCustomActionType_basic(t *testing.T) { - resourceName := "aws_codepipeline_custom_action_type.test" - rName := acctest.RandString(5) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAwsCodePipelineCustomActionType_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "category", "Build"), - resource.TestCheckResourceAttr(resourceName, "input_artifact_details.#", "1"), - resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.maximum_count", "1"), - resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "0"), - resource.TestCheckResourceAttr(resourceName, "output_artifact_details.#", "1"), - resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.maximum_count", "1"), - resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "0"), - resource.TestCheckResourceAttr(resourceName, "provider_name", fmt.Sprintf("tf-%s", rName)), - resource.TestCheckResourceAttr(resourceName, "version", "1"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccAwsCodePipelineCustomActionType_settings(t *testing.T) { - resourceName := "aws_codepipeline_custom_action_type.test" - rName := acctest.RandString(5) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAwsCodePipelineCustomActionType_settings(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "settings.0.entity_url_template", "http://example.com"), - resource.TestCheckResourceAttr(resourceName, "settings.0.execution_url_template", "http://example.com"), - resource.TestCheckResourceAttr(resourceName, "settings.0.revision_url_template", "http://example.com"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccAwsCodePipelineCustomActionType_configurationProperties(t *testing.T) { - resourceName := "aws_codepipeline_custom_action_type.test" - rName := acctest.RandString(5) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsCodePipelineCustomActionTypeDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAwsCodePipelineCustomActionType_configurationProperties(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCodePipelineCustomActionTypeExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.#", "1"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.description", "tf-test"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.key", "true"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.name", fmt.Sprintf("tf-test-%s", rName)), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.queryable", "true"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.required", "true"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.secret", "false"), - resource.TestCheckResourceAttr(resourceName, "configuration_properties.0.type", "String"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccCheckAwsCodePipelineCustomActionTypeExists(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No CodePipeline CustomActionType is set as ID") - } - - conn := testAccProvider.Meta().(*AWSClient).codepipelineconn - - actionType, err := lookAwsCodePipelineCustomActionType(conn, rs.Primary.ID) - if err != nil { - return err - } - if actionType == nil { - return fmt.Errorf("Not found CodePipeline CustomActionType: %s", rs.Primary.ID) - } - return nil - } -} - -func testAccCheckAwsCodePipelineCustomActionTypeDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).codepipelineconn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_codepipeline_custom_action_type" { - continue - } - - actionType, err := lookAwsCodePipelineCustomActionType(conn, rs.Primary.ID) - if err != nil { - return fmt.Errorf("Error reading CodePipeline CustomActionType: %s", err) - } - if actionType != nil { - return fmt.Errorf("CodePipeline CustomActionType still exists: %s", rs.Primary.ID) - } - - return err - } - - return nil -} - -func testAccAwsCodePipelineCustomActionType_basic(rName string) string { - return fmt.Sprintf(` -resource "aws_codepipeline_custom_action_type" "test" { - category = "Build" - input_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - output_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - provider_name = "tf-%s" - version = "1" -} -`, rName) -} - -func testAccAwsCodePipelineCustomActionType_settings(rName string) string { - return fmt.Sprintf(` -resource "aws_codepipeline_custom_action_type" "test" { - category = "Build" - input_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - output_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - provider_name = "tf-%s" - version = "1" - settings { - entity_url_template = "http://example.com" - execution_url_template = "http://example.com" - revision_url_template = "http://example.com" - } -} -`, rName) -} - -func testAccAwsCodePipelineCustomActionType_configurationProperties(rName string) string { - return fmt.Sprintf(` -resource "aws_codepipeline_custom_action_type" "test" { - category = "Build" - input_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - output_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - provider_name = "tf-%s" - version = "1" - configuration_properties { - description = "tf-test" - key = true - name = "tf-test-%s" - queryable = true - required = true - secret = false - type = "String" - } -} -`, rName, rName) -} diff --git a/website/aws.erb b/website/aws.erb index 2d489216e14e..850275707776 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -781,9 +781,6 @@ aws_codepipeline - > - aws_codepipeline_custom_action_type - > aws_codepipeline_webhook diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown deleted file mode 100644 index 9e0454c5f70d..000000000000 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ /dev/null @@ -1,87 +0,0 @@ ---- -layout: "aws" -page_title: "AWS: aws_codepipeline_custom_action_type" -sidebar_current: "docs-aws-resource-codepipeline-webhook" -description: |- - Provides a CodePipeline CustomActionType. ---- - -# aws_codepipeline_custom_action_type - -Provides a CodeDeploy CustomActionType - -## Example Usage - -```hcl -resource "aws_codepipeline_custom_action_type" "example" { - category = "Build" - input_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - output_artifact_details { - maximum_count = 1 - minimum_count = 0 - } - provider_name = "example" - version = "1" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `category` - (Required) The category of the custom action. Valid values: `Source`, `Build`, `Deploy`, `Test`, `Invoke`, `Approval` -* `configuration_properties` - (Optional) The configuration properties for the custom action. Max 10 items. - -The `configuration_properties` object supports the following: - -* `description` - (Optional) The description of the action configuration property. -* `key` - (Required) Whether the configuration property is a key. -* `name` - (Required) The name of the action configuration property. -* `queryable` - (Optional) Indicates that the property will be used in conjunction with PollForJobs. -* `required` - (Required) Whether the configuration property is a required value. -* `secret`- (Required) Whether the configuration property is secret. -* `type`- (Optional) The type of the configuration property. Valid values: `String`, `Number`, `Boolean` - -* `input_artifact_details` - (Required) The details of the input artifact for the action. - -The `input_artifact_details` object supports the following: - -* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 -* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 - -* `output_artifact_details` - (Required) The details of the output artifact of the action. - -The `output_artifact_details` object supports the following: - -* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 -* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 - -* `provider_name` - (Required) The provider of the service used in the custom action -* `settings` - (Optional) The settings for an action type. - -The `settings` object supports the following: - -* `entity_url_template` - (Optional) The URL returned to the AWS CodePipeline console that provides a deep link to the resources of the external system. -* `execution_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the top-level landing page for the external system. -* `revision_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the page where customers can update or change the configuration of the external action. -* `third_party_configuration_url` - (Optional) The URL of a sign-up page where users can sign up for an external service and perform initial configuration of the action provided by that service. - -* `version` - (Required) The version identifier of the custom action. - -## Attribute Reference - -The following arguments are exported: - -* `id` - Composed of owner, category, provider and version. For example, `Custom:Build:terraform:1` -* `owner` - The creator of the action being called. - -## Import - -CodeDeploy CustomActionType can be imported using the `id`, e.g. - -``` -$ terraform import aws_codepipeline_custom_action_type.example Custom:Build:terraform:1 -``` From 1652976fa96c5c3f5923f592ab170a26b80a414e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 14:09:09 -0400 Subject: [PATCH 13/23] r/aws_codepipeline_custom_action_type: New resource. --- .changelog/8123.txt | 3 + internal/provider/provider.go | 5 +- internal/service/codepipeline/codepipeline.go | 2 +- .../codepipeline/custom_action_type.go | 375 ++++++++++++++++++ ...epipeline_custom_action_type.html.markdown | 91 +++++ 5 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 .changelog/8123.txt create mode 100644 internal/service/codepipeline/custom_action_type.go create mode 100644 website/docs/r/codepipeline_custom_action_type.html.markdown diff --git a/.changelog/8123.txt b/.changelog/8123.txt new file mode 100644 index 000000000000..187897cf666d --- /dev/null +++ b/.changelog/8123.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_codepipeline_custom_action_type +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 09e020303d80..739f41aae231 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1172,8 +1172,9 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_codedeploy_deployment_config": deploy.ResourceDeploymentConfig(), "aws_codedeploy_deployment_group": deploy.ResourceDeploymentGroup(), - "aws_codepipeline": codepipeline.ResourcePipeline(), - "aws_codepipeline_webhook": codepipeline.ResourceWebhook(), + "aws_codepipeline": codepipeline.ResourcePipeline(), + "aws_codepipeline_custom_action_type": codepipeline.ResourceCustomActionType(), + "aws_codepipeline_webhook": codepipeline.ResourceWebhook(), "aws_codestarconnections_connection": codestarconnections.ResourceConnection(), "aws_codestarconnections_host": codestarconnections.ResourceHost(), diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index 3f7833d0cf11..1298201a2617 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -320,9 +320,9 @@ func resourcePipelineUpdate(ctx context.Context, d *schema.ResourceData, meta in } } - arn := d.Get("arn").(string) if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") + arn := d.Get("arn").(string) if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { return diag.Errorf("updating CodePipeline (%s) tags: %s", arn, err) diff --git a/internal/service/codepipeline/custom_action_type.go b/internal/service/codepipeline/custom_action_type.go new file mode 100644 index 000000000000..8efbe79864b2 --- /dev/null +++ b/internal/service/codepipeline/custom_action_type.go @@ -0,0 +1,375 @@ +package codepipeline + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceCustomActionType() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomActionTypeCreate, + ReadWithoutTimeout: resourceCustomActionTypeRead, + UpdateWithoutTimeout: resourceCustomActionTypeUpdate, + DeleteWithoutTimeout: resourceCustomActionTypeDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "category": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice(codepipeline.ActionCategory_Values(), false), + }, + "configuration_property": { + Type: schema.TypeList, + MaxItems: 10, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "key": { + Type: schema.TypeBool, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "queryable": { + Type: schema.TypeBool, + Optional: true, + }, + "required": { + Type: schema.TypeBool, + Required: true, + }, + "secret": { + Type: schema.TypeBool, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(codepipeline.ActionConfigurationPropertyType_Values(), false), + }, + }, + }, + }, + "input_artifact_details": { + Type: schema.TypeList, + ForceNew: true, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "maximum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + "minimum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + }, + }, + }, + "output_artifact_details": { + Type: schema.TypeList, + ForceNew: true, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "maximum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + "minimum_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 5), + }, + }, + }, + }, + "owner": { + Type: schema.TypeString, + Computed: true, + }, + "provider": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 25), + }, + "settings": { + Type: schema.TypeList, + ForceNew: true, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "entity_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "execution_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "revision_url_template": { + Type: schema.TypeString, + Optional: true, + }, + "third_party_configuration_url": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "version": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 9), + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceCustomActionTypeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + category := d.Get("category").(string) + provider := d.Get("provider").(string) + version := d.Get("version").(string) + id := CustomActionTypeCreateResourceID(category, provider, version) + input := &codepipeline.CreateCustomActionTypeInput{ + Category: aws.String(category), + Provider: aws.String(provider), + Version: aws.String(version), + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + _, err := conn.CreateCustomActionTypeWithContext(ctx, input) + + if err != nil { + return diag.Errorf("creating CodePipeline Custom Action Type (%s): %s", id, err) + } + + d.SetId(id) + + return resourceCustomActionTypeRead(ctx, d, meta) +} + +func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + category, provider, version, err := CustomActionTypeParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + actionType, err := FindCustomActionTypeByThreePartKey(ctx, conn, category, provider, version) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] CodePipeline Custom Action Type %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading CodePipeline Custom Action Type (%s): %s", d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: codepipeline.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("actiontype:%s:%s:%s:%s", codepipeline.ActionOwnerCustom, category, provider, version), + }.String() + d.Set("arn", arn) + d.Set("category", actionType.Id.Category) + d.Set("owner", actionType.Id.Owner) + d.Set("provider_name", actionType.Id.Provider) + d.Set("version", actionType.Id.Version) + + tags, err := ListTagsWithContext(ctx, conn, arn) + + if err != nil { + return diag.Errorf("listing tags for CodePipeline Custom Action Type (%s): %s", arn, err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("setting tags_all: %s", err) + } + + return nil +} + +func resourceCustomActionTypeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + + if d.HasChangesExcept("tags", "tags_all") { + category, provider, version, err := CustomActionTypeParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + input := &codepipeline.UpdateActionTypeInput{ + ActionType: &codepipeline.ActionTypeDeclaration{ + Id: &codepipeline.ActionTypeIdentifier{ + Category: aws.String(category), + Owner: aws.String(codepipeline.ActionOwnerCustom), + Provider: aws.String(provider), + Version: aws.String(version), + }, + }, + } + + _, err = conn.UpdateActionTypeWithContext(ctx, input) + + if err != nil { + return diag.Errorf("updating CodePipeline Custom Action Type (%s): %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + arn := d.Get("arn").(string) + + if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { + return diag.Errorf("updating CodePipeline Custom Action Type (%s) tags: %s", arn, err) + } + } + + return resourceCustomActionTypeRead(ctx, d, meta) +} + +func resourceCustomActionTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + + category, provider, version, err := CustomActionTypeParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[INFO] Deleting CodePipeline Custom Action Type: %s", d.Id()) + _, err = conn.DeleteCustomActionTypeWithContext(ctx, &codepipeline.DeleteCustomActionTypeInput{ + Category: aws.String(category), + Provider: aws.String(provider), + Version: aws.String(version), + }) + + if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodeActionTypeNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("deleting CodePipeline (%s): %s", d.Id(), err) + } + + return nil +} + +const customActionTypeResourceIDSeparator = ":" + +func CustomActionTypeCreateResourceID(category, provider, version string) string { + parts := []string{category, provider, version} + id := strings.Join(parts, customActionTypeResourceIDSeparator) + + return id +} + +func CustomActionTypeParseResourceID(id string) (string, string, string, error) { + parts := strings.Split(id, customActionTypeResourceIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected category%[2]sprovider%[2]sversion", id, customActionTypeResourceIDSeparator) +} + +func FindCustomActionTypeByThreePartKey(ctx context.Context, conn *codepipeline.CodePipeline, category, provider, version string) (*codepipeline.ActionTypeDeclaration, error) { + input := &codepipeline.GetActionTypeInput{ + Category: aws.String(category), + Owner: aws.String(codepipeline.ActionOwnerCustom), + Provider: aws.String(provider), + Version: aws.String(version), + } + + output, err := conn.GetActionTypeWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodeActionTypeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.ActionType == nil || output.ActionType.Id == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ActionType, nil +} diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown new file mode 100644 index 000000000000..bfd55f7fe269 --- /dev/null +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -0,0 +1,91 @@ +--- +subcategory: "CodePipeline" +layout: "aws" +page_title: "AWS: aws_codepipeline_custom_action_type" +description: |- + Provides a CodePipeline CustomActionType. +--- + +# aws_codepipeline_custom_action_type + +Provides a CodeDeploy CustomActionType + +## Example Usage + +```hcl +resource "aws_codepipeline_custom_action_type" "example" { + category = "Build" + + input_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + + output_artifact_details { + maximum_count = 1 + minimum_count = 0 + } + + provider = "example" + version = "1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `category` - (Required) The category of the custom action. Valid values: `Source`, `Build`, `Deploy`, `Test`, `Invoke`, `Approval` +* `configuration_property` - (Optional) The configuration properties for the custom action. Max 10 items. + +The `configuration_property` object supports the following: + +* `description` - (Optional) The description of the action configuration property. +* `key` - (Required) Whether the configuration property is a key. +* `name` - (Required) The name of the action configuration property. +* `queryable` - (Optional) Indicates that the property will be used in conjunction with PollForJobs. +* `required` - (Required) Whether the configuration property is a required value. +* `secret`- (Required) Whether the configuration property is secret. +* `type`- (Optional) The type of the configuration property. Valid values: `String`, `Number`, `Boolean` + +* `input_artifact_details` - (Required) The details of the input artifact for the action. + +The `input_artifact_details` object supports the following: + +* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 +* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 + +* `output_artifact_details` - (Required) The details of the output artifact of the action. + +The `output_artifact_details` object supports the following: + +* `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 +* `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 + +* `provider` - (Required) The provider of the service used in the custom action +* `settings` - (Optional) The settings for an action type. + +The `settings` object supports the following: + +* `entity_url_template` - (Optional) The URL returned to the AWS CodePipeline console that provides a deep link to the resources of the external system. +* `execution_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the top-level landing page for the external system. +* `revision_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the page where customers can update or change the configuration of the external action. +* `third_party_configuration_url` - (Optional) The URL of a sign-up page where users can sign up for an external service and perform initial configuration of the action provided by that service. + +* `version` - (Required) The version identifier of the custom action. + +## Attribute Reference + +The following arguments are exported: + +* `id` - Composed of category, provider and version. For example, `Build:terraform:1` +* `arn` - The action ARN. +* `owner` - The creator of the action being called. + +## Import + +CodeDeploy CustomActionType can be imported using the `id`, e.g. + +``` +$ terraform import aws_codepipeline_custom_action_type.example Build:terraform:1 +``` \ No newline at end of file From d73533f1e1938ab074641da28b44d2ec54747d2f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 14:43:30 -0400 Subject: [PATCH 14/23] r/aws_codepipeline_custom_action_type: Flex. --- .../codepipeline/custom_action_type.go | 328 +++++++++++++++--- 1 file changed, 273 insertions(+), 55 deletions(-) diff --git a/internal/service/codepipeline/custom_action_type.go b/internal/service/codepipeline/custom_action_type.go index 8efbe79864b2..4f56856ace8c 100644 --- a/internal/service/codepipeline/custom_action_type.go +++ b/internal/service/codepipeline/custom_action_type.go @@ -24,7 +24,6 @@ func ResourceCustomActionType() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceCustomActionTypeCreate, ReadWithoutTimeout: resourceCustomActionTypeRead, - UpdateWithoutTimeout: resourceCustomActionTypeUpdate, DeleteWithoutTimeout: resourceCustomActionTypeDelete, Importer: &schema.ResourceImporter{ @@ -189,6 +188,22 @@ func resourceCustomActionTypeCreate(ctx context.Context, d *schema.ResourceData, Version: aws.String(version), } + if v, ok := d.GetOk("configuration_property"); ok && len(v.([]interface{})) > 0 { + input.ConfigurationProperties = expandActionConfigurationProperties(v.([]interface{})) + } + + if v, ok := d.GetOk("input_artifact_details"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.InputArtifactDetails = expandArtifactDetails(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("output_artifact_details"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.OutputArtifactDetails = expandArtifactDetails(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("settings"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Settings = expandActionTypeSettings(v.([]interface{})[0].(map[string]interface{})) + } + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } @@ -236,8 +251,32 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m }.String() d.Set("arn", arn) d.Set("category", actionType.Id.Category) + if err := d.Set("configuration_property", flattenActionConfigurationProperties(actionType.ActionConfigurationProperties)); err != nil { + return diag.Errorf("setting configuration_property: %s", err) + } + if actionType.InputArtifactDetails != nil { + if err := d.Set("input_artifact_details", []interface{}{flattenArtifactDetails(actionType.InputArtifactDetails)}); err != nil { + return diag.Errorf("setting input_artifact_details: %s", err) + } + } else { + d.Set("input_artifact_details", nil) + } + if actionType.OutputArtifactDetails != nil { + if err := d.Set("output_artifact_details", []interface{}{flattenArtifactDetails(actionType.OutputArtifactDetails)}); err != nil { + return diag.Errorf("setting output_artifact_details: %s", err) + } + } else { + d.Set("output_artifact_details", nil) + } d.Set("owner", actionType.Id.Owner) d.Set("provider_name", actionType.Id.Provider) + if actionType.Settings != nil { + if err := d.Set("settings", []interface{}{flattenActionTypeSettings(actionType.Settings)}); err != nil { + return diag.Errorf("setting settings: %s", err) + } + } else { + d.Set("settings", nil) + } d.Set("version", actionType.Id.Version) tags, err := ListTagsWithContext(ctx, conn, arn) @@ -260,46 +299,6 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m return nil } -func resourceCustomActionTypeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).CodePipelineConn - - if d.HasChangesExcept("tags", "tags_all") { - category, provider, version, err := CustomActionTypeParseResourceID(d.Id()) - - if err != nil { - return diag.FromErr(err) - } - - input := &codepipeline.UpdateActionTypeInput{ - ActionType: &codepipeline.ActionTypeDeclaration{ - Id: &codepipeline.ActionTypeIdentifier{ - Category: aws.String(category), - Owner: aws.String(codepipeline.ActionOwnerCustom), - Provider: aws.String(provider), - Version: aws.String(version), - }, - }, - } - - _, err = conn.UpdateActionTypeWithContext(ctx, input) - - if err != nil { - return diag.Errorf("updating CodePipeline Custom Action Type (%s): %s", d.Id(), err) - } - } - - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - arn := d.Get("arn").(string) - - if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { - return diag.Errorf("updating CodePipeline Custom Action Type (%s) tags: %s", arn, err) - } - } - - return resourceCustomActionTypeRead(ctx, d, meta) -} - func resourceCustomActionTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn @@ -346,30 +345,249 @@ func CustomActionTypeParseResourceID(id string) (string, string, string, error) return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected category%[2]sprovider%[2]sversion", id, customActionTypeResourceIDSeparator) } -func FindCustomActionTypeByThreePartKey(ctx context.Context, conn *codepipeline.CodePipeline, category, provider, version string) (*codepipeline.ActionTypeDeclaration, error) { - input := &codepipeline.GetActionTypeInput{ - Category: aws.String(category), - Owner: aws.String(codepipeline.ActionOwnerCustom), - Provider: aws.String(provider), - Version: aws.String(version), +func FindCustomActionTypeByThreePartKey(ctx context.Context, conn *codepipeline.CodePipeline, category, provider, version string) (*codepipeline.ActionType, error) { + input := &codepipeline.ListActionTypesInput{ + ActionOwnerFilter: aws.String(codepipeline.ActionOwnerCustom), } + var output *codepipeline.ActionType - output, err := conn.GetActionTypeWithContext(ctx, input) + err := conn.ListActionTypesPagesWithContext(ctx, input, func(page *codepipeline.ListActionTypesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } - if tfawserr.ErrCodeEquals(err, codepipeline.ErrCodeActionTypeNotFoundException) { + for _, v := range page.ActionTypes { + if v == nil || v.Id == nil { + continue + } + + if aws.StringValue(v.Id.Category) == category && aws.StringValue(v.Id.Provider) == provider && aws.StringValue(v.Id.Version) == version { + output = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if output == nil { return nil, &resource.NotFoundError{ - LastError: err, LastRequest: input, } } - if err != nil { - return nil, err + return output, nil +} + +func expandActionConfigurationProperty(tfMap map[string]interface{}) *codepipeline.ActionConfigurationProperty { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.ActionConfigurationProperty{} + + if v, ok := tfMap["description"].(string); ok && v != "" { + apiObject.Description = aws.String(v) + } + + if v, ok := tfMap["key"].(bool); ok { + apiObject.Key = aws.Bool(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + if v, ok := tfMap["queryable"].(bool); ok && v { + apiObject.Queryable = aws.Bool(v) + } + + if v, ok := tfMap["required"].(bool); ok { + apiObject.Required = aws.Bool(v) + } + + if v, ok := tfMap["secret"].(bool); ok { + apiObject.Secret = aws.Bool(v) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + apiObject.Type = aws.String(v) + } + + return apiObject +} + +func expandActionConfigurationProperties(tfList []interface{}) []*codepipeline.ActionConfigurationProperty { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*codepipeline.ActionConfigurationProperty + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandActionConfigurationProperty(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandArtifactDetails(tfMap map[string]interface{}) *codepipeline.ArtifactDetails { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.ArtifactDetails{} + + if v, ok := tfMap["maximum_count"].(int); ok && v != 0 { + apiObject.MaximumCount = aws.Int64(int64(v)) + } + + if v, ok := tfMap["minimum_count"].(int); ok && v != 0 { + apiObject.MinimumCount = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandActionTypeSettings(tfMap map[string]interface{}) *codepipeline.ActionTypeSettings { + if tfMap == nil { + return nil + } + + apiObject := &codepipeline.ActionTypeSettings{} + + if v, ok := tfMap["entity_url_template"].(string); ok && v != "" { + apiObject.EntityUrlTemplate = aws.String(v) + } + + if v, ok := tfMap["execution_url_template"].(string); ok && v != "" { + apiObject.ExecutionUrlTemplate = aws.String(v) + } + + if v, ok := tfMap["revision_url_template"].(string); ok && v != "" { + apiObject.RevisionUrlTemplate = aws.String(v) + } + + if v, ok := tfMap["third_party_configuration_url"].(string); ok && v != "" { + apiObject.ThirdPartyConfigurationUrl = aws.String(v) + } + + return apiObject +} + +func flattenActionConfigurationProperty(apiObject *codepipeline.ActionConfigurationProperty) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Description; v != nil { + tfMap["description"] = aws.StringValue(v) + } + + if v := apiObject.Key; v != nil { + tfMap["key"] = aws.BoolValue(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.Queryable; v != nil { + tfMap["queryable"] = aws.BoolValue(v) + } + + if v := apiObject.Required; v != nil { + tfMap["required"] = aws.BoolValue(v) + } + + if v := apiObject.Secret; v != nil { + tfMap["secret"] = aws.BoolValue(v) + } + + if v := apiObject.Type; v != nil { + tfMap["type"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenActionConfigurationProperties(apiObjects []*codepipeline.ActionConfigurationProperty) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenActionConfigurationProperty(apiObject)) + } + + return tfList +} + +func flattenArtifactDetails(apiObject *codepipeline.ArtifactDetails) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.MaximumCount; v != nil { + tfMap["maximum_count"] = aws.Int64Value(v) + } + + if v := apiObject.MinimumCount; v != nil { + tfMap["minimum_count"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenActionTypeSettings(apiObject *codepipeline.ActionTypeSettings) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.EntityUrlTemplate; v != nil { + tfMap["entity_url_template"] = aws.StringValue(v) + } + + if v := apiObject.ExecutionUrlTemplate; v != nil { + tfMap["execution_url_template"] = aws.StringValue(v) + } + + if v := apiObject.RevisionUrlTemplate; v != nil { + tfMap["revision_url_template"] = aws.StringValue(v) } - if output == nil || output.ActionType == nil || output.ActionType.Id == nil { - return nil, tfresource.NewEmptyResultError(input) + if v := apiObject.ThirdPartyConfigurationUrl; v != nil { + tfMap["third_party_configuration_url"] = aws.StringValue(v) } - return output.ActionType, nil + return tfMap } From b47ffd716e24a07dbfc9df654136ec5f6874c8a4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 15:21:25 -0400 Subject: [PATCH 15/23] Add 'TestAccCodePipelineCustomActionType_basic'. --- .../service/codepipeline/codepipeline_test.go | 62 ++++---- .../codepipeline/custom_action_type.go | 36 +++-- .../codepipeline/custom_action_type_test.go | 140 ++++++++++++++++++ internal/service/codepipeline/webhook_test.go | 12 +- ...epipeline_custom_action_type.html.markdown | 6 +- 5 files changed, 207 insertions(+), 49 deletions(-) create mode 100644 internal/service/codepipeline/custom_action_type_test.go diff --git a/internal/service/codepipeline/codepipeline_test.go b/internal/service/codepipeline/codepipeline_test.go index 2c15fb4d126f..70979d6f2550 100644 --- a/internal/service/codepipeline/codepipeline_test.go +++ b/internal/service/codepipeline/codepipeline_test.go @@ -31,12 +31,12 @@ func TestAccCodePipeline_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.codepipeline_role", "arn"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), @@ -86,7 +86,7 @@ func TestAccCodePipeline_basic(t *testing.T) { { Config: testAccCodePipelineConfig_basicUpdated(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p2), + testAccCheckPipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -136,12 +136,12 @@ func TestAccCodePipeline_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p), + testAccCheckPipelineExists(resourceName, &p), acctest.CheckResourceDisappears(acctest.Provider, tfcodepipeline.ResourcePipeline(), resourceName), ), ExpectNonEmptyPlan: true, @@ -163,12 +163,12 @@ func TestAccCodePipeline_emptyStageArtifacts(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_emptyStageArtifacts(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p), + testAccCheckPipelineExists(resourceName, &p), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s$", name))), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -205,12 +205,12 @@ func TestAccCodePipeline_deployWithServiceRole(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_deployServiceRole(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p), + testAccCheckPipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "stage.2.name", "Deploy"), resource.TestCheckResourceAttr(resourceName, "stage.2.action.0.category", "Deploy"), resource.TestCheckResourceAttrPair(resourceName, "stage.2.action.0.role_arn", "aws_iam_role.codepipeline_action_role", "arn"), @@ -238,12 +238,12 @@ func TestAccCodePipeline_tags(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_tags(name, "tag1value", "tag2value"), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("test-pipeline-%s", name)), resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"), @@ -258,7 +258,7 @@ func TestAccCodePipeline_tags(t *testing.T) { { Config: testAccCodePipelineConfig_tags(name, "tag1valueUpdate", "tag2valueUpdate"), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p2), + testAccCheckPipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("test-pipeline-%s", name)), resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1valueUpdate"), @@ -273,7 +273,7 @@ func TestAccCodePipeline_tags(t *testing.T) { { Config: testAccCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p3), + testAccCheckPipelineExists(resourceName, &p3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -296,12 +296,12 @@ func TestAccCodePipeline_MultiRegion_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(t), - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p), + testAccCheckPipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -337,12 +337,12 @@ func TestAccCodePipeline_MultiRegion_update(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(t), - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -356,7 +356,7 @@ func TestAccCodePipeline_MultiRegion_update(t *testing.T) { { Config: testAccCodePipelineConfig_multiregionUpdated(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p2), + testAccCheckPipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -392,12 +392,12 @@ func TestAccCodePipeline_MultiRegion_convertSingleRegion(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(t), - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -409,7 +409,7 @@ func TestAccCodePipeline_MultiRegion_convertSingleRegion(t *testing.T) { { Config: testAccCodePipelineConfig_multiregion(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p2), + testAccCheckPipelineExists(resourceName, &p2), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -423,7 +423,7 @@ func TestAccCodePipeline_MultiRegion_convertSingleRegion(t *testing.T) { { Config: testAccCodePipelineConfig_backToBasic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), resource.TestCheckResourceAttr(resourceName, "artifact_store.#", "1"), resource.TestCheckResourceAttr(resourceName, "stage.1.name", "Build"), @@ -455,12 +455,12 @@ func TestAccCodePipeline_withNamespace(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_namespace(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p1), + testAccCheckPipelineExists(resourceName, &p1), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "codepipeline", regexp.MustCompile(fmt.Sprintf("test-pipeline-%s", name))), resource.TestCheckResourceAttr(resourceName, "stage.0.action.0.namespace", "SourceVariables"), ), @@ -488,12 +488,12 @@ func TestAccCodePipeline_withGitHubV1SourceAction(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_gitHubv1SourceAction(name, githubToken), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &v), + testAccCheckPipelineExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -521,7 +521,7 @@ func TestAccCodePipeline_withGitHubV1SourceAction(t *testing.T) { { Config: testAccCodePipelineConfig_gitHubv1SourceActionUpdated(name, githubToken), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &v), + testAccCheckPipelineExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), @@ -563,12 +563,12 @@ func TestAccCodePipeline_ecr(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccCodePipelineConfig_ecr(name), Check: resource.ComposeTestCheckFunc( - testAccCheckExists(resourceName, &p), + testAccCheckPipelineExists(resourceName, &p), resource.TestCheckResourceAttr(resourceName, "stage.#", "2"), resource.TestCheckResourceAttr(resourceName, "stage.0.name", "Source"), resource.TestCheckResourceAttr(resourceName, "stage.0.action.#", "1"), @@ -598,7 +598,7 @@ func TestAccCodePipeline_ecr(t *testing.T) { }) } -func testAccCheckExists(n string, v *codepipeline.PipelineDeclaration) resource.TestCheckFunc { +func testAccCheckPipelineExists(n string, v *codepipeline.PipelineDeclaration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -623,7 +623,7 @@ func testAccCheckExists(n string, v *codepipeline.PipelineDeclaration) resource. } } -func testAccCheckDestroy(s *terraform.State) error { +func testAccCheckPipelineDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).CodePipelineConn for _, rs := range s.RootModule().Resources { diff --git a/internal/service/codepipeline/custom_action_type.go b/internal/service/codepipeline/custom_action_type.go index 4f56856ace8c..15e20a99b561 100644 --- a/internal/service/codepipeline/custom_action_type.go +++ b/internal/service/codepipeline/custom_action_type.go @@ -24,6 +24,7 @@ func ResourceCustomActionType() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceCustomActionTypeCreate, ReadWithoutTimeout: resourceCustomActionTypeRead, + UpdateWithoutTimeout: resourceCustomActionTypeUpdate, DeleteWithoutTimeout: resourceCustomActionTypeDelete, Importer: &schema.ResourceImporter{ @@ -126,11 +127,11 @@ func ResourceCustomActionType() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "provider": { + "provider_name": { Type: schema.TypeString, ForceNew: true, Required: true, - ValidateFunc: validation.StringLenBetween(1, 25), + ValidateFunc: validation.StringLenBetween(1, 35), }, "settings": { Type: schema.TypeList, @@ -179,7 +180,7 @@ func resourceCustomActionTypeCreate(ctx context.Context, d *schema.ResourceData, tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) category := d.Get("category").(string) - provider := d.Get("provider").(string) + provider := d.Get("provider_name").(string) version := d.Get("version").(string) id := CustomActionTypeCreateResourceID(category, provider, version) input := &codepipeline.CreateCustomActionTypeInput{ @@ -247,7 +248,7 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m Service: codepipeline.ServiceName, Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("actiontype:%s:%s:%s:%s", codepipeline.ActionOwnerCustom, category, provider, version), + Resource: fmt.Sprintf("actiontype:%s/%s/%s/%s", codepipeline.ActionOwnerCustom, category, provider, version), }.String() d.Set("arn", arn) d.Set("category", actionType.Id.Category) @@ -270,7 +271,9 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m } d.Set("owner", actionType.Id.Owner) d.Set("provider_name", actionType.Id.Provider) - if actionType.Settings != nil { + if actionType.Settings != nil && + // Service can return empty ({}) Settings. + (actionType.Settings.EntityUrlTemplate != nil || actionType.Settings.ExecutionUrlTemplate != nil || actionType.Settings.RevisionUrlTemplate != nil || actionType.Settings.ThirdPartyConfigurationUrl != nil) { if err := d.Set("settings", []interface{}{flattenActionTypeSettings(actionType.Settings)}); err != nil { return diag.Errorf("setting settings: %s", err) } @@ -299,6 +302,21 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m return nil } +func resourceCustomActionTypeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CodePipelineConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + arn := d.Get("arn").(string) + + if err := UpdateTagsWithContext(ctx, conn, arn, o, n); err != nil { + return diag.Errorf("updating CodePipeline Custom Action Type (%s) tags: %s", arn, err) + } + } + + return resourceCustomActionTypeRead(ctx, d, meta) +} + func resourceCustomActionTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).CodePipelineConn @@ -320,13 +338,13 @@ func resourceCustomActionTypeDelete(ctx context.Context, d *schema.ResourceData, } if err != nil { - return diag.Errorf("deleting CodePipeline (%s): %s", d.Id(), err) + return diag.Errorf("deleting CodePipeline Custom Action Type (%s): %s", d.Id(), err) } return nil } -const customActionTypeResourceIDSeparator = ":" +const customActionTypeResourceIDSeparator = "/" func CustomActionTypeCreateResourceID(category, provider, version string) string { parts := []string{category, provider, version} @@ -455,11 +473,11 @@ func expandArtifactDetails(tfMap map[string]interface{}) *codepipeline.ArtifactD apiObject := &codepipeline.ArtifactDetails{} - if v, ok := tfMap["maximum_count"].(int); ok && v != 0 { + if v, ok := tfMap["maximum_count"].(int); ok { apiObject.MaximumCount = aws.Int64(int64(v)) } - if v, ok := tfMap["minimum_count"].(int); ok && v != 0 { + if v, ok := tfMap["minimum_count"].(int); ok { apiObject.MinimumCount = aws.Int64(int64(v)) } diff --git a/internal/service/codepipeline/custom_action_type_test.go b/internal/service/codepipeline/custom_action_type_test.go new file mode 100644 index 000000000000..0c410c368776 --- /dev/null +++ b/internal/service/codepipeline/custom_action_type_test.go @@ -0,0 +1,140 @@ +package codepipeline_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/aws/aws-sdk-go/service/codestarconnections" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfcodepipeline "github.com/hashicorp/terraform-provider-aws/internal/service/codepipeline" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccCodePipelineCustomActionType_basic(t *testing.T) { + var v codepipeline.ActionType + resourceName := "aws_codepipeline_custom_action_type.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(codestarconnections.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomActionType_basic(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", "actiontype:Custom/Test/CodeDeploy/1"), + resource.TestCheckResourceAttr(resourceName, "category", "Test"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.maximum_count", "5"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "0"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.maximum_count", "4"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.minimum_count", "1"), + resource.TestCheckResourceAttr(resourceName, "owner", "Custom"), + resource.TestCheckResourceAttr(resourceName, "provider_name", "CodeDeploy"), + resource.TestCheckResourceAttr(resourceName, "settings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "version", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCustomActionTypeExists(n string, v *codepipeline.ActionType) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No CodePipeline Custom Action Type ID is set") + } + + category, provider, version, err := tfcodepipeline.CustomActionTypeParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CodePipelineConn + + output, err := tfcodepipeline.FindCustomActionTypeByThreePartKey(context.Background(), conn, category, provider, version) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckCustomActionTypeDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CodePipelineConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_codepipeline_custom_action_type" { + continue + } + + category, provider, version, err := tfcodepipeline.CustomActionTypeParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfcodepipeline.FindCustomActionTypeByThreePartKey(context.Background(), conn, category, provider, version) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CodePipeline Custom Action Type %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCustomActionType_basic() string { + return ` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Test" + + input_artifact_details { + maximum_count = 5 + minimum_count = 0 + } + + output_artifact_details { + maximum_count = 4 + minimum_count = 1 + } + + provider_name = "CodeDeploy" + version = "1" +} +` +} diff --git a/internal/service/codepipeline/webhook_test.go b/internal/service/codepipeline/webhook_test.go index 33a5d944a330..235afd4bf697 100644 --- a/internal/service/codepipeline/webhook_test.go +++ b/internal/service/codepipeline/webhook_test.go @@ -31,7 +31,7 @@ func TestAccCodePipelineWebhook_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_basic(rName, githubToken), @@ -101,7 +101,7 @@ func TestAccCodePipelineWebhook_ipAuth(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_ipAuth(rName, githubToken), @@ -136,7 +136,7 @@ func TestAccCodePipelineWebhook_unauthenticated(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_unauthenticated(rName, githubToken), @@ -169,7 +169,7 @@ func TestAccCodePipelineWebhook_tags(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_tags(rName, "tag1value", "tag2value", githubToken), @@ -233,7 +233,7 @@ func TestAccCodePipelineWebhook_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_basic(rName, githubToken), @@ -262,7 +262,7 @@ func TestAccCodePipelineWebhook_UpdateAuthentication_secretToken(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDestroy, + CheckDestroy: testAccCheckPipelineDestroy, Steps: []resource.TestStep{ { Config: testAccWebhookConfig_basic(rName, githubToken), diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index bfd55f7fe269..1166aa804bda 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -26,8 +26,8 @@ resource "aws_codepipeline_custom_action_type" "example" { minimum_count = 0 } - provider = "example" - version = "1" + provider_name = "example" + version = "1" } ``` @@ -62,7 +62,7 @@ The `output_artifact_details` object supports the following: * `maximum_count` - (Required) The maximum number of artifacts allowed for the action type. Min: 0, Max: 5 * `minimum_count` - (Required) The minimum number of artifacts allowed for the action type. Min: 0, Max: 5 -* `provider` - (Required) The provider of the service used in the custom action +* `provider_name` - (Required) The provider of the service used in the custom action * `settings` - (Optional) The settings for an action type. The `settings` object supports the following: From f7025c6135305ed88192169643529740fde7ba21 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 15:37:01 -0400 Subject: [PATCH 16/23] Add 'TestAccCodePipelineCustomActionType_disappears' and 'TestAccCodePipelineCustomActionType_tags'. --- .../codepipeline/custom_action_type_test.go | 141 +++++++++++++++++- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/internal/service/codepipeline/custom_action_type_test.go b/internal/service/codepipeline/custom_action_type_test.go index 0c410c368776..6bdc827d887a 100644 --- a/internal/service/codepipeline/custom_action_type_test.go +++ b/internal/service/codepipeline/custom_action_type_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/service/codepipeline" "github.com/aws/aws-sdk-go/service/codestarconnections" + sdkacctest "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" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -17,6 +18,7 @@ import ( func TestAccCodePipelineCustomActionType_basic(t *testing.T) { var v codepipeline.ActionType + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_codepipeline_custom_action_type.test" resource.ParallelTest(t, resource.TestCase{ @@ -29,10 +31,10 @@ func TestAccCodePipelineCustomActionType_basic(t *testing.T) { CheckDestroy: testAccCheckCustomActionTypeDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomActionType_basic(), + Config: testAccCustomActionType_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), - acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", "actiontype:Custom/Test/CodeDeploy/1"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", fmt.Sprintf("actiontype:Custom/Test/%s/1", rName)), resource.TestCheckResourceAttr(resourceName, "category", "Test"), resource.TestCheckResourceAttr(resourceName, "configuration_property.#", "0"), resource.TestCheckResourceAttr(resourceName, "input_artifact_details.#", "1"), @@ -42,7 +44,7 @@ func TestAccCodePipelineCustomActionType_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.maximum_count", "4"), resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.minimum_count", "1"), resource.TestCheckResourceAttr(resourceName, "owner", "Custom"), - resource.TestCheckResourceAttr(resourceName, "provider_name", "CodeDeploy"), + resource.TestCheckResourceAttr(resourceName, "provider_name", rName), resource.TestCheckResourceAttr(resourceName, "settings.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "version", "1"), @@ -57,6 +59,80 @@ func TestAccCodePipelineCustomActionType_basic(t *testing.T) { }) } +func TestAccCodePipelineCustomActionType_disappears(t *testing.T) { + var v codepipeline.ActionType + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codepipeline_custom_action_type.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(codestarconnections.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomActionType_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfcodepipeline.ResourceCustomActionType(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccCodePipelineCustomActionType_tags(t *testing.T) { + var v codepipeline.ActionType + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codepipeline_custom_action_type.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(codestarconnections.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomActionType_tags1(rName, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCustomActionType_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccCustomActionType_tags1(rName, "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func testAccCheckCustomActionTypeExists(n string, v *codepipeline.ActionType) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -118,8 +194,8 @@ func testAccCheckCustomActionTypeDestroy(s *terraform.State) error { return nil } -func testAccCustomActionType_basic() string { - return ` +func testAccCustomActionType_basic(rName string) string { + return fmt.Sprintf(` resource "aws_codepipeline_custom_action_type" "test" { category = "Test" @@ -133,8 +209,59 @@ resource "aws_codepipeline_custom_action_type" "test" { minimum_count = 1 } - provider_name = "CodeDeploy" + provider_name = %[1]q version = "1" } -` +`, rName) +} + +func testAccCustomActionType_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Test" + + input_artifact_details { + maximum_count = 5 + minimum_count = 0 + } + + output_artifact_details { + maximum_count = 4 + minimum_count = 1 + } + + provider_name = %[1]q + version = "1" + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccCustomActionType_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Test" + + input_artifact_details { + maximum_count = 5 + minimum_count = 0 + } + + output_artifact_details { + maximum_count = 4 + minimum_count = 1 + } + + provider_name = %[1]q + version = "1" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } From d74c2d399470643d2f14c10d19d5d7116fd71219 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 15:39:11 -0400 Subject: [PATCH 17/23] r/aws_codepipeline_custom_action_type: Document tags. --- website/docs/r/codepipeline_custom_action_type.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index 1166aa804bda..6f8b0d282c3f 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -72,6 +72,7 @@ The `settings` object supports the following: * `revision_url_template` - (Optional) The URL returned to the AWS CodePipeline console that contains a link to the page where customers can update or change the configuration of the external action. * `third_party_configuration_url` - (Optional) The URL of a sign-up page where users can sign up for an external service and perform initial configuration of the action provided by that service. +* `tags` - (Optional) Map of tags to assign to this resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `version` - (Required) The version identifier of the custom action. ## Attribute Reference @@ -81,6 +82,7 @@ The following arguments are exported: * `id` - Composed of category, provider and version. For example, `Build:terraform:1` * `arn` - The action ARN. * `owner` - The creator of the action being called. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). ## Import From 4ce42ccb1bb9b3febe741c569a95200af2bcfef7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:05:41 -0400 Subject: [PATCH 18/23] r/aws_codepipeline_custom_action_type: Add 'TestAccCodePipelineCustomActionType_allAttributes'. --- .../codepipeline/custom_action_type.go | 14 ++- .../codepipeline/custom_action_type_test.go | 110 ++++++++++++++++++ 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/internal/service/codepipeline/custom_action_type.go b/internal/service/codepipeline/custom_action_type.go index 15e20a99b561..33ab9ce525d7 100644 --- a/internal/service/codepipeline/custom_action_type.go +++ b/internal/service/codepipeline/custom_action_type.go @@ -252,7 +252,7 @@ func resourceCustomActionTypeRead(ctx context.Context, d *schema.ResourceData, m }.String() d.Set("arn", arn) d.Set("category", actionType.Id.Category) - if err := d.Set("configuration_property", flattenActionConfigurationProperties(actionType.ActionConfigurationProperties)); err != nil { + if err := d.Set("configuration_property", flattenActionConfigurationProperties(d, actionType.ActionConfigurationProperties)); err != nil { return diag.Errorf("setting configuration_property: %s", err) } if actionType.InputArtifactDetails != nil { @@ -510,7 +510,7 @@ func expandActionTypeSettings(tfMap map[string]interface{}) *codepipeline.Action return apiObject } -func flattenActionConfigurationProperty(apiObject *codepipeline.ActionConfigurationProperty) map[string]interface{} { +func flattenActionConfigurationProperty(d *schema.ResourceData, i int, apiObject *codepipeline.ActionConfigurationProperty) map[string]interface{} { if apiObject == nil { return nil } @@ -543,24 +543,28 @@ func flattenActionConfigurationProperty(apiObject *codepipeline.ActionConfigurat if v := apiObject.Type; v != nil { tfMap["type"] = aws.StringValue(v) + } else { + // The AWS API does not return Type. + key := fmt.Sprintf("configuration_property.%d.type", i) + tfMap["type"] = d.Get(key).(string) } return tfMap } -func flattenActionConfigurationProperties(apiObjects []*codepipeline.ActionConfigurationProperty) []interface{} { +func flattenActionConfigurationProperties(d *schema.ResourceData, apiObjects []*codepipeline.ActionConfigurationProperty) []interface{} { if len(apiObjects) == 0 { return nil } var tfList []interface{} - for _, apiObject := range apiObjects { + for i, apiObject := range apiObjects { if apiObject == nil { continue } - tfList = append(tfList, flattenActionConfigurationProperty(apiObject)) + tfList = append(tfList, flattenActionConfigurationProperty(d, i, apiObject)) } return tfList diff --git a/internal/service/codepipeline/custom_action_type_test.go b/internal/service/codepipeline/custom_action_type_test.go index 6bdc827d887a..4c419e1fe430 100644 --- a/internal/service/codepipeline/custom_action_type_test.go +++ b/internal/service/codepipeline/custom_action_type_test.go @@ -133,6 +133,71 @@ func TestAccCodePipelineCustomActionType_tags(t *testing.T) { }) } +func TestAccCodePipelineCustomActionType_allAttributes(t *testing.T) { + var v codepipeline.ActionType + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codepipeline_custom_action_type.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(codestarconnections.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, codepipeline.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomActionTypeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomActionType_allAttributes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomActionTypeExists(resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", fmt.Sprintf("actiontype:Custom/Test/%s/1", rName)), + resource.TestCheckResourceAttr(resourceName, "category", "Test"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.#", "2"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.description", ""), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.key", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.name", "pk"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.queryable", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.required", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.secret", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.0.type", "Number"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.description", "Date of birth"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.key", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.name", "dob"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.queryable", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.required", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.secret", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_property.1.type", "String"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.maximum_count", "3"), + resource.TestCheckResourceAttr(resourceName, "input_artifact_details.0.minimum_count", "2"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.maximum_count", "5"), + resource.TestCheckResourceAttr(resourceName, "output_artifact_details.0.minimum_count", "4"), + resource.TestCheckResourceAttr(resourceName, "owner", "Custom"), + resource.TestCheckResourceAttr(resourceName, "provider_name", rName), + resource.TestCheckResourceAttr(resourceName, "settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "settings.0.entity_url_template", "https://example.com/entity"), + resource.TestCheckResourceAttr(resourceName, "settings.0.execution_url_template", ""), + resource.TestCheckResourceAttr(resourceName, "settings.0.revision_url_template", "https://example.com/configuration"), + resource.TestCheckResourceAttr(resourceName, "settings.0.third_party_configuration_url", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "version", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "configuration_property.0.type", + "configuration_property.1.type", + }, + }, + }, + }) +} + func testAccCheckCustomActionTypeExists(n string, v *codepipeline.ActionType) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -265,3 +330,48 @@ resource "aws_codepipeline_custom_action_type" "test" { } `, rName, tagKey1, tagValue1, tagKey2, tagValue2) } + +func testAccCustomActionType_allAttributes(rName string) string { + return fmt.Sprintf(` +resource "aws_codepipeline_custom_action_type" "test" { + category = "Test" + + configuration_property { + key = true + name = "pk" + queryable = true + required = true + secret = false + type = "Number" + } + + configuration_property { + description = "Date of birth" + key = false + name = "dob" + queryable = false + required = false + secret = true + type = "String" + } + + input_artifact_details { + maximum_count = 3 + minimum_count = 2 + } + + output_artifact_details { + maximum_count = 5 + minimum_count = 4 + } + + provider_name = %[1]q + version = "1" + + settings { + entity_url_template = "https://example.com/entity" + revision_url_template = "https://example.com/configuration" + } +} +`, rName) +} From 89f25b83261b139679829ac870b998c7464f5a62 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:14:41 -0400 Subject: [PATCH 19/23] Fix semgrep 'ci.test-config-funcs-correct-form'. --- .../codepipeline/custom_action_type_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/codepipeline/custom_action_type_test.go b/internal/service/codepipeline/custom_action_type_test.go index 4c419e1fe430..6249410bb0ee 100644 --- a/internal/service/codepipeline/custom_action_type_test.go +++ b/internal/service/codepipeline/custom_action_type_test.go @@ -31,7 +31,7 @@ func TestAccCodePipelineCustomActionType_basic(t *testing.T) { CheckDestroy: testAccCheckCustomActionTypeDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomActionType_basic(rName), + Config: testAccCustomActionTypeConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", fmt.Sprintf("actiontype:Custom/Test/%s/1", rName)), @@ -74,7 +74,7 @@ func TestAccCodePipelineCustomActionType_disappears(t *testing.T) { CheckDestroy: testAccCheckCustomActionTypeDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomActionType_basic(rName), + Config: testAccCustomActionTypeConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), acctest.CheckResourceDisappears(acctest.Provider, tfcodepipeline.ResourceCustomActionType(), resourceName), @@ -100,7 +100,7 @@ func TestAccCodePipelineCustomActionType_tags(t *testing.T) { CheckDestroy: testAccCheckCustomActionTypeDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomActionType_tags1(rName, "key1", "value1"), + Config: testAccCustomActionTypeConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -113,7 +113,7 @@ func TestAccCodePipelineCustomActionType_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccCustomActionType_tags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccCustomActionTypeConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -122,7 +122,7 @@ func TestAccCodePipelineCustomActionType_tags(t *testing.T) { ), }, { - Config: testAccCustomActionType_tags1(rName, "key2", "value2"), + Config: testAccCustomActionTypeConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -148,7 +148,7 @@ func TestAccCodePipelineCustomActionType_allAttributes(t *testing.T) { CheckDestroy: testAccCheckCustomActionTypeDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomActionType_allAttributes(rName), + Config: testAccCustomActionTypeConfig_allAttributes(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckCustomActionTypeExists(resourceName, &v), acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "codepipeline", fmt.Sprintf("actiontype:Custom/Test/%s/1", rName)), @@ -259,7 +259,7 @@ func testAccCheckCustomActionTypeDestroy(s *terraform.State) error { return nil } -func testAccCustomActionType_basic(rName string) string { +func testAccCustomActionTypeConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_codepipeline_custom_action_type" "test" { category = "Test" @@ -280,7 +280,7 @@ resource "aws_codepipeline_custom_action_type" "test" { `, rName) } -func testAccCustomActionType_tags1(rName, tagKey1, tagValue1 string) string { +func testAccCustomActionTypeConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_codepipeline_custom_action_type" "test" { category = "Test" @@ -305,7 +305,7 @@ resource "aws_codepipeline_custom_action_type" "test" { `, rName, tagKey1, tagValue1) } -func testAccCustomActionType_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccCustomActionTypeConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_codepipeline_custom_action_type" "test" { category = "Test" @@ -331,7 +331,7 @@ resource "aws_codepipeline_custom_action_type" "test" { `, rName, tagKey1, tagValue1, tagKey2, tagValue2) } -func testAccCustomActionType_allAttributes(rName string) string { +func testAccCustomActionTypeConfig_allAttributes(rName string) string { return fmt.Sprintf(` resource "aws_codepipeline_custom_action_type" "test" { category = "Test" From 4f45edd65e2f29c178b4fc36e2f94251ef2e5891 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:39:55 -0400 Subject: [PATCH 20/23] Fix terrafmt errors. --- .../codepipeline/custom_action_type_test.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/service/codepipeline/custom_action_type_test.go b/internal/service/codepipeline/custom_action_type_test.go index 6249410bb0ee..0d70f7f78182 100644 --- a/internal/service/codepipeline/custom_action_type_test.go +++ b/internal/service/codepipeline/custom_action_type_test.go @@ -339,20 +339,20 @@ resource "aws_codepipeline_custom_action_type" "test" { configuration_property { key = true name = "pk" - queryable = true - required = true - secret = false - type = "Number" + queryable = true + required = true + secret = false + type = "Number" } configuration_property { - description = "Date of birth" + description = "Date of birth" key = false name = "dob" - queryable = false - required = false - secret = true - type = "String" + queryable = false + required = false + secret = true + type = "String" } input_artifact_details { @@ -369,8 +369,8 @@ resource "aws_codepipeline_custom_action_type" "test" { version = "1" settings { - entity_url_template = "https://example.com/entity" - revision_url_template = "https://example.com/configuration" + entity_url_template = "https://example.com/entity" + revision_url_template = "https://example.com/configuration" } } `, rName) From 5ee2b86e6d0a04b001ec5f5e15634f8f56b8d6cd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:40:56 -0400 Subject: [PATCH 21/23] Fix markdownlint 'MD047/single-trailing-newline Files should end with a single newline character'. --- website/docs/r/codepipeline_custom_action_type.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index 6f8b0d282c3f..fc713d099429 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -90,4 +90,4 @@ CodeDeploy CustomActionType can be imported using the `id`, e.g. ``` $ terraform import aws_codepipeline_custom_action_type.example Build:terraform:1 -``` \ No newline at end of file +``` From 3493702c5eabde7c72c6d9aa17fe8a6799fc6f3f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:42:07 -0400 Subject: [PATCH 22/23] Fix tfproviderdocs error. --- website/docs/r/codepipeline_custom_action_type.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index fc713d099429..03e57086ac67 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -6,7 +6,7 @@ description: |- Provides a CodePipeline CustomActionType. --- -# aws_codepipeline_custom_action_type +# Resource: aws_codepipeline_custom_action_type Provides a CodeDeploy CustomActionType From 1efdb4362819ab185d1c541d718df92db26c5d81 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 10 Oct 2022 16:56:29 -0400 Subject: [PATCH 23/23] Fix tfproviderdocs errors. --- .../docs/r/codepipeline_custom_action_type.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/codepipeline_custom_action_type.html.markdown b/website/docs/r/codepipeline_custom_action_type.html.markdown index 03e57086ac67..2bcaf44a1028 100644 --- a/website/docs/r/codepipeline_custom_action_type.html.markdown +++ b/website/docs/r/codepipeline_custom_action_type.html.markdown @@ -12,7 +12,7 @@ Provides a CodeDeploy CustomActionType ## Example Usage -```hcl +```terraform resource "aws_codepipeline_custom_action_type" "example" { category = "Build" @@ -75,9 +75,9 @@ The `settings` object supports the following: * `tags` - (Optional) Map of tags to assign to this resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `version` - (Required) The version identifier of the custom action. -## Attribute Reference +## Attributes Reference -The following arguments are exported: +In addition to all arguments above, the following attributes are exported: * `id` - Composed of category, provider and version. For example, `Build:terraform:1` * `arn` - The action ARN.