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