github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "sort" 8 "strings" 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/service/ec2" 17 "github.com/aws/aws-sdk-go/service/elasticbeanstalk" 18 ) 19 20 func resourceAwsElasticBeanstalkOptionSetting() *schema.Resource { 21 return &schema.Resource{ 22 Schema: map[string]*schema.Schema{ 23 "namespace": &schema.Schema{ 24 Type: schema.TypeString, 25 Required: true, 26 }, 27 "name": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 }, 31 "value": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 }, 35 "resource": &schema.Schema{ 36 Type: schema.TypeString, 37 Optional: true, 38 }, 39 }, 40 } 41 } 42 43 func resourceAwsElasticBeanstalkEnvironment() *schema.Resource { 44 return &schema.Resource{ 45 Create: resourceAwsElasticBeanstalkEnvironmentCreate, 46 Read: resourceAwsElasticBeanstalkEnvironmentRead, 47 Update: resourceAwsElasticBeanstalkEnvironmentUpdate, 48 Delete: resourceAwsElasticBeanstalkEnvironmentDelete, 49 Importer: &schema.ResourceImporter{ 50 State: schema.ImportStatePassthrough, 51 }, 52 53 SchemaVersion: 1, 54 MigrateState: resourceAwsElasticBeanstalkEnvironmentMigrateState, 55 56 Schema: map[string]*schema.Schema{ 57 "name": &schema.Schema{ 58 Type: schema.TypeString, 59 Required: true, 60 ForceNew: true, 61 }, 62 "application": &schema.Schema{ 63 Type: schema.TypeString, 64 Required: true, 65 }, 66 "description": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 }, 70 "cname": &schema.Schema{ 71 Type: schema.TypeString, 72 Computed: true, 73 }, 74 "cname_prefix": &schema.Schema{ 75 Type: schema.TypeString, 76 Computed: true, 77 Optional: true, 78 ForceNew: true, 79 }, 80 "tier": &schema.Schema{ 81 Type: schema.TypeString, 82 Optional: true, 83 Default: "WebServer", 84 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 85 value := v.(string) 86 switch value { 87 case 88 "Worker", 89 "WebServer": 90 return 91 } 92 errors = append(errors, fmt.Errorf("%s is not a valid tier. Valid options are WebServer or Worker", value)) 93 return 94 }, 95 ForceNew: true, 96 }, 97 "setting": &schema.Schema{ 98 Type: schema.TypeSet, 99 Optional: true, 100 Elem: resourceAwsElasticBeanstalkOptionSetting(), 101 Set: optionSettingValueHash, 102 }, 103 "all_settings": &schema.Schema{ 104 Type: schema.TypeSet, 105 Computed: true, 106 Elem: resourceAwsElasticBeanstalkOptionSetting(), 107 Set: optionSettingValueHash, 108 }, 109 "solution_stack_name": &schema.Schema{ 110 Type: schema.TypeString, 111 Optional: true, 112 Computed: true, 113 ConflictsWith: []string{"template_name"}, 114 }, 115 "template_name": &schema.Schema{ 116 Type: schema.TypeString, 117 Optional: true, 118 }, 119 "wait_for_ready_timeout": &schema.Schema{ 120 Type: schema.TypeString, 121 Optional: true, 122 Default: "10m", 123 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 124 value := v.(string) 125 duration, err := time.ParseDuration(value) 126 if err != nil { 127 errors = append(errors, fmt.Errorf( 128 "%q cannot be parsed as a duration: %s", k, err)) 129 } 130 if duration < 0 { 131 errors = append(errors, fmt.Errorf( 132 "%q must be greater than zero", k)) 133 } 134 return 135 }, 136 }, 137 "poll_interval": &schema.Schema{ 138 Type: schema.TypeString, 139 Optional: true, 140 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 141 value := v.(string) 142 duration, err := time.ParseDuration(value) 143 if err != nil { 144 errors = append(errors, fmt.Errorf( 145 "%q cannot be parsed as a duration: %s", k, err)) 146 } 147 if duration < 10*time.Second || duration > 60*time.Second { 148 errors = append(errors, fmt.Errorf( 149 "%q must be between 10s and 180s", k)) 150 } 151 return 152 }, 153 }, 154 "autoscaling_groups": &schema.Schema{ 155 Type: schema.TypeList, 156 Computed: true, 157 Elem: &schema.Schema{Type: schema.TypeString}, 158 }, 159 "instances": &schema.Schema{ 160 Type: schema.TypeList, 161 Computed: true, 162 Elem: &schema.Schema{Type: schema.TypeString}, 163 }, 164 "launch_configurations": &schema.Schema{ 165 Type: schema.TypeList, 166 Computed: true, 167 Elem: &schema.Schema{Type: schema.TypeString}, 168 }, 169 "load_balancers": &schema.Schema{ 170 Type: schema.TypeList, 171 Computed: true, 172 Elem: &schema.Schema{Type: schema.TypeString}, 173 }, 174 "queues": &schema.Schema{ 175 Type: schema.TypeList, 176 Computed: true, 177 Elem: &schema.Schema{Type: schema.TypeString}, 178 }, 179 "triggers": &schema.Schema{ 180 Type: schema.TypeList, 181 Computed: true, 182 Elem: &schema.Schema{Type: schema.TypeString}, 183 }, 184 185 "tags": tagsSchema(), 186 }, 187 } 188 } 189 190 func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta interface{}) error { 191 conn := meta.(*AWSClient).elasticbeanstalkconn 192 193 // Get values from config 194 name := d.Get("name").(string) 195 cnamePrefix := d.Get("cname_prefix").(string) 196 tier := d.Get("tier").(string) 197 app := d.Get("application").(string) 198 desc := d.Get("description").(string) 199 settings := d.Get("setting").(*schema.Set) 200 solutionStack := d.Get("solution_stack_name").(string) 201 templateName := d.Get("template_name").(string) 202 203 // TODO set tags 204 // Note: at time of writing, you cannot view or edit Tags after creation 205 // d.Set("tags", tagsToMap(instance.Tags)) 206 createOpts := elasticbeanstalk.CreateEnvironmentInput{ 207 EnvironmentName: aws.String(name), 208 ApplicationName: aws.String(app), 209 OptionSettings: extractOptionSettings(settings), 210 Tags: tagsFromMapBeanstalk(d.Get("tags").(map[string]interface{})), 211 } 212 213 if desc != "" { 214 createOpts.Description = aws.String(desc) 215 } 216 217 if cnamePrefix != "" { 218 if tier != "WebServer" { 219 return fmt.Errorf("Cannont set cname_prefix for tier: %s.", tier) 220 } 221 createOpts.CNAMEPrefix = aws.String(cnamePrefix) 222 } 223 224 if tier != "" { 225 var tierType string 226 227 switch tier { 228 case "WebServer": 229 tierType = "Standard" 230 case "Worker": 231 tierType = "SQS/HTTP" 232 } 233 environmentTier := elasticbeanstalk.EnvironmentTier{ 234 Name: aws.String(tier), 235 Type: aws.String(tierType), 236 } 237 createOpts.Tier = &environmentTier 238 } 239 240 if solutionStack != "" { 241 createOpts.SolutionStackName = aws.String(solutionStack) 242 } 243 244 if templateName != "" { 245 createOpts.TemplateName = aws.String(templateName) 246 } 247 248 // Get the current time to filter describeBeanstalkEvents messages 249 t := time.Now() 250 log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts) 251 resp, err := conn.CreateEnvironment(&createOpts) 252 if err != nil { 253 return err 254 } 255 256 // Assign the application name as the resource ID 257 d.SetId(*resp.EnvironmentId) 258 259 waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string)) 260 if err != nil { 261 return err 262 } 263 264 pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string)) 265 if err != nil { 266 pollInterval = 0 267 log.Printf("[WARN] Error parsing poll_interval, using default backoff") 268 } 269 270 stateConf := &resource.StateChangeConf{ 271 Pending: []string{"Launching", "Updating"}, 272 Target: []string{"Ready"}, 273 Refresh: environmentStateRefreshFunc(conn, d.Id()), 274 Timeout: waitForReadyTimeOut, 275 Delay: 10 * time.Second, 276 PollInterval: pollInterval, 277 MinTimeout: 3 * time.Second, 278 } 279 280 _, err = stateConf.WaitForState() 281 if err != nil { 282 return fmt.Errorf( 283 "Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s", 284 d.Id(), err) 285 } 286 287 err = describeBeanstalkEvents(conn, d.Id(), t) 288 if err != nil { 289 return err 290 } 291 292 return resourceAwsElasticBeanstalkEnvironmentRead(d, meta) 293 } 294 295 func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error { 296 conn := meta.(*AWSClient).elasticbeanstalkconn 297 298 envId := d.Id() 299 300 var hasChange bool 301 302 updateOpts := elasticbeanstalk.UpdateEnvironmentInput{ 303 EnvironmentId: aws.String(envId), 304 } 305 306 if d.HasChange("description") { 307 hasChange = true 308 updateOpts.Description = aws.String(d.Get("description").(string)) 309 } 310 311 if d.HasChange("solution_stack_name") { 312 hasChange = true 313 if v, ok := d.GetOk("solution_stack_name"); ok { 314 updateOpts.SolutionStackName = aws.String(v.(string)) 315 } 316 } 317 318 if d.HasChange("setting") { 319 hasChange = true 320 o, n := d.GetChange("setting") 321 if o == nil { 322 o = &schema.Set{F: optionSettingValueHash} 323 } 324 if n == nil { 325 n = &schema.Set{F: optionSettingValueHash} 326 } 327 328 os := o.(*schema.Set) 329 ns := n.(*schema.Set) 330 331 rm := extractOptionSettings(os.Difference(ns)) 332 add := extractOptionSettings(ns.Difference(os)) 333 334 // Additions and removals of options are done in a single API call, so we 335 // can't do our normal "remove these" and then later "add these", re-adding 336 // any updated settings. 337 // Because of this, we need to exclude any settings in the "removable" 338 // settings that are also found in the "add" settings, otherwise they 339 // conflict. Here we loop through all the initial removables from the set 340 // difference, and create a new slice `remove` that contains those settings 341 // found in `rm` but not in `add` 342 var remove []*elasticbeanstalk.ConfigurationOptionSetting 343 if len(add) > 0 { 344 for _, r := range rm { 345 var update = false 346 for _, a := range add { 347 // ResourceNames are optional. Some defaults come with it, some do 348 // not. We need to guard against nil/empty in state as well as 349 // nil/empty from the API 350 if a.ResourceName != nil { 351 if r.ResourceName == nil { 352 continue 353 } 354 if *r.ResourceName != *a.ResourceName { 355 continue 356 } 357 } 358 if *r.Namespace == *a.Namespace && *r.OptionName == *a.OptionName { 359 log.Printf("[DEBUG] Updating Beanstalk setting (%s::%s) \"%s\" => \"%s\"", *a.Namespace, *a.OptionName, *r.Value, *a.Value) 360 update = true 361 break 362 } 363 } 364 // Only remove options that are not updates 365 if !update { 366 remove = append(remove, r) 367 } 368 } 369 } else { 370 remove = rm 371 } 372 373 for _, elem := range remove { 374 updateOpts.OptionsToRemove = append(updateOpts.OptionsToRemove, &elasticbeanstalk.OptionSpecification{ 375 Namespace: elem.Namespace, 376 OptionName: elem.OptionName, 377 }) 378 } 379 380 updateOpts.OptionSettings = add 381 } 382 383 if d.HasChange("template_name") { 384 hasChange = true 385 if v, ok := d.GetOk("template_name"); ok { 386 updateOpts.TemplateName = aws.String(v.(string)) 387 } 388 } 389 390 if hasChange { 391 // Get the current time to filter describeBeanstalkEvents messages 392 t := time.Now() 393 log.Printf("[DEBUG] Elastic Beanstalk Environment update opts: %s", updateOpts) 394 _, err := conn.UpdateEnvironment(&updateOpts) 395 if err != nil { 396 return err 397 } 398 399 waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string)) 400 if err != nil { 401 return err 402 } 403 pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string)) 404 if err != nil { 405 pollInterval = 0 406 log.Printf("[WARN] Error parsing poll_interval, using default backoff") 407 } 408 409 stateConf := &resource.StateChangeConf{ 410 Pending: []string{"Launching", "Updating"}, 411 Target: []string{"Ready"}, 412 Refresh: environmentStateRefreshFunc(conn, d.Id()), 413 Timeout: waitForReadyTimeOut, 414 Delay: 10 * time.Second, 415 PollInterval: pollInterval, 416 MinTimeout: 3 * time.Second, 417 } 418 419 _, err = stateConf.WaitForState() 420 if err != nil { 421 return fmt.Errorf( 422 "Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s", 423 d.Id(), err) 424 } 425 426 err = describeBeanstalkEvents(conn, d.Id(), t) 427 if err != nil { 428 return err 429 } 430 } 431 432 return resourceAwsElasticBeanstalkEnvironmentRead(d, meta) 433 } 434 435 func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta interface{}) error { 436 conn := meta.(*AWSClient).elasticbeanstalkconn 437 438 envId := d.Id() 439 440 log.Printf("[DEBUG] Elastic Beanstalk environment read %s: id %s", d.Get("name").(string), d.Id()) 441 442 resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{ 443 EnvironmentIds: []*string{aws.String(envId)}, 444 }) 445 446 if err != nil { 447 return err 448 } 449 450 if len(resp.Environments) == 0 { 451 log.Printf("[DEBUG] Elastic Beanstalk environment properties: could not find environment %s", d.Id()) 452 453 d.SetId("") 454 return nil 455 } else if len(resp.Environments) != 1 { 456 return fmt.Errorf("Error reading application properties: found %d environments, expected 1", len(resp.Environments)) 457 } 458 459 env := resp.Environments[0] 460 461 if *env.Status == "Terminated" { 462 log.Printf("[DEBUG] Elastic Beanstalk environment %s was terminated", d.Id()) 463 464 d.SetId("") 465 return nil 466 } 467 468 resources, err := conn.DescribeEnvironmentResources(&elasticbeanstalk.DescribeEnvironmentResourcesInput{ 469 EnvironmentId: aws.String(envId), 470 }) 471 472 if err != nil { 473 return err 474 } 475 476 if err := d.Set("name", env.EnvironmentName); err != nil { 477 return err 478 } 479 480 if err := d.Set("application", env.ApplicationName); err != nil { 481 return err 482 } 483 484 if err := d.Set("description", env.Description); err != nil { 485 return err 486 } 487 488 if err := d.Set("cname", env.CNAME); err != nil { 489 return err 490 } 491 492 if err := d.Set("tier", *env.Tier.Name); err != nil { 493 return err 494 } 495 496 if env.CNAME != nil { 497 beanstalkCnamePrefixRegexp := regexp.MustCompile(`(^[^.]+)(.\w{2}-\w{4,9}-\d)?.elasticbeanstalk.com$`) 498 var cnamePrefix string 499 cnamePrefixMatch := beanstalkCnamePrefixRegexp.FindStringSubmatch(*env.CNAME) 500 501 if cnamePrefixMatch == nil { 502 cnamePrefix = "" 503 } else { 504 cnamePrefix = cnamePrefixMatch[1] 505 } 506 507 if err := d.Set("cname_prefix", cnamePrefix); err != nil { 508 return err 509 } 510 } else { 511 if err := d.Set("cname_prefix", ""); err != nil { 512 return err 513 } 514 } 515 516 if err := d.Set("solution_stack_name", env.SolutionStackName); err != nil { 517 return err 518 } 519 520 if err := d.Set("autoscaling_groups", flattenBeanstalkAsg(resources.EnvironmentResources.AutoScalingGroups)); err != nil { 521 return err 522 } 523 524 if err := d.Set("instances", flattenBeanstalkInstances(resources.EnvironmentResources.Instances)); err != nil { 525 return err 526 } 527 if err := d.Set("launch_configurations", flattenBeanstalkLc(resources.EnvironmentResources.LaunchConfigurations)); err != nil { 528 return err 529 } 530 if err := d.Set("load_balancers", flattenBeanstalkElb(resources.EnvironmentResources.LoadBalancers)); err != nil { 531 return err 532 } 533 if err := d.Set("queues", flattenBeanstalkSqs(resources.EnvironmentResources.Queues)); err != nil { 534 return err 535 } 536 if err := d.Set("triggers", flattenBeanstalkTrigger(resources.EnvironmentResources.Triggers)); err != nil { 537 return err 538 } 539 540 return resourceAwsElasticBeanstalkEnvironmentSettingsRead(d, meta) 541 } 542 543 func fetchAwsElasticBeanstalkEnvironmentSettings(d *schema.ResourceData, meta interface{}) (*schema.Set, error) { 544 conn := meta.(*AWSClient).elasticbeanstalkconn 545 546 app := d.Get("application").(string) 547 name := d.Get("name").(string) 548 549 resp, err := conn.DescribeConfigurationSettings(&elasticbeanstalk.DescribeConfigurationSettingsInput{ 550 ApplicationName: aws.String(app), 551 EnvironmentName: aws.String(name), 552 }) 553 554 if err != nil { 555 return nil, err 556 } 557 558 if len(resp.ConfigurationSettings) != 1 { 559 return nil, fmt.Errorf("Error reading environment settings: received %d settings groups, expected 1", len(resp.ConfigurationSettings)) 560 } 561 562 settings := &schema.Set{F: optionSettingValueHash} 563 for _, optionSetting := range resp.ConfigurationSettings[0].OptionSettings { 564 m := map[string]interface{}{} 565 566 if optionSetting.Namespace != nil { 567 m["namespace"] = *optionSetting.Namespace 568 } else { 569 return nil, fmt.Errorf("Error reading environment settings: option setting with no namespace: %v", optionSetting) 570 } 571 572 if optionSetting.OptionName != nil { 573 m["name"] = *optionSetting.OptionName 574 } else { 575 return nil, fmt.Errorf("Error reading environment settings: option setting with no name: %v", optionSetting) 576 } 577 578 if *optionSetting.Namespace == "aws:autoscaling:scheduledaction" && optionSetting.ResourceName != nil { 579 m["resource"] = *optionSetting.ResourceName 580 } 581 582 if optionSetting.Value != nil { 583 switch *optionSetting.OptionName { 584 case "SecurityGroups": 585 m["value"] = dropGeneratedSecurityGroup(*optionSetting.Value, meta) 586 case "Subnets", "ELBSubnets": 587 m["value"] = sortValues(*optionSetting.Value) 588 default: 589 m["value"] = *optionSetting.Value 590 } 591 } 592 593 settings.Add(m) 594 } 595 596 return settings, nil 597 } 598 599 func resourceAwsElasticBeanstalkEnvironmentSettingsRead(d *schema.ResourceData, meta interface{}) error { 600 log.Printf("[DEBUG] Elastic Beanstalk environment settings read %s: id %s", d.Get("name").(string), d.Id()) 601 602 allSettings, err := fetchAwsElasticBeanstalkEnvironmentSettings(d, meta) 603 if err != nil { 604 return err 605 } 606 607 settings := d.Get("setting").(*schema.Set) 608 609 log.Printf("[DEBUG] Elastic Beanstalk allSettings: %s", allSettings.GoString()) 610 log.Printf("[DEBUG] Elastic Beanstalk settings: %s", settings.GoString()) 611 612 // perform the set operation with only name/namespace as keys, excluding value 613 // this is so we override things in the settings resource data key with updated values 614 // from the api. we skip values we didn't know about before because there are so many 615 // defaults set by the eb api that we would delete many useful defaults. 616 // 617 // there is likely a better way to do this 618 allSettingsKeySet := schema.NewSet(optionSettingKeyHash, allSettings.List()) 619 settingsKeySet := schema.NewSet(optionSettingKeyHash, settings.List()) 620 updatedSettingsKeySet := allSettingsKeySet.Intersection(settingsKeySet) 621 622 log.Printf("[DEBUG] Elastic Beanstalk updatedSettingsKeySet: %s", updatedSettingsKeySet.GoString()) 623 624 updatedSettings := schema.NewSet(optionSettingValueHash, updatedSettingsKeySet.List()) 625 626 log.Printf("[DEBUG] Elastic Beanstalk updatedSettings: %s", updatedSettings.GoString()) 627 628 if err := d.Set("all_settings", allSettings.List()); err != nil { 629 return err 630 } 631 632 if err := d.Set("setting", updatedSettings.List()); err != nil { 633 return err 634 } 635 636 return nil 637 } 638 639 func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error { 640 conn := meta.(*AWSClient).elasticbeanstalkconn 641 642 opts := elasticbeanstalk.TerminateEnvironmentInput{ 643 EnvironmentId: aws.String(d.Id()), 644 TerminateResources: aws.Bool(true), 645 } 646 647 // Get the current time to filter describeBeanstalkEvents messages 648 t := time.Now() 649 log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts) 650 _, err := conn.TerminateEnvironment(&opts) 651 652 if err != nil { 653 return err 654 } 655 656 waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string)) 657 if err != nil { 658 return err 659 } 660 pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string)) 661 if err != nil { 662 pollInterval = 0 663 log.Printf("[WARN] Error parsing poll_interval, using default backoff") 664 } 665 666 stateConf := &resource.StateChangeConf{ 667 Pending: []string{"Terminating"}, 668 Target: []string{"Terminated"}, 669 Refresh: environmentStateRefreshFunc(conn, d.Id()), 670 Timeout: waitForReadyTimeOut, 671 Delay: 10 * time.Second, 672 PollInterval: pollInterval, 673 MinTimeout: 3 * time.Second, 674 } 675 676 _, err = stateConf.WaitForState() 677 if err != nil { 678 return fmt.Errorf( 679 "Error waiting for Elastic Beanstalk Environment (%s) to become terminated: %s", 680 d.Id(), err) 681 } 682 683 err = describeBeanstalkEvents(conn, d.Id(), t) 684 if err != nil { 685 return err 686 } 687 688 return nil 689 } 690 691 // environmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 692 // the creation of the Beanstalk Environment 693 func environmentStateRefreshFunc(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string) resource.StateRefreshFunc { 694 return func() (interface{}, string, error) { 695 resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{ 696 EnvironmentIds: []*string{aws.String(environmentId)}, 697 }) 698 if err != nil { 699 log.Printf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err) 700 return -1, "failed", fmt.Errorf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err) 701 } 702 703 if resp == nil || len(resp.Environments) == 0 { 704 // Sometimes AWS just has consistency issues and doesn't see 705 // our instance yet. Return an empty state. 706 return nil, "", nil 707 } 708 709 var env *elasticbeanstalk.EnvironmentDescription 710 for _, e := range resp.Environments { 711 if environmentId == *e.EnvironmentId { 712 env = e 713 } 714 } 715 716 if env == nil { 717 return -1, "failed", fmt.Errorf("[Err] Error finding Elastic Beanstalk Environment, environment not found") 718 } 719 720 return env, *env.Status, nil 721 } 722 } 723 724 // we use the following two functions to allow us to split out defaults 725 // as they become overridden from within the template 726 func optionSettingValueHash(v interface{}) int { 727 rd := v.(map[string]interface{}) 728 namespace := rd["namespace"].(string) 729 optionName := rd["name"].(string) 730 var resourceName string 731 if v, ok := rd["resource"].(string); ok { 732 resourceName = v 733 } 734 value, _ := rd["value"].(string) 735 hk := fmt.Sprintf("%s:%s%s=%s", namespace, optionName, resourceName, sortValues(value)) 736 log.Printf("[DEBUG] Elastic Beanstalk optionSettingValueHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk)) 737 return hashcode.String(hk) 738 } 739 740 func optionSettingKeyHash(v interface{}) int { 741 rd := v.(map[string]interface{}) 742 namespace := rd["namespace"].(string) 743 optionName := rd["name"].(string) 744 var resourceName string 745 if v, ok := rd["resource"].(string); ok { 746 resourceName = v 747 } 748 hk := fmt.Sprintf("%s:%s%s", namespace, optionName, resourceName) 749 log.Printf("[DEBUG] Elastic Beanstalk optionSettingKeyHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk)) 750 return hashcode.String(hk) 751 } 752 753 func sortValues(v string) string { 754 values := strings.Split(v, ",") 755 sort.Strings(values) 756 return strings.Join(values, ",") 757 } 758 759 func extractOptionSettings(s *schema.Set) []*elasticbeanstalk.ConfigurationOptionSetting { 760 settings := []*elasticbeanstalk.ConfigurationOptionSetting{} 761 762 if s != nil { 763 for _, setting := range s.List() { 764 optionSetting := elasticbeanstalk.ConfigurationOptionSetting{ 765 Namespace: aws.String(setting.(map[string]interface{})["namespace"].(string)), 766 OptionName: aws.String(setting.(map[string]interface{})["name"].(string)), 767 Value: aws.String(setting.(map[string]interface{})["value"].(string)), 768 } 769 if *optionSetting.Namespace == "aws:autoscaling:scheduledaction" { 770 if v, ok := setting.(map[string]interface{})["resource"].(string); ok && v != "" { 771 optionSetting.ResourceName = aws.String(v) 772 } 773 } 774 settings = append(settings, &optionSetting) 775 } 776 } 777 778 return settings 779 } 780 781 func dropGeneratedSecurityGroup(settingValue string, meta interface{}) string { 782 conn := meta.(*AWSClient).ec2conn 783 784 groups := strings.Split(settingValue, ",") 785 786 // Check to see if groups are ec2-classic or vpc security groups 787 ec2Classic := true 788 beanstalkSGRegexp := "sg-[0-9a-fA-F]{8}" 789 for _, g := range groups { 790 if ok, _ := regexp.MatchString(beanstalkSGRegexp, g); ok { 791 ec2Classic = false 792 break 793 } 794 } 795 796 var resp *ec2.DescribeSecurityGroupsOutput 797 var err error 798 799 if ec2Classic { 800 resp, err = conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{ 801 GroupNames: aws.StringSlice(groups), 802 }) 803 } else { 804 resp, err = conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{ 805 GroupIds: aws.StringSlice(groups), 806 }) 807 } 808 809 if err != nil { 810 log.Printf("[DEBUG] Elastic Beanstalk error describing SecurityGroups: %v", err) 811 return settingValue 812 } 813 814 log.Printf("[DEBUG] Elastic Beanstalk using ec2-classic security-groups: %t", ec2Classic) 815 var legitGroups []string 816 for _, group := range resp.SecurityGroups { 817 log.Printf("[DEBUG] Elastic Beanstalk SecurityGroup: %v", *group.GroupName) 818 if !strings.HasPrefix(*group.GroupName, "awseb") { 819 if ec2Classic { 820 legitGroups = append(legitGroups, *group.GroupName) 821 } else { 822 legitGroups = append(legitGroups, *group.GroupId) 823 } 824 } 825 } 826 827 sort.Strings(legitGroups) 828 829 return strings.Join(legitGroups, ",") 830 } 831 832 func describeBeanstalkEvents(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string, t time.Time) error { 833 beanstalkErrors, err := conn.DescribeEvents(&elasticbeanstalk.DescribeEventsInput{ 834 EnvironmentId: aws.String(environmentId), 835 Severity: aws.String("ERROR"), 836 StartTime: aws.Time(t), 837 }) 838 839 if err != nil { 840 log.Printf("[Err] Unable to get Elastic Beanstalk Evironment events: %s", err) 841 } 842 843 events := "" 844 for _, event := range beanstalkErrors.Events { 845 events = events + "\n" + event.EventDate.String() + ": " + *event.Message 846 } 847 848 if events != "" { 849 return fmt.Errorf("%s", events) 850 } 851 852 return nil 853 }