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