github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go (about)

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