github.com/danrjohnson/terraform@v0.7.0-rc2.0.20160627135212-d0fc1fa086ff/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  		},
    36  	}
    37  }
    38  
    39  func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
    40  	return &schema.Resource{
    41  		Create: resourceAwsElasticBeanstalkEnvironmentCreate,
    42  		Read:   resourceAwsElasticBeanstalkEnvironmentRead,
    43  		Update: resourceAwsElasticBeanstalkEnvironmentUpdate,
    44  		Delete: resourceAwsElasticBeanstalkEnvironmentDelete,
    45  
    46  		SchemaVersion: 1,
    47  		MigrateState:  resourceAwsElasticBeanstalkEnvironmentMigrateState,
    48  
    49  		Schema: map[string]*schema.Schema{
    50  			"name": &schema.Schema{
    51  				Type:     schema.TypeString,
    52  				Required: true,
    53  				ForceNew: true,
    54  			},
    55  			"application": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Required: true,
    58  			},
    59  			"description": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  			},
    63  			"cname": &schema.Schema{
    64  				Type:     schema.TypeString,
    65  				Computed: true,
    66  			},
    67  			"cname_prefix": &schema.Schema{
    68  				Type:     schema.TypeString,
    69  				Computed: true,
    70  				Optional: true,
    71  				ForceNew: true,
    72  			},
    73  			"tier": &schema.Schema{
    74  				Type:     schema.TypeString,
    75  				Optional: true,
    76  				Default:  "WebServer",
    77  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    78  					value := v.(string)
    79  					switch value {
    80  					case
    81  						"Worker",
    82  						"WebServer":
    83  						return
    84  					}
    85  					errors = append(errors, fmt.Errorf("%s is not a valid tier. Valid options are WebServer or Worker", value))
    86  					return
    87  				},
    88  				ForceNew: true,
    89  			},
    90  			"setting": &schema.Schema{
    91  				Type:     schema.TypeSet,
    92  				Optional: true,
    93  				Computed: true,
    94  				Elem:     resourceAwsElasticBeanstalkOptionSetting(),
    95  				Set:      optionSettingValueHash,
    96  			},
    97  			"all_settings": &schema.Schema{
    98  				Type:     schema.TypeSet,
    99  				Computed: true,
   100  				Elem:     resourceAwsElasticBeanstalkOptionSetting(),
   101  				Set:      optionSettingValueHash,
   102  			},
   103  			"solution_stack_name": &schema.Schema{
   104  				Type:     schema.TypeString,
   105  				Optional: true,
   106  			},
   107  			"template_name": &schema.Schema{
   108  				Type:          schema.TypeString,
   109  				Optional:      true,
   110  				ConflictsWith: []string{"solution_stack_name"},
   111  			},
   112  			"wait_for_ready_timeout": &schema.Schema{
   113  				Type:     schema.TypeString,
   114  				Optional: true,
   115  				Default:  "10m",
   116  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   117  					value := v.(string)
   118  					duration, err := time.ParseDuration(value)
   119  					if err != nil {
   120  						errors = append(errors, fmt.Errorf(
   121  							"%q cannot be parsed as a duration: %s", k, err))
   122  					}
   123  					if duration < 0 {
   124  						errors = append(errors, fmt.Errorf(
   125  							"%q must be greater than zero", k))
   126  					}
   127  					return
   128  				},
   129  			},
   130  			"autoscaling_groups": &schema.Schema{
   131  				Type:     schema.TypeList,
   132  				Computed: true,
   133  				Elem:     &schema.Schema{Type: schema.TypeString},
   134  			},
   135  			"instances": &schema.Schema{
   136  				Type:     schema.TypeList,
   137  				Computed: true,
   138  				Elem:     &schema.Schema{Type: schema.TypeString},
   139  			},
   140  			"launch_configurations": &schema.Schema{
   141  				Type:     schema.TypeList,
   142  				Computed: true,
   143  				Elem:     &schema.Schema{Type: schema.TypeString},
   144  			},
   145  			"load_balancers": &schema.Schema{
   146  				Type:     schema.TypeList,
   147  				Computed: true,
   148  				Elem:     &schema.Schema{Type: schema.TypeString},
   149  			},
   150  			"queues": &schema.Schema{
   151  				Type:     schema.TypeList,
   152  				Computed: true,
   153  				Elem:     &schema.Schema{Type: schema.TypeString},
   154  			},
   155  			"triggers": &schema.Schema{
   156  				Type:     schema.TypeList,
   157  				Computed: true,
   158  				Elem:     &schema.Schema{Type: schema.TypeString},
   159  			},
   160  
   161  			"tags": tagsSchema(),
   162  		},
   163  	}
   164  }
   165  
   166  func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta interface{}) error {
   167  	conn := meta.(*AWSClient).elasticbeanstalkconn
   168  
   169  	// Get values from config
   170  	name := d.Get("name").(string)
   171  	cnamePrefix := d.Get("cname_prefix").(string)
   172  	tier := d.Get("tier").(string)
   173  	app := d.Get("application").(string)
   174  	desc := d.Get("description").(string)
   175  	settings := d.Get("setting").(*schema.Set)
   176  	solutionStack := d.Get("solution_stack_name").(string)
   177  	templateName := d.Get("template_name").(string)
   178  	waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	// TODO set tags
   184  	// Note: at time of writing, you cannot view or edit Tags after creation
   185  	// d.Set("tags", tagsToMap(instance.Tags))
   186  	createOpts := elasticbeanstalk.CreateEnvironmentInput{
   187  		EnvironmentName: aws.String(name),
   188  		ApplicationName: aws.String(app),
   189  		OptionSettings:  extractOptionSettings(settings),
   190  		Tags:            tagsFromMapBeanstalk(d.Get("tags").(map[string]interface{})),
   191  	}
   192  
   193  	if desc != "" {
   194  		createOpts.Description = aws.String(desc)
   195  	}
   196  
   197  	if cnamePrefix != "" {
   198  		if tier != "WebServer" {
   199  			return fmt.Errorf("Cannont set cname_prefix for tier: %s.", tier)
   200  		}
   201  		createOpts.CNAMEPrefix = aws.String(cnamePrefix)
   202  	}
   203  
   204  	if tier != "" {
   205  		var tierType string
   206  
   207  		switch tier {
   208  		case "WebServer":
   209  			tierType = "Standard"
   210  		case "Worker":
   211  			tierType = "SQS/HTTP"
   212  		}
   213  		environmentTier := elasticbeanstalk.EnvironmentTier{
   214  			Name: aws.String(tier),
   215  			Type: aws.String(tierType),
   216  		}
   217  		createOpts.Tier = &environmentTier
   218  	}
   219  
   220  	if solutionStack != "" {
   221  		createOpts.SolutionStackName = aws.String(solutionStack)
   222  	}
   223  
   224  	if templateName != "" {
   225  		createOpts.TemplateName = aws.String(templateName)
   226  	}
   227  
   228  	// Get the current time to filter describeBeanstalkEvents messages
   229  	t := time.Now()
   230  	log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts)
   231  	resp, err := conn.CreateEnvironment(&createOpts)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	// Assign the application name as the resource ID
   237  	d.SetId(*resp.EnvironmentId)
   238  
   239  	stateConf := &resource.StateChangeConf{
   240  		Pending:    []string{"Launching", "Updating"},
   241  		Target:     []string{"Ready"},
   242  		Refresh:    environmentStateRefreshFunc(conn, d.Id()),
   243  		Timeout:    waitForReadyTimeOut,
   244  		Delay:      10 * time.Second,
   245  		MinTimeout: 3 * time.Second,
   246  	}
   247  
   248  	_, err = stateConf.WaitForState()
   249  	if err != nil {
   250  		return fmt.Errorf(
   251  			"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
   252  			d.Id(), err)
   253  	}
   254  
   255  	err = describeBeanstalkEvents(conn, d.Id(), t)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
   261  }
   262  
   263  func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error {
   264  	conn := meta.(*AWSClient).elasticbeanstalkconn
   265  
   266  	envId := d.Id()
   267  	waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	updateOpts := elasticbeanstalk.UpdateEnvironmentInput{
   273  		EnvironmentId: aws.String(envId),
   274  	}
   275  
   276  	if d.HasChange("description") {
   277  		updateOpts.Description = aws.String(d.Get("description").(string))
   278  	}
   279  
   280  	if d.HasChange("solution_stack_name") {
   281  		updateOpts.SolutionStackName = aws.String(d.Get("solution_stack_name").(string))
   282  	}
   283  
   284  	if d.HasChange("setting") {
   285  		o, n := d.GetChange("setting")
   286  		if o == nil {
   287  			o = &schema.Set{F: optionSettingValueHash}
   288  		}
   289  		if n == nil {
   290  			n = &schema.Set{F: optionSettingValueHash}
   291  		}
   292  
   293  		os := o.(*schema.Set)
   294  		ns := n.(*schema.Set)
   295  
   296  		updateOpts.OptionSettings = extractOptionSettings(ns.Difference(os))
   297  	}
   298  
   299  	if d.HasChange("template_name") {
   300  		updateOpts.TemplateName = aws.String(d.Get("template_name").(string))
   301  	}
   302  
   303  	// Get the current time to filter describeBeanstalkEvents messages
   304  	t := time.Now()
   305  	log.Printf("[DEBUG] Elastic Beanstalk Environment update opts: %s", updateOpts)
   306  	_, err = conn.UpdateEnvironment(&updateOpts)
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	stateConf := &resource.StateChangeConf{
   312  		Pending:    []string{"Launching", "Updating"},
   313  		Target:     []string{"Ready"},
   314  		Refresh:    environmentStateRefreshFunc(conn, d.Id()),
   315  		Timeout:    waitForReadyTimeOut,
   316  		Delay:      10 * time.Second,
   317  		MinTimeout: 3 * time.Second,
   318  	}
   319  
   320  	_, err = stateConf.WaitForState()
   321  	if err != nil {
   322  		return fmt.Errorf(
   323  			"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
   324  			d.Id(), err)
   325  	}
   326  
   327  	err = describeBeanstalkEvents(conn, d.Id(), t)
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
   333  }
   334  
   335  func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta interface{}) error {
   336  	conn := meta.(*AWSClient).elasticbeanstalkconn
   337  
   338  	app := d.Get("application").(string)
   339  	envId := d.Id()
   340  	tier := d.Get("tier").(string)
   341  
   342  	log.Printf("[DEBUG] Elastic Beanstalk environment read %s: id %s", d.Get("name").(string), d.Id())
   343  
   344  	resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
   345  		ApplicationName: aws.String(app),
   346  		EnvironmentIds:  []*string{aws.String(envId)},
   347  	})
   348  
   349  	if err != nil {
   350  		return err
   351  	}
   352  
   353  	if len(resp.Environments) == 0 {
   354  		log.Printf("[DEBUG] Elastic Beanstalk environment properties: could not find environment %s", d.Id())
   355  
   356  		d.SetId("")
   357  		return nil
   358  	} else if len(resp.Environments) != 1 {
   359  		return fmt.Errorf("Error reading application properties: found %d environments, expected 1", len(resp.Environments))
   360  	}
   361  
   362  	env := resp.Environments[0]
   363  
   364  	if *env.Status == "Terminated" {
   365  		log.Printf("[DEBUG] Elastic Beanstalk environment %s was terminated", d.Id())
   366  
   367  		d.SetId("")
   368  		return nil
   369  	}
   370  
   371  	resources, err := conn.DescribeEnvironmentResources(&elasticbeanstalk.DescribeEnvironmentResourcesInput{
   372  		EnvironmentId: aws.String(envId),
   373  	})
   374  
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	if err := d.Set("description", env.Description); err != nil {
   380  		return err
   381  	}
   382  
   383  	if err := d.Set("cname", env.CNAME); err != nil {
   384  		return err
   385  	}
   386  
   387  	if tier == "WebServer" && env.CNAME != nil {
   388  		beanstalkCnamePrefixRegexp := regexp.MustCompile(`(^[^.]+).\w{2}-\w{4,9}-\d.elasticbeanstalk.com$`)
   389  		var cnamePrefix string
   390  		cnamePrefixMatch := beanstalkCnamePrefixRegexp.FindStringSubmatch(*env.CNAME)
   391  
   392  		if cnamePrefixMatch == nil {
   393  			cnamePrefix = ""
   394  		} else {
   395  			cnamePrefix = cnamePrefixMatch[1]
   396  		}
   397  
   398  		if err := d.Set("cname_prefix", cnamePrefix); err != nil {
   399  			return err
   400  		}
   401  	}
   402  
   403  	if err := d.Set("autoscaling_groups", flattenBeanstalkAsg(resources.EnvironmentResources.AutoScalingGroups)); err != nil {
   404  		return err
   405  	}
   406  
   407  	if err := d.Set("instances", flattenBeanstalkInstances(resources.EnvironmentResources.Instances)); err != nil {
   408  		return err
   409  	}
   410  	if err := d.Set("launch_configurations", flattenBeanstalkLc(resources.EnvironmentResources.LaunchConfigurations)); err != nil {
   411  		return err
   412  	}
   413  	if err := d.Set("load_balancers", flattenBeanstalkElb(resources.EnvironmentResources.LoadBalancers)); err != nil {
   414  		return err
   415  	}
   416  	if err := d.Set("queues", flattenBeanstalkSqs(resources.EnvironmentResources.Queues)); err != nil {
   417  		return err
   418  	}
   419  	if err := d.Set("triggers", flattenBeanstalkTrigger(resources.EnvironmentResources.Triggers)); err != nil {
   420  		return err
   421  	}
   422  
   423  	return resourceAwsElasticBeanstalkEnvironmentSettingsRead(d, meta)
   424  }
   425  
   426  func fetchAwsElasticBeanstalkEnvironmentSettings(d *schema.ResourceData, meta interface{}) (*schema.Set, error) {
   427  	conn := meta.(*AWSClient).elasticbeanstalkconn
   428  
   429  	app := d.Get("application").(string)
   430  	name := d.Get("name").(string)
   431  
   432  	resp, err := conn.DescribeConfigurationSettings(&elasticbeanstalk.DescribeConfigurationSettingsInput{
   433  		ApplicationName: aws.String(app),
   434  		EnvironmentName: aws.String(name),
   435  	})
   436  
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  
   441  	if len(resp.ConfigurationSettings) != 1 {
   442  		return nil, fmt.Errorf("Error reading environment settings: received %d settings groups, expected 1", len(resp.ConfigurationSettings))
   443  	}
   444  
   445  	settings := &schema.Set{F: optionSettingValueHash}
   446  	for _, optionSetting := range resp.ConfigurationSettings[0].OptionSettings {
   447  		m := map[string]interface{}{}
   448  
   449  		if optionSetting.Namespace != nil {
   450  			m["namespace"] = *optionSetting.Namespace
   451  		} else {
   452  			return nil, fmt.Errorf("Error reading environment settings: option setting with no namespace: %v", optionSetting)
   453  		}
   454  
   455  		if optionSetting.OptionName != nil {
   456  			m["name"] = *optionSetting.OptionName
   457  		} else {
   458  			return nil, fmt.Errorf("Error reading environment settings: option setting with no name: %v", optionSetting)
   459  		}
   460  
   461  		if optionSetting.Value != nil {
   462  			switch *optionSetting.OptionName {
   463  			case "SecurityGroups":
   464  				m["value"] = dropGeneratedSecurityGroup(*optionSetting.Value, meta)
   465  			case "Subnets", "ELBSubnets":
   466  				m["value"] = sortValues(*optionSetting.Value)
   467  			default:
   468  				m["value"] = *optionSetting.Value
   469  			}
   470  		}
   471  
   472  		settings.Add(m)
   473  	}
   474  
   475  	return settings, nil
   476  }
   477  
   478  func resourceAwsElasticBeanstalkEnvironmentSettingsRead(d *schema.ResourceData, meta interface{}) error {
   479  	log.Printf("[DEBUG] Elastic Beanstalk environment settings read %s: id %s", d.Get("name").(string), d.Id())
   480  
   481  	allSettings, err := fetchAwsElasticBeanstalkEnvironmentSettings(d, meta)
   482  	if err != nil {
   483  		return err
   484  	}
   485  
   486  	settings := d.Get("setting").(*schema.Set)
   487  
   488  	log.Printf("[DEBUG] Elastic Beanstalk allSettings: %s", allSettings.GoString())
   489  	log.Printf("[DEBUG] Elastic Beanstalk settings: %s", settings.GoString())
   490  
   491  	// perform the set operation with only name/namespace as keys, excluding value
   492  	// this is so we override things in the settings resource data key with updated values
   493  	// from the api.  we skip values we didn't know about before because there are so many
   494  	// defaults set by the eb api that we would delete many useful defaults.
   495  	//
   496  	// there is likely a better way to do this
   497  	allSettingsKeySet := schema.NewSet(optionSettingKeyHash, allSettings.List())
   498  	settingsKeySet := schema.NewSet(optionSettingKeyHash, settings.List())
   499  	updatedSettingsKeySet := allSettingsKeySet.Intersection(settingsKeySet)
   500  
   501  	log.Printf("[DEBUG] Elastic Beanstalk updatedSettingsKeySet: %s", updatedSettingsKeySet.GoString())
   502  
   503  	updatedSettings := schema.NewSet(optionSettingValueHash, updatedSettingsKeySet.List())
   504  
   505  	log.Printf("[DEBUG] Elastic Beanstalk updatedSettings: %s", updatedSettings.GoString())
   506  
   507  	if err := d.Set("all_settings", allSettings.List()); err != nil {
   508  		return err
   509  	}
   510  
   511  	if err := d.Set("setting", updatedSettings.List()); err != nil {
   512  		return err
   513  	}
   514  
   515  	return nil
   516  }
   517  
   518  func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error {
   519  	conn := meta.(*AWSClient).elasticbeanstalkconn
   520  
   521  	waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
   522  	if err != nil {
   523  		return err
   524  	}
   525  
   526  	opts := elasticbeanstalk.TerminateEnvironmentInput{
   527  		EnvironmentId:      aws.String(d.Id()),
   528  		TerminateResources: aws.Bool(true),
   529  	}
   530  
   531  	// Get the current time to filter describeBeanstalkEvents messages
   532  	t := time.Now()
   533  	log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts)
   534  	_, err = conn.TerminateEnvironment(&opts)
   535  
   536  	if err != nil {
   537  		return err
   538  	}
   539  
   540  	stateConf := &resource.StateChangeConf{
   541  		Pending:    []string{"Terminating"},
   542  		Target:     []string{"Terminated"},
   543  		Refresh:    environmentStateRefreshFunc(conn, d.Id()),
   544  		Timeout:    waitForReadyTimeOut,
   545  		Delay:      10 * time.Second,
   546  		MinTimeout: 3 * time.Second,
   547  	}
   548  
   549  	_, err = stateConf.WaitForState()
   550  	if err != nil {
   551  		return fmt.Errorf(
   552  			"Error waiting for Elastic Beanstalk Environment (%s) to become terminated: %s",
   553  			d.Id(), err)
   554  	}
   555  
   556  	err = describeBeanstalkEvents(conn, d.Id(), t)
   557  	if err != nil {
   558  		return err
   559  	}
   560  
   561  	return nil
   562  }
   563  
   564  // environmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   565  // the creation of the Beanstalk Environment
   566  func environmentStateRefreshFunc(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string) resource.StateRefreshFunc {
   567  	return func() (interface{}, string, error) {
   568  		resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
   569  			EnvironmentIds: []*string{aws.String(environmentId)},
   570  		})
   571  		if err != nil {
   572  			log.Printf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
   573  			return -1, "failed", fmt.Errorf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
   574  		}
   575  
   576  		if resp == nil || len(resp.Environments) == 0 {
   577  			// Sometimes AWS just has consistency issues and doesn't see
   578  			// our instance yet. Return an empty state.
   579  			return nil, "", nil
   580  		}
   581  
   582  		var env *elasticbeanstalk.EnvironmentDescription
   583  		for _, e := range resp.Environments {
   584  			if environmentId == *e.EnvironmentId {
   585  				env = e
   586  			}
   587  		}
   588  
   589  		if env == nil {
   590  			return -1, "failed", fmt.Errorf("[Err] Error finding Elastic Beanstalk Environment, environment not found")
   591  		}
   592  
   593  		return env, *env.Status, nil
   594  	}
   595  }
   596  
   597  // we use the following two functions to allow us to split out defaults
   598  // as they become overridden from within the template
   599  func optionSettingValueHash(v interface{}) int {
   600  	rd := v.(map[string]interface{})
   601  	namespace := rd["namespace"].(string)
   602  	optionName := rd["name"].(string)
   603  	value, _ := rd["value"].(string)
   604  	hk := fmt.Sprintf("%s:%s=%s", namespace, optionName, sortValues(value))
   605  	log.Printf("[DEBUG] Elastic Beanstalk optionSettingValueHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
   606  	return hashcode.String(hk)
   607  }
   608  
   609  func optionSettingKeyHash(v interface{}) int {
   610  	rd := v.(map[string]interface{})
   611  	namespace := rd["namespace"].(string)
   612  	optionName := rd["name"].(string)
   613  	hk := fmt.Sprintf("%s:%s", namespace, optionName)
   614  	log.Printf("[DEBUG] Elastic Beanstalk optionSettingKeyHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
   615  	return hashcode.String(hk)
   616  }
   617  
   618  func sortValues(v string) string {
   619  	values := strings.Split(v, ",")
   620  	sort.Strings(values)
   621  	return strings.Join(values, ",")
   622  }
   623  
   624  func extractOptionSettings(s *schema.Set) []*elasticbeanstalk.ConfigurationOptionSetting {
   625  	settings := []*elasticbeanstalk.ConfigurationOptionSetting{}
   626  
   627  	if s != nil {
   628  		for _, setting := range s.List() {
   629  			settings = append(settings, &elasticbeanstalk.ConfigurationOptionSetting{
   630  				Namespace:  aws.String(setting.(map[string]interface{})["namespace"].(string)),
   631  				OptionName: aws.String(setting.(map[string]interface{})["name"].(string)),
   632  				Value:      aws.String(setting.(map[string]interface{})["value"].(string)),
   633  			})
   634  		}
   635  	}
   636  
   637  	return settings
   638  }
   639  
   640  func dropGeneratedSecurityGroup(settingValue string, meta interface{}) string {
   641  	conn := meta.(*AWSClient).ec2conn
   642  
   643  	groups := strings.Split(settingValue, ",")
   644  
   645  	resp, err := conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
   646  		GroupIds: aws.StringSlice(groups),
   647  	})
   648  
   649  	if err != nil {
   650  		log.Printf("[DEBUG] Elastic Beanstalk error describing SecurityGroups: %v", err)
   651  		return settingValue
   652  	}
   653  
   654  	var legitGroups []string
   655  	for _, group := range resp.SecurityGroups {
   656  		log.Printf("[DEBUG] Elastic Beanstalk SecurityGroup: %v", *group.GroupName)
   657  		if !strings.HasPrefix(*group.GroupName, "awseb") {
   658  			legitGroups = append(legitGroups, *group.GroupId)
   659  		}
   660  	}
   661  
   662  	sort.Strings(legitGroups)
   663  
   664  	return strings.Join(legitGroups, ",")
   665  }
   666  
   667  func describeBeanstalkEvents(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string, t time.Time) error {
   668  	beanstalkErrors, err := conn.DescribeEvents(&elasticbeanstalk.DescribeEventsInput{
   669  		EnvironmentId: aws.String(environmentId),
   670  		Severity:      aws.String("ERROR"),
   671  		StartTime:     aws.Time(t),
   672  	})
   673  
   674  	if err != nil {
   675  		log.Printf("[Err] Unable to get Elastic Beanstalk Evironment events: %s", err)
   676  	}
   677  
   678  	events := ""
   679  	for _, event := range beanstalkErrors.Events {
   680  		events = events + "\n" + event.EventDate.String() + ": " + *event.Message
   681  	}
   682  
   683  	if events != "" {
   684  		return fmt.Errorf("%s", events)
   685  	}
   686  
   687  	return nil
   688  }