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  }