github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/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 _, err := conn.UpdateDeploymentGroup(&input) 302 if err != nil { 303 return err 304 } 305 306 return resourceAwsCodeDeployDeploymentGroupRead(d, meta) 307 } 308 309 func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error { 310 conn := meta.(*AWSClient).codedeployconn 311 312 log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id()) 313 _, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{ 314 ApplicationName: aws.String(d.Get("app_name").(string)), 315 DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)), 316 }) 317 if err != nil { 318 return err 319 } 320 321 d.SetId("") 322 323 return nil 324 } 325 326 // buildOnPremTagFilters converts raw schema lists into a list of 327 // codedeploy.TagFilters. 328 func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter { 329 filters := make([]*codedeploy.TagFilter, 0) 330 for _, raw := range configured { 331 var filter codedeploy.TagFilter 332 m := raw.(map[string]interface{}) 333 334 if v, ok := m["key"]; ok { 335 filter.Key = aws.String(v.(string)) 336 } 337 if v, ok := m["type"]; ok { 338 filter.Type = aws.String(v.(string)) 339 } 340 if v, ok := m["value"]; ok { 341 filter.Value = aws.String(v.(string)) 342 } 343 344 filters = append(filters, &filter) 345 } 346 347 return filters 348 } 349 350 // buildEC2TagFilters converts raw schema lists into a list of 351 // codedeploy.EC2TagFilters. 352 func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter { 353 filters := make([]*codedeploy.EC2TagFilter, 0) 354 for _, raw := range configured { 355 var filter codedeploy.EC2TagFilter 356 m := raw.(map[string]interface{}) 357 358 filter.Key = aws.String(m["key"].(string)) 359 filter.Type = aws.String(m["type"].(string)) 360 filter.Value = aws.String(m["value"].(string)) 361 362 filters = append(filters, &filter) 363 } 364 365 return filters 366 } 367 368 // buildTriggerConfigs converts a raw schema list into a list of 369 // codedeploy.TriggerConfig. 370 func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig { 371 configs := make([]*codedeploy.TriggerConfig, 0, len(configured)) 372 for _, raw := range configured { 373 var config codedeploy.TriggerConfig 374 m := raw.(map[string]interface{}) 375 376 config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set)) 377 config.TriggerName = aws.String(m["trigger_name"].(string)) 378 config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string)) 379 380 configs = append(configs, &config) 381 } 382 return configs 383 } 384 385 // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string. 386 func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string { 387 result := make([]map[string]string, 0, len(list)) 388 for _, tf := range list { 389 l := make(map[string]string) 390 if *tf.Key != "" { 391 l["key"] = *tf.Key 392 } 393 if *tf.Value != "" { 394 l["value"] = *tf.Value 395 } 396 if *tf.Type != "" { 397 l["type"] = *tf.Type 398 } 399 result = append(result, l) 400 } 401 return result 402 } 403 404 // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string. 405 func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string { 406 result := make([]map[string]string, 0, len(list)) 407 for _, tf := range list { 408 l := make(map[string]string) 409 if tf.Key != nil && *tf.Key != "" { 410 l["key"] = *tf.Key 411 } 412 if tf.Value != nil && *tf.Value != "" { 413 l["value"] = *tf.Value 414 } 415 if tf.Type != nil && *tf.Type != "" { 416 l["type"] = *tf.Type 417 } 418 result = append(result, l) 419 } 420 return result 421 } 422 423 // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{} 424 func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} { 425 result := make([]map[string]interface{}, 0, len(list)) 426 for _, tc := range list { 427 item := make(map[string]interface{}) 428 item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents)) 429 item["trigger_name"] = *tc.TriggerName 430 item["trigger_target_arn"] = *tc.TriggerTargetArn 431 result = append(result, item) 432 } 433 return result 434 } 435 436 func resourceAwsCodeDeployTagFilterHash(v interface{}) int { 437 var buf bytes.Buffer 438 m := v.(map[string]interface{}) 439 440 // Nothing's actually required in tag filters, so we must check the 441 // presence of all values before attempting a hash. 442 if v, ok := m["key"]; ok { 443 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 444 } 445 if v, ok := m["type"]; ok { 446 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 447 } 448 if v, ok := m["value"]; ok { 449 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 450 } 451 452 return hashcode.String(buf.String()) 453 } 454 455 func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int { 456 var buf bytes.Buffer 457 m := v.(map[string]interface{}) 458 buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string))) 459 buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string))) 460 461 if triggerEvents, ok := m["trigger_events"]; ok { 462 names := triggerEvents.(*schema.Set).List() 463 strings := make([]string, len(names)) 464 for i, raw := range names { 465 strings[i] = raw.(string) 466 } 467 sort.Strings(strings) 468 469 for _, s := range strings { 470 buf.WriteString(fmt.Sprintf("%s-", s)) 471 } 472 } 473 return hashcode.String(buf.String()) 474 } 475 476 func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) { 477 value := v.(string) 478 triggerEvents := map[string]bool{ 479 "DeploymentStart": true, 480 "DeploymentStop": true, 481 "DeploymentSuccess": true, 482 "DeploymentFailure": true, 483 "InstanceStart": true, 484 "InstanceSuccess": true, 485 "InstanceFailure": true, 486 } 487 488 if !triggerEvents[value] { 489 errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value)) 490 } 491 return 492 }