github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "regexp" 8 "sort" 9 "time" 10 11 "github.com/hashicorp/terraform/helper/hashcode" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 15 "github.com/aws/aws-sdk-go/aws" 16 "github.com/aws/aws-sdk-go/aws/awserr" 17 "github.com/aws/aws-sdk-go/service/codedeploy" 18 ) 19 20 func resourceAwsCodeDeployDeploymentGroup() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsCodeDeployDeploymentGroupCreate, 23 Read: resourceAwsCodeDeployDeploymentGroupRead, 24 Update: resourceAwsCodeDeployDeploymentGroupUpdate, 25 Delete: resourceAwsCodeDeployDeploymentGroupDelete, 26 27 Schema: map[string]*schema.Schema{ 28 "app_name": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 32 value := v.(string) 33 if len(value) > 100 { 34 errors = append(errors, fmt.Errorf( 35 "%q cannot exceed 100 characters", k)) 36 } 37 return 38 }, 39 }, 40 41 "deployment_group_name": &schema.Schema{ 42 Type: schema.TypeString, 43 Required: true, 44 ForceNew: true, 45 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 46 value := v.(string) 47 if len(value) > 100 { 48 errors = append(errors, fmt.Errorf( 49 "%q cannot exceed 100 characters", k)) 50 } 51 return 52 }, 53 }, 54 55 "service_role_arn": &schema.Schema{ 56 Type: schema.TypeString, 57 Required: true, 58 }, 59 60 "autoscaling_groups": &schema.Schema{ 61 Type: schema.TypeSet, 62 Optional: true, 63 Elem: &schema.Schema{Type: schema.TypeString}, 64 Set: schema.HashString, 65 }, 66 67 "deployment_config_name": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 Default: "CodeDeployDefault.OneAtATime", 71 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 72 value := v.(string) 73 if len(value) > 100 { 74 errors = append(errors, fmt.Errorf( 75 "%q cannot exceed 100 characters", k)) 76 } 77 return 78 }, 79 }, 80 81 "ec2_tag_filter": &schema.Schema{ 82 Type: schema.TypeSet, 83 Optional: true, 84 Elem: &schema.Resource{ 85 Schema: map[string]*schema.Schema{ 86 "key": &schema.Schema{ 87 Type: schema.TypeString, 88 Optional: true, 89 }, 90 91 "type": &schema.Schema{ 92 Type: schema.TypeString, 93 Optional: true, 94 ValidateFunc: validateTagFilters, 95 }, 96 97 "value": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 }, 101 }, 102 }, 103 Set: resourceAwsCodeDeployTagFilterHash, 104 }, 105 106 "on_premises_instance_tag_filter": &schema.Schema{ 107 Type: schema.TypeSet, 108 Optional: true, 109 Elem: &schema.Resource{ 110 Schema: map[string]*schema.Schema{ 111 "key": &schema.Schema{ 112 Type: schema.TypeString, 113 Optional: true, 114 }, 115 116 "type": &schema.Schema{ 117 Type: schema.TypeString, 118 Optional: true, 119 ValidateFunc: validateTagFilters, 120 }, 121 122 "value": &schema.Schema{ 123 Type: schema.TypeString, 124 Optional: true, 125 }, 126 }, 127 }, 128 Set: resourceAwsCodeDeployTagFilterHash, 129 }, 130 131 "trigger_configuration": &schema.Schema{ 132 Type: schema.TypeSet, 133 Optional: true, 134 Elem: &schema.Resource{ 135 Schema: map[string]*schema.Schema{ 136 "trigger_events": &schema.Schema{ 137 Type: schema.TypeSet, 138 Required: true, 139 Set: schema.HashString, 140 Elem: &schema.Schema{ 141 Type: schema.TypeString, 142 ValidateFunc: validateTriggerEvent, 143 }, 144 }, 145 146 "trigger_name": &schema.Schema{ 147 Type: schema.TypeString, 148 Required: true, 149 }, 150 151 "trigger_target_arn": &schema.Schema{ 152 Type: schema.TypeString, 153 Required: true, 154 }, 155 }, 156 }, 157 Set: resourceAwsCodeDeployTriggerConfigHash, 158 }, 159 }, 160 } 161 } 162 163 func resourceAwsCodeDeployDeploymentGroupCreate(d *schema.ResourceData, meta interface{}) error { 164 conn := meta.(*AWSClient).codedeployconn 165 166 application := d.Get("app_name").(string) 167 deploymentGroup := d.Get("deployment_group_name").(string) 168 169 input := codedeploy.CreateDeploymentGroupInput{ 170 ApplicationName: aws.String(application), 171 DeploymentGroupName: aws.String(deploymentGroup), 172 ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)), 173 } 174 if attr, ok := d.GetOk("deployment_config_name"); ok { 175 input.DeploymentConfigName = aws.String(attr.(string)) 176 } 177 if attr, ok := d.GetOk("autoscaling_groups"); ok { 178 input.AutoScalingGroups = expandStringList(attr.(*schema.Set).List()) 179 } 180 if attr, ok := d.GetOk("on_premises_instance_tag_filter"); ok { 181 onPremFilters := buildOnPremTagFilters(attr.(*schema.Set).List()) 182 input.OnPremisesInstanceTagFilters = onPremFilters 183 } 184 if attr, ok := d.GetOk("ec2_tag_filter"); ok { 185 ec2TagFilters := buildEC2TagFilters(attr.(*schema.Set).List()) 186 input.Ec2TagFilters = ec2TagFilters 187 } 188 if attr, ok := d.GetOk("trigger_configuration"); ok { 189 triggerConfigs := buildTriggerConfigs(attr.(*schema.Set).List()) 190 input.TriggerConfigurations = triggerConfigs 191 } 192 193 // Retry to handle IAM role eventual consistency. 194 var resp *codedeploy.CreateDeploymentGroupOutput 195 var err error 196 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 197 resp, err = conn.CreateDeploymentGroup(&input) 198 if err != nil { 199 retry := false 200 codedeployErr, ok := err.(awserr.Error) 201 if !ok { 202 return resource.NonRetryableError(err) 203 } 204 if codedeployErr.Code() == "InvalidRoleException" { 205 retry = true 206 } 207 if codedeployErr.Code() == "InvalidTriggerConfigException" { 208 r := regexp.MustCompile("^Topic ARN .+ is not valid$") 209 if r.MatchString(codedeployErr.Message()) { 210 retry = true 211 } 212 } 213 if retry { 214 log.Printf("[DEBUG] Trying to create deployment group again: %q", 215 codedeployErr.Message()) 216 return resource.RetryableError(err) 217 } 218 219 return resource.NonRetryableError(err) 220 } 221 return nil 222 }) 223 if err != nil { 224 return err 225 } 226 227 d.SetId(*resp.DeploymentGroupId) 228 229 return resourceAwsCodeDeployDeploymentGroupRead(d, meta) 230 } 231 232 func resourceAwsCodeDeployDeploymentGroupRead(d *schema.ResourceData, meta interface{}) error { 233 conn := meta.(*AWSClient).codedeployconn 234 235 log.Printf("[DEBUG] Reading CodeDeploy DeploymentGroup %s", d.Id()) 236 resp, err := conn.GetDeploymentGroup(&codedeploy.GetDeploymentGroupInput{ 237 ApplicationName: aws.String(d.Get("app_name").(string)), 238 DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 239 }) 240 if err != nil { 241 return err 242 } 243 244 d.Set("app_name", *resp.DeploymentGroupInfo.ApplicationName) 245 d.Set("autoscaling_groups", resp.DeploymentGroupInfo.AutoScalingGroups) 246 d.Set("deployment_config_name", *resp.DeploymentGroupInfo.DeploymentConfigName) 247 d.Set("deployment_group_name", *resp.DeploymentGroupInfo.DeploymentGroupName) 248 d.Set("service_role_arn", *resp.DeploymentGroupInfo.ServiceRoleArn) 249 if err := d.Set("ec2_tag_filter", ec2TagFiltersToMap(resp.DeploymentGroupInfo.Ec2TagFilters)); err != nil { 250 return err 251 } 252 if err := d.Set("on_premises_instance_tag_filter", onPremisesTagFiltersToMap(resp.DeploymentGroupInfo.OnPremisesInstanceTagFilters)); err != nil { 253 return err 254 } 255 if err := d.Set("trigger_configuration", triggerConfigsToMap(resp.DeploymentGroupInfo.TriggerConfigurations)); err != nil { 256 return err 257 } 258 259 return nil 260 } 261 262 func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta interface{}) error { 263 conn := meta.(*AWSClient).codedeployconn 264 265 input := codedeploy.UpdateDeploymentGroupInput{ 266 ApplicationName: aws.String(d.Get("app_name").(string)), 267 CurrentDeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 268 } 269 270 if d.HasChange("autoscaling_groups") { 271 _, n := d.GetChange("autoscaling_groups") 272 input.AutoScalingGroups = expandStringList(n.(*schema.Set).List()) 273 } 274 if d.HasChange("deployment_config_name") { 275 _, n := d.GetChange("deployment_config_name") 276 input.DeploymentConfigName = aws.String(n.(string)) 277 } 278 if d.HasChange("deployment_group_name") { 279 _, n := d.GetChange("deployment_group_name") 280 input.NewDeploymentGroupName = aws.String(n.(string)) 281 } 282 283 // TagFilters aren't like tags. They don't append. They simply replace. 284 if d.HasChange("on_premises_instance_tag_filter") { 285 _, n := d.GetChange("on_premises_instance_tag_filter") 286 onPremFilters := buildOnPremTagFilters(n.(*schema.Set).List()) 287 input.OnPremisesInstanceTagFilters = onPremFilters 288 } 289 if d.HasChange("ec2_tag_filter") { 290 _, n := d.GetChange("ec2_tag_filter") 291 ec2Filters := buildEC2TagFilters(n.(*schema.Set).List()) 292 input.Ec2TagFilters = ec2Filters 293 } 294 if d.HasChange("trigger_configuration") { 295 _, n := d.GetChange("trigger_configuration") 296 triggerConfigs := buildTriggerConfigs(n.(*schema.Set).List()) 297 input.TriggerConfigurations = triggerConfigs 298 } 299 300 log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id()) 301 // Retry to handle IAM role eventual consistency. 302 err := resource.Retry(5*time.Minute, func() *resource.RetryError { 303 _, err := conn.UpdateDeploymentGroup(&input) 304 if err != nil { 305 retry := false 306 codedeployErr, ok := err.(awserr.Error) 307 if !ok { 308 return resource.NonRetryableError(err) 309 } 310 if codedeployErr.Code() == "InvalidRoleException" { 311 retry = true 312 } 313 if codedeployErr.Code() == "InvalidTriggerConfigException" { 314 r := regexp.MustCompile("^Topic ARN .+ is not valid$") 315 if r.MatchString(codedeployErr.Message()) { 316 retry = true 317 } 318 } 319 if retry { 320 log.Printf("[DEBUG] Retrying Code Deployment Group Update: %q", 321 codedeployErr.Message()) 322 return resource.RetryableError(err) 323 } 324 325 return resource.NonRetryableError(err) 326 } 327 return nil 328 }) 329 330 if err != nil { 331 return err 332 } 333 334 return resourceAwsCodeDeployDeploymentGroupRead(d, meta) 335 } 336 337 func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error { 338 conn := meta.(*AWSClient).codedeployconn 339 340 log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id()) 341 _, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{ 342 ApplicationName: aws.String(d.Get("app_name").(string)), 343 DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 344 }) 345 if err != nil { 346 return err 347 } 348 349 d.SetId("") 350 351 return nil 352 } 353 354 // buildOnPremTagFilters converts raw schema lists into a list of 355 // codedeploy.TagFilters. 356 func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter { 357 filters := make([]*codedeploy.TagFilter, 0) 358 for _, raw := range configured { 359 var filter codedeploy.TagFilter 360 m := raw.(map[string]interface{}) 361 362 if v, ok := m["key"]; ok { 363 filter.Key = aws.String(v.(string)) 364 } 365 if v, ok := m["type"]; ok { 366 filter.Type = aws.String(v.(string)) 367 } 368 if v, ok := m["value"]; ok { 369 filter.Value = aws.String(v.(string)) 370 } 371 372 filters = append(filters, &filter) 373 } 374 375 return filters 376 } 377 378 // buildEC2TagFilters converts raw schema lists into a list of 379 // codedeploy.EC2TagFilters. 380 func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter { 381 filters := make([]*codedeploy.EC2TagFilter, 0) 382 for _, raw := range configured { 383 var filter codedeploy.EC2TagFilter 384 m := raw.(map[string]interface{}) 385 386 filter.Key = aws.String(m["key"].(string)) 387 filter.Type = aws.String(m["type"].(string)) 388 filter.Value = aws.String(m["value"].(string)) 389 390 filters = append(filters, &filter) 391 } 392 393 return filters 394 } 395 396 // buildTriggerConfigs converts a raw schema list into a list of 397 // codedeploy.TriggerConfig. 398 func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig { 399 configs := make([]*codedeploy.TriggerConfig, 0, len(configured)) 400 for _, raw := range configured { 401 var config codedeploy.TriggerConfig 402 m := raw.(map[string]interface{}) 403 404 config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set)) 405 config.TriggerName = aws.String(m["trigger_name"].(string)) 406 config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string)) 407 408 configs = append(configs, &config) 409 } 410 return configs 411 } 412 413 // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string. 414 func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string { 415 result := make([]map[string]string, 0, len(list)) 416 for _, tf := range list { 417 l := make(map[string]string) 418 if tf.Key != nil && *tf.Key != "" { 419 l["key"] = *tf.Key 420 } 421 if tf.Value != nil && *tf.Value != "" { 422 l["value"] = *tf.Value 423 } 424 if tf.Type != nil && *tf.Type != "" { 425 l["type"] = *tf.Type 426 } 427 result = append(result, l) 428 } 429 return result 430 } 431 432 // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string. 433 func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string { 434 result := make([]map[string]string, 0, len(list)) 435 for _, tf := range list { 436 l := make(map[string]string) 437 if tf.Key != nil && *tf.Key != "" { 438 l["key"] = *tf.Key 439 } 440 if tf.Value != nil && *tf.Value != "" { 441 l["value"] = *tf.Value 442 } 443 if tf.Type != nil && *tf.Type != "" { 444 l["type"] = *tf.Type 445 } 446 result = append(result, l) 447 } 448 return result 449 } 450 451 // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{} 452 func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} { 453 result := make([]map[string]interface{}, 0, len(list)) 454 for _, tc := range list { 455 item := make(map[string]interface{}) 456 item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents)) 457 item["trigger_name"] = *tc.TriggerName 458 item["trigger_target_arn"] = *tc.TriggerTargetArn 459 result = append(result, item) 460 } 461 return result 462 } 463 464 func resourceAwsCodeDeployTagFilterHash(v interface{}) int { 465 var buf bytes.Buffer 466 m := v.(map[string]interface{}) 467 468 // Nothing's actually required in tag filters, so we must check the 469 // presence of all values before attempting a hash. 470 if v, ok := m["key"]; ok { 471 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 472 } 473 if v, ok := m["type"]; ok { 474 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 475 } 476 if v, ok := m["value"]; ok { 477 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 478 } 479 480 return hashcode.String(buf.String()) 481 } 482 483 func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int { 484 var buf bytes.Buffer 485 m := v.(map[string]interface{}) 486 buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string))) 487 buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string))) 488 489 if triggerEvents, ok := m["trigger_events"]; ok { 490 names := triggerEvents.(*schema.Set).List() 491 strings := make([]string, len(names)) 492 for i, raw := range names { 493 strings[i] = raw.(string) 494 } 495 sort.Strings(strings) 496 497 for _, s := range strings { 498 buf.WriteString(fmt.Sprintf("%s-", s)) 499 } 500 } 501 return hashcode.String(buf.String()) 502 } 503 504 func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) { 505 value := v.(string) 506 triggerEvents := map[string]bool{ 507 "DeploymentStart": true, 508 "DeploymentStop": true, 509 "DeploymentSuccess": true, 510 "DeploymentFailure": true, 511 "InstanceStart": true, 512 "InstanceSuccess": true, 513 "InstanceFailure": true, 514 } 515 516 if !triggerEvents[value] { 517 errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value)) 518 } 519 return 520 }