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