github.com/opsidian/terraform@v0.7.8-0.20161104123224-27c39cdfba5b/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 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DeploymentGroupDoesNotExistException" { 242 log.Printf("[INFO] CodeDeployment DeploymentGroup %s not found", d.Get("deployment_group_name").(string)) 243 d.SetId("") 244 return nil 245 } 246 247 return err 248 } 249 250 d.Set("app_name", resp.DeploymentGroupInfo.ApplicationName) 251 d.Set("autoscaling_groups", resp.DeploymentGroupInfo.AutoScalingGroups) 252 d.Set("deployment_config_name", resp.DeploymentGroupInfo.DeploymentConfigName) 253 d.Set("deployment_group_name", resp.DeploymentGroupInfo.DeploymentGroupName) 254 d.Set("service_role_arn", resp.DeploymentGroupInfo.ServiceRoleArn) 255 if err := d.Set("ec2_tag_filter", ec2TagFiltersToMap(resp.DeploymentGroupInfo.Ec2TagFilters)); err != nil { 256 return err 257 } 258 if err := d.Set("on_premises_instance_tag_filter", onPremisesTagFiltersToMap(resp.DeploymentGroupInfo.OnPremisesInstanceTagFilters)); err != nil { 259 return err 260 } 261 if err := d.Set("trigger_configuration", triggerConfigsToMap(resp.DeploymentGroupInfo.TriggerConfigurations)); err != nil { 262 return err 263 } 264 265 return nil 266 } 267 268 func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta interface{}) error { 269 conn := meta.(*AWSClient).codedeployconn 270 271 input := codedeploy.UpdateDeploymentGroupInput{ 272 ApplicationName: aws.String(d.Get("app_name").(string)), 273 CurrentDeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 274 ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)), 275 } 276 277 if d.HasChange("autoscaling_groups") { 278 _, n := d.GetChange("autoscaling_groups") 279 input.AutoScalingGroups = expandStringList(n.(*schema.Set).List()) 280 } 281 if d.HasChange("deployment_config_name") { 282 _, n := d.GetChange("deployment_config_name") 283 input.DeploymentConfigName = aws.String(n.(string)) 284 } 285 if d.HasChange("deployment_group_name") { 286 _, n := d.GetChange("deployment_group_name") 287 input.NewDeploymentGroupName = aws.String(n.(string)) 288 } 289 290 // TagFilters aren't like tags. They don't append. They simply replace. 291 if d.HasChange("on_premises_instance_tag_filter") { 292 _, n := d.GetChange("on_premises_instance_tag_filter") 293 onPremFilters := buildOnPremTagFilters(n.(*schema.Set).List()) 294 input.OnPremisesInstanceTagFilters = onPremFilters 295 } 296 if d.HasChange("ec2_tag_filter") { 297 _, n := d.GetChange("ec2_tag_filter") 298 ec2Filters := buildEC2TagFilters(n.(*schema.Set).List()) 299 input.Ec2TagFilters = ec2Filters 300 } 301 if d.HasChange("trigger_configuration") { 302 _, n := d.GetChange("trigger_configuration") 303 triggerConfigs := buildTriggerConfigs(n.(*schema.Set).List()) 304 input.TriggerConfigurations = triggerConfigs 305 } 306 307 log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id()) 308 // Retry to handle IAM role eventual consistency. 309 err := resource.Retry(5*time.Minute, func() *resource.RetryError { 310 _, err := conn.UpdateDeploymentGroup(&input) 311 if err != nil { 312 retry := false 313 codedeployErr, ok := err.(awserr.Error) 314 if !ok { 315 return resource.NonRetryableError(err) 316 } 317 if codedeployErr.Code() == "InvalidRoleException" { 318 retry = true 319 } 320 if codedeployErr.Code() == "InvalidTriggerConfigException" { 321 r := regexp.MustCompile("^Topic ARN .+ is not valid$") 322 if r.MatchString(codedeployErr.Message()) { 323 retry = true 324 } 325 } 326 if retry { 327 log.Printf("[DEBUG] Retrying Code Deployment Group Update: %q", 328 codedeployErr.Message()) 329 return resource.RetryableError(err) 330 } 331 332 return resource.NonRetryableError(err) 333 } 334 return nil 335 }) 336 337 if err != nil { 338 return err 339 } 340 341 return resourceAwsCodeDeployDeploymentGroupRead(d, meta) 342 } 343 344 func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error { 345 conn := meta.(*AWSClient).codedeployconn 346 347 log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id()) 348 _, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{ 349 ApplicationName: aws.String(d.Get("app_name").(string)), 350 DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 351 }) 352 if err != nil { 353 return err 354 } 355 356 d.SetId("") 357 358 return nil 359 } 360 361 // buildOnPremTagFilters converts raw schema lists into a list of 362 // codedeploy.TagFilters. 363 func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter { 364 filters := make([]*codedeploy.TagFilter, 0) 365 for _, raw := range configured { 366 var filter codedeploy.TagFilter 367 m := raw.(map[string]interface{}) 368 369 if v, ok := m["key"]; ok { 370 filter.Key = aws.String(v.(string)) 371 } 372 if v, ok := m["type"]; ok { 373 filter.Type = aws.String(v.(string)) 374 } 375 if v, ok := m["value"]; ok { 376 filter.Value = aws.String(v.(string)) 377 } 378 379 filters = append(filters, &filter) 380 } 381 382 return filters 383 } 384 385 // buildEC2TagFilters converts raw schema lists into a list of 386 // codedeploy.EC2TagFilters. 387 func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter { 388 filters := make([]*codedeploy.EC2TagFilter, 0) 389 for _, raw := range configured { 390 var filter codedeploy.EC2TagFilter 391 m := raw.(map[string]interface{}) 392 393 filter.Key = aws.String(m["key"].(string)) 394 filter.Type = aws.String(m["type"].(string)) 395 filter.Value = aws.String(m["value"].(string)) 396 397 filters = append(filters, &filter) 398 } 399 400 return filters 401 } 402 403 // buildTriggerConfigs converts a raw schema list into a list of 404 // codedeploy.TriggerConfig. 405 func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig { 406 configs := make([]*codedeploy.TriggerConfig, 0, len(configured)) 407 for _, raw := range configured { 408 var config codedeploy.TriggerConfig 409 m := raw.(map[string]interface{}) 410 411 config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set)) 412 config.TriggerName = aws.String(m["trigger_name"].(string)) 413 config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string)) 414 415 configs = append(configs, &config) 416 } 417 return configs 418 } 419 420 // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string. 421 func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string { 422 result := make([]map[string]string, 0, len(list)) 423 for _, tf := range list { 424 l := make(map[string]string) 425 if tf.Key != nil && *tf.Key != "" { 426 l["key"] = *tf.Key 427 } 428 if tf.Value != nil && *tf.Value != "" { 429 l["value"] = *tf.Value 430 } 431 if tf.Type != nil && *tf.Type != "" { 432 l["type"] = *tf.Type 433 } 434 result = append(result, l) 435 } 436 return result 437 } 438 439 // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string. 440 func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string { 441 result := make([]map[string]string, 0, len(list)) 442 for _, tf := range list { 443 l := make(map[string]string) 444 if tf.Key != nil && *tf.Key != "" { 445 l["key"] = *tf.Key 446 } 447 if tf.Value != nil && *tf.Value != "" { 448 l["value"] = *tf.Value 449 } 450 if tf.Type != nil && *tf.Type != "" { 451 l["type"] = *tf.Type 452 } 453 result = append(result, l) 454 } 455 return result 456 } 457 458 // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{} 459 func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} { 460 result := make([]map[string]interface{}, 0, len(list)) 461 for _, tc := range list { 462 item := make(map[string]interface{}) 463 item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents)) 464 item["trigger_name"] = *tc.TriggerName 465 item["trigger_target_arn"] = *tc.TriggerTargetArn 466 result = append(result, item) 467 } 468 return result 469 } 470 471 func resourceAwsCodeDeployTagFilterHash(v interface{}) int { 472 var buf bytes.Buffer 473 m := v.(map[string]interface{}) 474 475 // Nothing's actually required in tag filters, so we must check the 476 // presence of all values before attempting a hash. 477 if v, ok := m["key"]; ok { 478 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 479 } 480 if v, ok := m["type"]; ok { 481 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 482 } 483 if v, ok := m["value"]; ok { 484 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 485 } 486 487 return hashcode.String(buf.String()) 488 } 489 490 func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int { 491 var buf bytes.Buffer 492 m := v.(map[string]interface{}) 493 buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string))) 494 buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string))) 495 496 if triggerEvents, ok := m["trigger_events"]; ok { 497 names := triggerEvents.(*schema.Set).List() 498 strings := make([]string, len(names)) 499 for i, raw := range names { 500 strings[i] = raw.(string) 501 } 502 sort.Strings(strings) 503 504 for _, s := range strings { 505 buf.WriteString(fmt.Sprintf("%s-", s)) 506 } 507 } 508 return hashcode.String(buf.String()) 509 } 510 511 func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) { 512 value := v.(string) 513 triggerEvents := map[string]bool{ 514 "DeploymentStart": true, 515 "DeploymentStop": true, 516 "DeploymentSuccess": true, 517 "DeploymentFailure": true, 518 "InstanceStart": true, 519 "InstanceSuccess": true, 520 "InstanceFailure": true, 521 } 522 523 if !triggerEvents[value] { 524 errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value)) 525 } 526 return 527 }