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