Automatic Rollback of CodeDeploy deployments and CloudWatch Alarms for a Deployment Group (#9039)
* provider/aws: Add DeploymentRollback as a valid TriggerEvent type * provider/aws: Add auto_rollback_configuration to aws_codedeploy_deployment_group * provider/aws: Document auto_rollback_configuration - part of aws_codedeploy_deployment_group * provider/aws: Support removing and disabling auto_rollback_configuration - part of aws_codedeploy_deployment_group resource - when removing configuration, ensure events are removed - when disabling configuration, preserve events in case configuration is re-enabled * provider/aws: Add alarm_configuration to aws_codedeploy_deployment_group * provider/aws: Document alarm_configuration - part of aws_codedeploy_deployment_group * provider/aws: Support removing alarm_configuration - part of aws_codedeploy_deployment_group resource - disabling configuration doesn't appear to work... * provider/aws: Refactor auto_rollback_configuration tests - Add create test - SKIP failing test for now - Add tests for build & map functions * provider/aws: Refactor new aws_code_deploy_deployment_group tests - alarm_configuration and auto_rollback_configuration only - add assertions to deployment_group basic test - rename config funcs to be more easy to read - group public tests together * provider/aws: A max of 10 alarms can be added to a deployment group. - aws_code_deploy_deployment_group.alarm_configuration.alarms - verified this causes test failure with expected exception * provider/aws: Test disabling alarm_configuration and auto_rollback_configuration - the tests now pass after rebasing the latest master branch
This commit is contained in:
parent
3eb4f16eaa
commit
7b672d4656
|
@ -57,6 +57,55 @@ func resourceAwsCodeDeployDeploymentGroup() *schema.Resource {
|
|||
Required: true,
|
||||
},
|
||||
|
||||
"alarm_configuration": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"alarms": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
MaxItems: 10,
|
||||
Optional: true,
|
||||
Set: schema.HashString,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"enabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"ignore_poll_alarm_failure": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"auto_rollback_configuration": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"enabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"events": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Set: schema.HashString,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"autoscaling_groups": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
|
@ -190,6 +239,14 @@ func resourceAwsCodeDeployDeploymentGroupCreate(d *schema.ResourceData, meta int
|
|||
input.TriggerConfigurations = triggerConfigs
|
||||
}
|
||||
|
||||
if attr, ok := d.GetOk("auto_rollback_configuration"); ok {
|
||||
input.AutoRollbackConfiguration = buildAutoRollbackConfig(attr.([]interface{}))
|
||||
}
|
||||
|
||||
if attr, ok := d.GetOk("alarm_configuration"); ok {
|
||||
input.AlarmConfiguration = buildAlarmConfig(attr.([]interface{}))
|
||||
}
|
||||
|
||||
// Retry to handle IAM role eventual consistency.
|
||||
var resp *codedeploy.CreateDeploymentGroupOutput
|
||||
var err error
|
||||
|
@ -262,6 +319,14 @@ func resourceAwsCodeDeployDeploymentGroupRead(d *schema.ResourceData, meta inter
|
|||
return err
|
||||
}
|
||||
|
||||
if err := d.Set("auto_rollback_configuration", autoRollbackConfigToMap(resp.DeploymentGroupInfo.AutoRollbackConfiguration)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.Set("alarm_configuration", alarmConfigToMap(resp.DeploymentGroupInfo.AlarmConfiguration)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -303,6 +368,16 @@ func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta int
|
|||
input.TriggerConfigurations = triggerConfigs
|
||||
}
|
||||
|
||||
if d.HasChange("auto_rollback_configuration") {
|
||||
_, n := d.GetChange("auto_rollback_configuration")
|
||||
input.AutoRollbackConfiguration = buildAutoRollbackConfig(n.([]interface{}))
|
||||
}
|
||||
|
||||
if d.HasChange("alarm_configuration") {
|
||||
_, n := d.GetChange("alarm_configuration")
|
||||
input.AlarmConfiguration = buildAlarmConfig(n.([]interface{}))
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id())
|
||||
// Retry to handle IAM role eventual consistency.
|
||||
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
|
||||
|
@ -416,6 +491,52 @@ func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig {
|
|||
return configs
|
||||
}
|
||||
|
||||
// buildAutoRollbackConfig converts a raw schema list containing a map[string]interface{}
|
||||
// into a single codedeploy.AutoRollbackConfiguration
|
||||
func buildAutoRollbackConfig(configured []interface{}) *codedeploy.AutoRollbackConfiguration {
|
||||
result := &codedeploy.AutoRollbackConfiguration{}
|
||||
|
||||
if len(configured) == 1 {
|
||||
config := configured[0].(map[string]interface{})
|
||||
result.Enabled = aws.Bool(config["enabled"].(bool))
|
||||
result.Events = expandStringSet(config["events"].(*schema.Set))
|
||||
} else { // delete the configuration
|
||||
result.Enabled = aws.Bool(false)
|
||||
result.Events = make([]*string, 0)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// buildAlarmConfig converts a raw schema list containing a map[string]interface{}
|
||||
// into a single codedeploy.AlarmConfiguration
|
||||
func buildAlarmConfig(configured []interface{}) *codedeploy.AlarmConfiguration {
|
||||
result := &codedeploy.AlarmConfiguration{}
|
||||
|
||||
if len(configured) == 1 {
|
||||
config := configured[0].(map[string]interface{})
|
||||
names := expandStringSet(config["alarms"].(*schema.Set))
|
||||
alarms := make([]*codedeploy.Alarm, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
alarm := &codedeploy.Alarm{
|
||||
Name: name,
|
||||
}
|
||||
alarms = append(alarms, alarm)
|
||||
}
|
||||
|
||||
result.Alarms = alarms
|
||||
result.Enabled = aws.Bool(config["enabled"].(bool))
|
||||
result.IgnorePollAlarmFailure = aws.Bool(config["ignore_poll_alarm_failure"].(bool))
|
||||
} else { // delete the configuration
|
||||
result.Alarms = make([]*codedeploy.Alarm, 0)
|
||||
result.Enabled = aws.Bool(false)
|
||||
result.IgnorePollAlarmFailure = aws.Bool(false)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
@ -467,6 +588,47 @@ func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interfac
|
|||
return result
|
||||
}
|
||||
|
||||
// autoRollbackConfigToMap converts a codedeploy.AutoRollbackConfiguration
|
||||
// into a []map[string]interface{} list containing a single item
|
||||
func autoRollbackConfigToMap(config *codedeploy.AutoRollbackConfiguration) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0, 1)
|
||||
|
||||
// only create configurations that are enabled or temporarily disabled (retaining events)
|
||||
// otherwise empty configurations will be created
|
||||
if config != nil && (*config.Enabled == true || len(config.Events) > 0) {
|
||||
item := make(map[string]interface{})
|
||||
item["enabled"] = *config.Enabled
|
||||
item["events"] = schema.NewSet(schema.HashString, flattenStringList(config.Events))
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// alarmConfigToMap converts a codedeploy.AlarmConfiguration
|
||||
// into a []map[string]interface{} list containing a single item
|
||||
func alarmConfigToMap(config *codedeploy.AlarmConfiguration) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0, 1)
|
||||
|
||||
// only create configurations that are enabled or temporarily disabled (retaining alarms)
|
||||
// otherwise empty configurations will be created
|
||||
if config != nil && (*config.Enabled == true || len(config.Alarms) > 0) {
|
||||
names := make([]*string, 0, len(config.Alarms))
|
||||
for _, alarm := range config.Alarms {
|
||||
names = append(names, alarm.Name)
|
||||
}
|
||||
|
||||
item := make(map[string]interface{})
|
||||
item["alarms"] = schema.NewSet(schema.HashString, flattenStringList(names))
|
||||
item["enabled"] = *config.Enabled
|
||||
item["ignore_poll_alarm_failure"] = *config.IgnorePollAlarmFailure
|
||||
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func resourceAwsCodeDeployTagFilterHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
@ -510,13 +672,14 @@ func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int {
|
|||
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,
|
||||
"DeploymentStart": true,
|
||||
"DeploymentStop": true,
|
||||
"DeploymentSuccess": true,
|
||||
"DeploymentFailure": true,
|
||||
"DeploymentRollback": true,
|
||||
"InstanceStart": true,
|
||||
"InstanceSuccess": true,
|
||||
"InstanceFailure": true,
|
||||
}
|
||||
|
||||
if !triggerEvents[value] {
|
||||
|
|
|
@ -46,6 +46,13 @@ func TestAccAWSCodeDeployDeploymentGroup_basic(t *testing.T) {
|
|||
"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.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "alarm_configuration.#", "0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "auto_rollback_configuration.#", "0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "trigger_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
|
@ -67,6 +74,13 @@ func TestAccAWSCodeDeployDeploymentGroup_basic(t *testing.T) {
|
|||
"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"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "alarm_configuration.#", "0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "auto_rollback_configuration.#", "0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo", "trigger_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -223,6 +237,334 @@ func TestAccAWSCodeDeployDeploymentGroup_triggerConfiguration_multiple(t *testin
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_autoRollbackConfiguration_create(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_delete(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_autoRollbackConfiguration_update(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_update(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.104943466", "DEPLOYMENT_STOP_ON_ALARM"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_autoRollbackConfiguration_delete(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_delete(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_autoRollbackConfiguration_disable(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_auto_rollback_configuration_disable(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.enabled", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "auto_rollback_configuration.0.events.135881253", "DEPLOYMENT_FAILURE"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_alarmConfiguration_create(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_delete(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "false"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_alarmConfiguration_update(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "false"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_update(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.1996459178", "bar"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_alarmConfiguration_delete(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "false"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_delete(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "0"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSCodeDeployDeploymentGroup_alarmConfiguration_disable(t *testing.T) {
|
||||
var group codedeploy.DeploymentGroupInfo
|
||||
|
||||
rName := acctest.RandString(5)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSCodeDeployDeploymentGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_create(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "false"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: test_config_alarm_configuration_disable(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSCodeDeployDeploymentGroupExists("aws_codedeploy_deployment_group.foo_group", &group),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.enabled", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.#", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.alarms.2356372769", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_codedeploy_deployment_group.foo_group", "alarm_configuration.0.ignore_poll_alarm_failure", "false"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateAWSCodeDeployTriggerEvent(t *testing.T) {
|
||||
cases := []struct {
|
||||
Value string
|
||||
|
@ -244,6 +586,10 @@ func TestValidateAWSCodeDeployTriggerEvent(t *testing.T) {
|
|||
Value: "DeploymentFailure",
|
||||
ErrCount: 0,
|
||||
},
|
||||
{
|
||||
Value: "DeploymentRollback",
|
||||
ErrCount: 0,
|
||||
},
|
||||
{
|
||||
Value: "InstanceStart",
|
||||
ErrCount: 0,
|
||||
|
@ -356,6 +702,144 @@ func TestTriggerConfigsToMap(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildAutoRollbackConfig(t *testing.T) {
|
||||
input := []interface{}{
|
||||
map[string]interface{}{
|
||||
"events": schema.NewSet(schema.HashString, []interface{}{
|
||||
"DEPLOYMENT_FAILURE",
|
||||
}),
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
|
||||
expected := &codedeploy.AutoRollbackConfiguration{
|
||||
Events: []*string{
|
||||
aws.String("DEPLOYMENT_FAILURE"),
|
||||
},
|
||||
Enabled: aws.Bool(true),
|
||||
}
|
||||
|
||||
actual := buildAutoRollbackConfig(input)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("buildAutoRollbackConfig output is not correct.\nGot:\n%#v\nExpected:\n%#v\n",
|
||||
actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoRollbackConfigToMap(t *testing.T) {
|
||||
input := &codedeploy.AutoRollbackConfiguration{
|
||||
Events: []*string{
|
||||
aws.String("DEPLOYMENT_FAILURE"),
|
||||
aws.String("DEPLOYMENT_STOP_ON_ALARM"),
|
||||
},
|
||||
Enabled: aws.Bool(false),
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"events": schema.NewSet(schema.HashString, []interface{}{
|
||||
"DEPLOYMENT_FAILURE",
|
||||
"DEPLOYMENT_STOP_ON_ALARM",
|
||||
}),
|
||||
"enabled": false,
|
||||
}
|
||||
|
||||
actual := autoRollbackConfigToMap(input)[0]
|
||||
|
||||
fatal := false
|
||||
|
||||
if actual["enabled"] != expected["enabled"] {
|
||||
fatal = true
|
||||
}
|
||||
|
||||
actualEvents := actual["events"].(*schema.Set)
|
||||
expectedEvents := expected["events"].(*schema.Set)
|
||||
if !actualEvents.Equal(expectedEvents) {
|
||||
fatal = true
|
||||
}
|
||||
|
||||
if fatal {
|
||||
t.Fatalf("autoRollbackConfigToMap output is not correct.\nGot:\n%#v\nExpected:\n%#v\n",
|
||||
actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAlarmConfig(t *testing.T) {
|
||||
input := []interface{}{
|
||||
map[string]interface{}{
|
||||
"alarms": schema.NewSet(schema.HashString, []interface{}{
|
||||
"foo-alarm",
|
||||
}),
|
||||
"enabled": true,
|
||||
"ignore_poll_alarm_failure": false,
|
||||
},
|
||||
}
|
||||
|
||||
expected := &codedeploy.AlarmConfiguration{
|
||||
Alarms: []*codedeploy.Alarm{
|
||||
{
|
||||
Name: aws.String("foo-alarm"),
|
||||
},
|
||||
},
|
||||
Enabled: aws.Bool(true),
|
||||
IgnorePollAlarmFailure: aws.Bool(false),
|
||||
}
|
||||
|
||||
actual := buildAlarmConfig(input)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("buildAlarmConfig output is not correct.\nGot:\n%#v\nExpected:\n%#v\n",
|
||||
actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlarmConfigToMap(t *testing.T) {
|
||||
input := &codedeploy.AlarmConfiguration{
|
||||
Alarms: []*codedeploy.Alarm{
|
||||
{
|
||||
Name: aws.String("bar-alarm"),
|
||||
},
|
||||
{
|
||||
Name: aws.String("foo-alarm"),
|
||||
},
|
||||
},
|
||||
Enabled: aws.Bool(false),
|
||||
IgnorePollAlarmFailure: aws.Bool(true),
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"alarms": schema.NewSet(schema.HashString, []interface{}{
|
||||
"bar-alarm",
|
||||
"foo-alarm",
|
||||
}),
|
||||
"enabled": false,
|
||||
"ignore_poll_alarm_failure": true,
|
||||
}
|
||||
|
||||
actual := alarmConfigToMap(input)[0]
|
||||
|
||||
fatal := false
|
||||
|
||||
if actual["enabled"] != expected["enabled"] {
|
||||
fatal = true
|
||||
}
|
||||
|
||||
if actual["ignore_poll_alarm_failure"] != expected["ignore_poll_alarm_failure"] {
|
||||
fatal = true
|
||||
}
|
||||
|
||||
actualAlarms := actual["alarms"].(*schema.Set)
|
||||
expectedAlarms := expected["alarms"].(*schema.Set)
|
||||
if !actualAlarms.Equal(expectedAlarms) {
|
||||
fatal = true
|
||||
}
|
||||
|
||||
if fatal {
|
||||
t.Fatalf("alarmConfigToMap output is not correct.\nGot:\n%#v\nExpected:\n%#v\n",
|
||||
actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTriggerEvents(group *codedeploy.DeploymentGroupInfo, triggerName string, expectedEvents []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
|
@ -839,3 +1323,130 @@ resource "aws_codedeploy_deployment_group" "foo_group" {
|
|||
}
|
||||
}`, baseCodeDeployConfig(rName), rName, rName, rName)
|
||||
}
|
||||
|
||||
func test_config_auto_rollback_configuration_create(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
auto_rollback_configuration {
|
||||
enabled = true
|
||||
events = ["DEPLOYMENT_FAILURE"]
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_auto_rollback_configuration_update(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
auto_rollback_configuration {
|
||||
enabled = true
|
||||
events = ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM"]
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_auto_rollback_configuration_delete(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_auto_rollback_configuration_disable(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
auto_rollback_configuration {
|
||||
enabled = false
|
||||
events = ["DEPLOYMENT_FAILURE"]
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_alarm_configuration_create(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
alarm_configuration {
|
||||
alarms = ["foo"]
|
||||
enabled = true
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_alarm_configuration_update(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
alarm_configuration {
|
||||
alarms = ["foo", "bar"]
|
||||
enabled = true
|
||||
ignore_poll_alarm_failure = true
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_alarm_configuration_delete(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
||||
func test_config_alarm_configuration_disable(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
|
||||
%s
|
||||
|
||||
resource "aws_codedeploy_deployment_group" "foo_group" {
|
||||
app_name = "${aws_codedeploy_app.foo_app.name}"
|
||||
deployment_group_name = "foo-group-%s"
|
||||
service_role_arn = "${aws_iam_role.foo_role.arn}"
|
||||
|
||||
alarm_configuration {
|
||||
alarms = ["foo"]
|
||||
enabled = false
|
||||
}
|
||||
}`, baseCodeDeployConfig(rName), rName)
|
||||
}
|
||||
|
|
|
@ -82,6 +82,16 @@ resource "aws_codedeploy_deployment_group" "foo" {
|
|||
trigger_name = "foo-trigger"
|
||||
trigger_target_arn = "foo-topic-arn"
|
||||
}
|
||||
|
||||
auto_rollback_configuration {
|
||||
enabled = true
|
||||
events = ["DEPLOYMENT_FAILURE"]
|
||||
}
|
||||
|
||||
alarm_configuration {
|
||||
alarms = ["my-alarm-name"]
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -97,6 +107,8 @@ The following arguments are supported:
|
|||
* `ec2_tag_filter` - (Optional) Tag filters associated with the group. See the AWS docs for details.
|
||||
* `on_premises_instance_tag_filter` - (Optional) On premise tag filters associated with the group. See the AWS docs for details.
|
||||
* `trigger_configuration` - (Optional) A Trigger Configuration block. Trigger Configurations are documented below.
|
||||
* `auto_rollback_configuration` - (Optional) The automatic rollback configuration associated with the deployment group, documented below.
|
||||
* `alarm_configuration` - (Optional) A list of alarms associated with the deployment group, documented below.
|
||||
|
||||
Both ec2_tag_filter and on_premises_tag_filter blocks support the following:
|
||||
|
||||
|
@ -110,6 +122,19 @@ Add triggers to a Deployment Group to receive notifications about events related
|
|||
* `trigger_name` - (Required) The name of the notification trigger.
|
||||
* `trigger_target_arn` - (Required) The ARN of the SNS topic through which notifications are sent.
|
||||
|
||||
You can configure a deployment group to automatically rollback when a deployment fails or when a monitoring threshold you specify is met. In this case, the last known good version of an application revision is deployed. Only one rollback configuration block is allowed.
|
||||
|
||||
* `enabled` - (Optional) Indicates whether a defined automatic rollback configuration is currently enabled for this Deployment Group. If you enable automatic rollback, you must specify at least one event type.
|
||||
* `events` - (Optional) The event type or types that trigger a rollback. Supported types are `DEPLOYMENT_FAILURE` and `DEPLOYMENT_STOP_ON_ALARM`.
|
||||
|
||||
You can configure a deployment to stop when a CloudWatch alarm detects that a metric has fallen below or exceeded a defined threshold. Only one alarm configuration block is allowed.
|
||||
|
||||
* `alarms` - (Optional) A list of alarms configured for the deployment group. A maximum of 10 alarms can be added to a deployment group.
|
||||
* `enabled` - (Optional) Indicates whether the alarm configuration is enabled. This option is useful when you want to temporarily deactivate alarm monitoring for a deployment group without having to add the same alarms again later.
|
||||
* `ignore_poll_alarm_failure` - (Optional) Indicates whether a deployment should continue if information about the current state of alarms cannot be retrieved from CloudWatch. The default value is `false`.
|
||||
* `true`: The deployment will proceed even if alarm status information can't be retrieved.
|
||||
* `false`: The deployment will stop if alarm status information can't be retrieved.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
|
Loading…
Reference in New Issue