Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Manage Triggers for AWS CodeDeploy Event Notifications #5599

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions builtin/providers/aws/resource_aws_codedeploy_deployment_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 = expandStringList(m["trigger_events"].(*schema.Set).List())
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))
Expand Down Expand Up @@ -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{})
Expand All @@ -362,3 +433,20 @@ 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)
if value != "DeploymentStart" && value != "DeploymentSuccess" && value != "DeploymentFailure" && value != "DeploymentStop" && value != "InstanceStart" && value != "InstanceSuccess" && value != "InstanceFailure" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels pretty hacky - this conditional will need to continue to be expanded if newer values are added. A potential pattern to use here may be something like this:

https://github.com/hashicorp/terraform/blob/master/builtin/providers/aws/validators.go#L81

This would mean the associated tests would then be able to be specified as follows:

https://github.com/hashicorp/terraform/blob/master/builtin/providers/aws/resource_aws_dynamodb_table_test.go#L58

rather than having 2 ranges within the test func

errors = append(errors, fmt.Errorf(
"%q must be a valid event type value: %q", k, value))
}
return
}
220 changes: 216 additions & 4 deletions builtin/providers/aws/resource_aws_codedeploy_deployment_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,83 @@ func TestAccAWSCodeDeployDeploymentGroup_basic(t *testing.T) {
})
}

func TestAccAWSCodeDeployDeploymentGroup_triggerConfiguration(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.TestStep{
Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"),
),
},
},
})
}

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.TestStep{
Config: testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_updateMultiple,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group"),
),
},
},
})
}

func TestValidateAWSCodeDeployTriggerEvent(t *testing.T) {
validEvents := []string{
"DeploymentStart",
"DeploymentSuccess",
"DeploymentFailure",
"DeploymentStop",
"InstanceStart",
"InstanceSuccess",
"InstanceFailure",
}

for _, v := range validEvents {
_, errors := validateTriggerEvent(v, "trigger_event")
if len(errors) != 0 {
t.Fatalf("%q should be a valid trigger event type: %q", v, errors)
}
}

invalidEvents := []string{
"DeploymentStarts",
"InstanceFail",
"Foo",
"",
}

for _, v := range invalidEvents {
_, errors := validateTriggerEvent(v, "trigger_event")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid trigger event type: %q", v, errors)
}
}
}

func testAccCheckAWSCodeDeployDeploymentGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).codedeployconn

Expand Down Expand Up @@ -90,14 +167,14 @@ resource "aws_iam_role_policy" "foo_policy" {
"Action": [
"autoscaling:CompleteLifecycleAction",
"autoscaling:DeleteLifecycleHook",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeLifecycleHooks",
"autoscaling:PutLifecycleHook",
"autoscaling:RecordLifecycleActionHeartbeat",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"tag:GetTags",
"tag:GetResources"
"tag:GetResources"
],
"Resource": "*"
}
Expand Down Expand Up @@ -155,14 +232,14 @@ resource "aws_iam_role_policy" "foo_policy" {
"Action": [
"autoscaling:CompleteLifecycleAction",
"autoscaling:DeleteLifecycleHook",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeLifecycleHooks",
"autoscaling:PutLifecycleHook",
"autoscaling:RecordLifecycleActionHeartbeat",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"tag:GetTags",
"tag:GetResources"
"tag:GetResources"
],
"Resource": "*"
}
Expand Down Expand Up @@ -202,3 +279,138 @@ resource "aws_codedeploy_deployment_group" "foo" {
value = "filtervalue"
}
}`

const baseCodeDeployConfig = `
resource "aws_codedeploy_app" "foo_app" {
name = "foo"
}

resource "aws_iam_role_policy" "foo_policy" {
name = "foo"
role = "${aws_iam_role.foo_role.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"autoscaling:CompleteLifecycleAction",
"autoscaling:DeleteLifecycleHook",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeLifecycleHooks",
"autoscaling:PutLifecycleHook",
"autoscaling:RecordLifecycleActionHeartbeat",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"tag:GetTags",
"tag:GetResources",
"sns:Publish"
],
"Resource": "*"
}
]
}
EOF
}

resource "aws_iam_role" "foo_role" {
name = "foo"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "codedeploy.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_sns_topic" "foo_topic" {
name = "foo"
}

`

const testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_create = baseCodeDeployConfig + `
resource "aws_codedeploy_deployment_group" "foo_group" {
app_name = "${aws_codedeploy_app.foo_app.name}"
deployment_group_name = "foo"
service_role_arn = "${aws_iam_role.foo_role.arn}"

trigger_configuration {
trigger_events = ["DeploymentFailure"]
trigger_name = "foo-trigger"
trigger_target_arn = "${aws_sns_topic.foo_topic.arn}"
}
}`

const testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_update = baseCodeDeployConfig + `
resource "aws_codedeploy_deployment_group" "foo_group" {
app_name = "${aws_codedeploy_app.foo_app.name}"
deployment_group_name = "foo"
service_role_arn = "${aws_iam_role.foo_role.arn}"

trigger_configuration {
trigger_events = ["DeploymentSuccess", "DeploymentFailure"]
trigger_name = "foo-trigger"
trigger_target_arn = "${aws_sns_topic.foo_topic.arn}"
}
}`

const testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_createMultiple = baseCodeDeployConfig + `
resource "aws_sns_topic" "bar_topic" {
name = "bar"
}

resource "aws_codedeploy_deployment_group" "foo_group" {
app_name = "${aws_codedeploy_app.foo_app.name}"
deployment_group_name = "foo"
service_role_arn = "${aws_iam_role.foo_role.arn}"

trigger_configuration {
trigger_events = ["DeploymentFailure"]
trigger_name = "foo-trigger"
trigger_target_arn = "${aws_sns_topic.foo_topic.arn}"
}

trigger_configuration {
trigger_events = ["InstanceFailure"]
trigger_name = "bar-trigger"
trigger_target_arn = "${aws_sns_topic.bar_topic.arn}"
}
}`

const testAccAWSCodeDeployDeploymentGroup_triggerConfiguration_updateMultiple = baseCodeDeployConfig + `
resource "aws_sns_topic" "bar_topic" {
name = "bar"
}

resource "aws_sns_topic" "baz_topic" {
name = "baz"
}

resource "aws_codedeploy_deployment_group" "foo_group" {
app_name = "${aws_codedeploy_app.foo_app.name}"
deployment_group_name = "foo"
service_role_arn = "${aws_iam_role.foo_role.arn}"

trigger_configuration {
trigger_events = ["DeploymentStart", "DeploymentSuccess", "DeploymentFailure", "DeploymentStop"]
trigger_name = "foo-trigger"
trigger_target_arn = "${aws_sns_topic.foo_topic.arn}"
}

trigger_configuration {
trigger_events = ["InstanceFailure"]
trigger_name = "bar-trigger"
trigger_target_arn = "${aws_sns_topic.baz_topic.arn}"
}
}`
Loading