diff --git a/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go b/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go index ed4177e423c3..931f374a4ae8 100644 --- a/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go +++ b/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go @@ -125,6 +125,35 @@ func resourceAwsCodeDeployDeploymentGroup() *schema.Resource { }, Set: resourceAwsCodeDeployTagFilterHash, }, + + "trigger_configuration": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "trigger_events": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Set: schema.HashString, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateTriggerEvent, + }, + }, + + "trigger_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "trigger_target_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: resourceAwsCodeDeployTriggerConfigHash, + }, }, } } @@ -154,6 +183,10 @@ func resourceAwsCodeDeployDeploymentGroupCreate(d *schema.ResourceData, meta int ec2TagFilters := buildEC2TagFilters(attr.(*schema.Set).List()) input.Ec2TagFilters = ec2TagFilters } + if attr, ok := d.GetOk("trigger_configuration"); ok { + triggerConfigs := buildTriggerConfigs(attr.(*schema.Set).List()) + input.TriggerConfigurations = triggerConfigs + } // Retry to handle IAM role eventual consistency. var resp *codedeploy.CreateDeploymentGroupOutput @@ -207,6 +240,9 @@ func resourceAwsCodeDeployDeploymentGroupRead(d *schema.ResourceData, meta inter if err := d.Set("on_premises_instance_tag_filter", onPremisesTagFiltersToMap(resp.DeploymentGroupInfo.OnPremisesInstanceTagFilters)); err != nil { return err } + if err := d.Set("trigger_configuration", triggerConfigsToMap(resp.DeploymentGroupInfo.TriggerConfigurations)); err != nil { + return err + } return nil } @@ -243,6 +279,11 @@ func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta int ec2Filters := buildEC2TagFilters(n.(*schema.Set).List()) input.Ec2TagFilters = ec2Filters } + if d.HasChange("trigger_configuration") { + _, n := d.GetChange("trigger_configuration") + triggerConfigs := buildTriggerConfigs(n.(*schema.Set).List()) + input.TriggerConfigurations = triggerConfigs + } log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id()) _, err := conn.UpdateDeploymentGroup(&input) @@ -306,6 +347,23 @@ func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter { return filters } +// buildTriggerConfigs converts a raw schema list into a list of +// codedeploy.TriggerConfig. +func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig { + configs := make([]*codedeploy.TriggerConfig, 0, len(configured)) + for _, raw := range configured { + var config codedeploy.TriggerConfig + m := raw.(map[string]interface{}) + + config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set)) + config.TriggerName = aws.String(m["trigger_name"].(string)) + config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string)) + + configs = append(configs, &config) + } + return configs +} + // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string. func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string { result := make([]map[string]string, 0, len(list)) @@ -344,6 +402,19 @@ func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string return result } +// triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{} +func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, tc := range list { + item := make(map[string]interface{}) + item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents)) + item["trigger_name"] = *tc.TriggerName + item["trigger_target_arn"] = *tc.TriggerTargetArn + result = append(result, item) + } + return result +} + func resourceAwsCodeDeployTagFilterHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -362,3 +433,29 @@ func resourceAwsCodeDeployTagFilterHash(v interface{}) int { return hashcode.String(buf.String()) } + +func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string))) + return hashcode.String(buf.String()) +} + +func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + triggerEvents := map[string]bool{ + "DeploymentStart": true, + "DeploymentStop": true, + "DeploymentSuccess": true, + "DeploymentFailure": true, + "InstanceStart": true, + "InstanceSuccess": true, + "InstanceFailure": true, + } + + if !triggerEvents[value] { + errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value)) + } + return +} diff --git a/builtin/providers/aws/resource_aws_codedeploy_deployment_group_test.go b/builtin/providers/aws/resource_aws_codedeploy_deployment_group_test.go index fa97ca4cc68f..509f4d9f1ed3 100644 --- a/builtin/providers/aws/resource_aws_codedeploy_deployment_group_test.go +++ b/builtin/providers/aws/resource_aws_codedeploy_deployment_group_test.go @@ -2,12 +2,14 @@ package aws import ( "fmt" + "reflect" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/codedeploy" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -21,18 +23,249 @@ func TestAccAWSCodeDeployDeploymentGroup_basic(t *testing.T) { Config: testAccAWSCodeDeployDeploymentGroup, Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "app_name", "foo_app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "deployment_group_name", "foo"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "deployment_config_name", "CodeDeployDefault.OneAtATime"), + + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.#", "1"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2916377465.key", "filterkey"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2916377465.type", "KEY_AND_VALUE"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2916377465.value", "filtervalue"), ), }, resource.TestStep{ Config: testAccAWSCodeDeployDeploymentGroupModified, Check: resource.ComposeTestCheckFunc( testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "app_name", "foo_app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "deployment_group_name", "bar"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "deployment_config_name", "CodeDeployDefault.OneAtATime"), + + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.#", "1"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2369538975.key", "filterkey"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2369538975.type", "KEY_AND_VALUE"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo", "ec2_tag_filter.2369538975.value", "anotherfiltervalue"), + ), + }, + }, + }) +} + +func TestAccAWSCodeDeployDeploymentGroup_triggerConfiguration_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_create, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "app_name", "foo-app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_group_name", "foo-group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_config_name", "CodeDeployDefault.OneAtATime"), + ), + }, + resource.TestStep{ + Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "app_name", "foo-app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_group_name", "foo-group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_config_name", "CodeDeployDefault.OneAtATime"), ), }, }, }) } +func TestAccAWSCodeDeployDeploymentGroup_triggerConfiguration_multiple(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_createMultiple, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "app_name", "foo-app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_group_name", "foo-group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_config_name", "CodeDeployDefault.OneAtATime"), + ), + }, + resource.TestStep{ + Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_updateMultiple, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "app_name", "foo-app"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_group_name", "foo-group"), + resource.TestCheckResourceAttr( + "aws_codedeploy_deployment_group.foo_group", "deployment_config_name", "CodeDeployDefault.OneAtATime"), + ), + }, + }, + }) +} + +func TestValidateAWSCodeDeployTriggerEvent(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "DeploymentStart", + ErrCount: 0, + }, + { + Value: "DeploymentStop", + ErrCount: 0, + }, + { + Value: "DeploymentSuccess", + ErrCount: 0, + }, + { + Value: "DeploymentFailure", + ErrCount: 0, + }, + { + Value: "InstanceStart", + ErrCount: 0, + }, + { + Value: "InstanceSuccess", + ErrCount: 0, + }, + { + Value: "InstanceFailure", + ErrCount: 0, + }, + { + Value: "DeploymentStarts", + ErrCount: 1, + }, + { + Value: "InstanceFail", + ErrCount: 1, + }, + { + Value: "Foo", + ErrCount: 1, + }, + { + Value: "", + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateTriggerEvent(tc.Value, "trigger_event") + if len(errors) != tc.ErrCount { + t.Fatalf("Trigger event validation failed for event type %q: %q", tc.Value, errors) + } + } +} + +func TestBuildTriggerConfigs(t *testing.T) { + input := []interface{}{ + map[string]interface{}{ + "trigger_events": schema.NewSet(schema.HashString, []interface{}{ + "DeploymentFailure", + }), + "trigger_name": "foo-trigger", + "trigger_target_arn": "arn:aws:sns:us-west-2:123456789012:foo-topic", + }, + } + + expected := []*codedeploy.TriggerConfig{ + &codedeploy.TriggerConfig{ + TriggerEvents: []*string{ + aws.String("DeploymentFailure"), + }, + TriggerName: aws.String("foo-trigger"), + TriggerTargetArn: aws.String("arn:aws:sns:us-west-2:123456789012:foo-topic"), + }, + } + + actual := buildTriggerConfigs(input) + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("buildTriggerConfigs output is not correct.\nGot:\n%#v\nExpected:\n%#v\n", + actual, expected) + } +} + +func TestTriggerConfigsToMap(t *testing.T) { + input := []*codedeploy.TriggerConfig{ + &codedeploy.TriggerConfig{ + TriggerEvents: []*string{ + aws.String("DeploymentFailure"), + aws.String("InstanceFailure"), + }, + TriggerName: aws.String("bar-trigger"), + TriggerTargetArn: aws.String("arn:aws:sns:us-west-2:123456789012:bar-topic"), + }, + } + + expected := map[string]interface{}{ + "trigger_events": schema.NewSet(schema.HashString, []interface{}{ + "DeploymentFailure", + "InstanceFailure", + }), + "trigger_name": "bar-trigger", + "trigger_target_arn": "arn:aws:sns:us-west-2:123456789012:bar-topic", + } + + actual := triggerConfigsToMap(input)[0] + + fatal := false + + if actual["trigger_name"] != expected["trigger_name"] { + fatal = true + } + + if actual["trigger_target_arn"] != expected["trigger_target_arn"] { + fatal = true + } + + actualEvents := actual["trigger_events"].(*schema.Set) + expectedEvents := expected["trigger_events"].(*schema.Set) + if !actualEvents.Equal(expectedEvents) { + fatal = true + } + + if fatal { + t.Fatalf("triggerConfigsToMap output is not correct.\nGot:\n%#v\nExpected:\n%#v\n", + actual, expected) + } +} + func testAccCheckAWSCodeDeployDeploymentGroupDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).codedeployconn @@ -83,25 +316,25 @@ resource "aws_iam_role_policy" "foo_policy" { role = "${aws_iam_role.foo_role.id}" policy = <