github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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:  "20m",
   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("Cannot 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  }