github.com/subuk/terraform@v0.6.14-0.20160317140351-de1567c2e732/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/helper/hashcode"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  
    14  	"github.com/aws/aws-sdk-go/aws"
    15  	"github.com/aws/aws-sdk-go/service/ec2"
    16  	"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
    17  )
    18  
    19  func resourceAwsElasticBeanstalkOptionSetting() *schema.Resource {
    20  	return &schema.Resource{
    21  		Schema: map[string]*schema.Schema{
    22  			"namespace": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Required: true,
    25  			},
    26  			"name": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Required: true,
    29  			},
    30  			"value": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  			},
    34  		},
    35  	}
    36  }
    37  
    38  func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
    39  	return &schema.Resource{
    40  		Create: resourceAwsElasticBeanstalkEnvironmentCreate,
    41  		Read:   resourceAwsElasticBeanstalkEnvironmentRead,
    42  		Update: resourceAwsElasticBeanstalkEnvironmentUpdate,
    43  		Delete: resourceAwsElasticBeanstalkEnvironmentDelete,
    44  
    45  		Schema: map[string]*schema.Schema{
    46  			"name": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Required: true,
    49  				ForceNew: true,
    50  			},
    51  			"application": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  			},
    55  			"description": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Optional: true,
    58  			},
    59  			"cname": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Computed: true,
    62  			},
    63  			"tier": &schema.Schema{
    64  				Type:     schema.TypeString,
    65  				Optional: true,
    66  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    67  					value := v.(string)
    68  					switch value {
    69  					case
    70  						"Worker",
    71  						"WebServer":
    72  						return
    73  					}
    74  					errors = append(errors, fmt.Errorf("%s is not a valid tier. Valid options are WebServer or Worker", value))
    75  					return
    76  				},
    77  				ForceNew: true,
    78  			},
    79  			"setting": &schema.Schema{
    80  				Type:     schema.TypeSet,
    81  				Optional: true,
    82  				Computed: true,
    83  				Elem:     resourceAwsElasticBeanstalkOptionSetting(),
    84  				Set:      optionSettingValueHash,
    85  			},
    86  			"all_settings": &schema.Schema{
    87  				Type:     schema.TypeSet,
    88  				Computed: true,
    89  				Elem:     resourceAwsElasticBeanstalkOptionSetting(),
    90  				Set:      optionSettingValueHash,
    91  			},
    92  			"solution_stack_name": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  			},
    96  			"template_name": &schema.Schema{
    97  				Type:          schema.TypeString,
    98  				Optional:      true,
    99  				ConflictsWith: []string{"solution_stack_name"},
   100  			},
   101  
   102  			"tags": tagsSchema(),
   103  		},
   104  	}
   105  }
   106  
   107  func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta interface{}) error {
   108  	conn := meta.(*AWSClient).elasticbeanstalkconn
   109  
   110  	// Get values from config
   111  	name := d.Get("name").(string)
   112  	cname := d.Get("cname").(string)
   113  	tier := d.Get("tier").(string)
   114  	app := d.Get("application").(string)
   115  	desc := d.Get("description").(string)
   116  	settings := d.Get("setting").(*schema.Set)
   117  	solutionStack := d.Get("solution_stack_name").(string)
   118  	templateName := d.Get("template_name").(string)
   119  
   120  	// TODO set tags
   121  	// Note: at time of writing, you cannot view or edit Tags after creation
   122  	// d.Set("tags", tagsToMap(instance.Tags))
   123  	createOpts := elasticbeanstalk.CreateEnvironmentInput{
   124  		EnvironmentName: aws.String(name),
   125  		ApplicationName: aws.String(app),
   126  		OptionSettings:  extractOptionSettings(settings),
   127  		Tags:            tagsFromMapBeanstalk(d.Get("tags").(map[string]interface{})),
   128  	}
   129  
   130  	if desc != "" {
   131  		createOpts.Description = aws.String(desc)
   132  	}
   133  
   134  	if cname != "" {
   135  		createOpts.CNAMEPrefix = aws.String(cname)
   136  	}
   137  
   138  	if tier != "" {
   139  		var tierType string
   140  
   141  		switch tier {
   142  		case "WebServer":
   143  			tierType = "Standard"
   144  		case "Worker":
   145  			tierType = "SQS/HTTP"
   146  		}
   147  		environmentTier := elasticbeanstalk.EnvironmentTier{
   148  			Name: aws.String(tier),
   149  			Type: aws.String(tierType),
   150  		}
   151  		createOpts.Tier = &environmentTier
   152  	}
   153  
   154  	if solutionStack != "" {
   155  		createOpts.SolutionStackName = aws.String(solutionStack)
   156  	}
   157  
   158  	if templateName != "" {
   159  		createOpts.TemplateName = aws.String(templateName)
   160  	}
   161  
   162  	log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts)
   163  	resp, err := conn.CreateEnvironment(&createOpts)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	// Assign the application name as the resource ID
   169  	d.SetId(*resp.EnvironmentId)
   170  
   171  	stateConf := &resource.StateChangeConf{
   172  		Pending:    []string{"Launching", "Updating"},
   173  		Target:     []string{"Ready"},
   174  		Refresh:    environmentStateRefreshFunc(conn, d.Id()),
   175  		Timeout:    10 * time.Minute,
   176  		Delay:      10 * time.Second,
   177  		MinTimeout: 3 * time.Second,
   178  	}
   179  
   180  	_, err = stateConf.WaitForState()
   181  	if err != nil {
   182  		return fmt.Errorf(
   183  			"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
   184  			d.Id(), err)
   185  	}
   186  
   187  	return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
   188  }
   189  
   190  func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error {
   191  	conn := meta.(*AWSClient).elasticbeanstalkconn
   192  
   193  	if d.HasChange("description") {
   194  		if err := resourceAwsElasticBeanstalkEnvironmentDescriptionUpdate(conn, d); err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	if d.HasChange("solution_stack_name") {
   200  		if err := resourceAwsElasticBeanstalkEnvironmentSolutionStackUpdate(conn, d); err != nil {
   201  			return err
   202  		}
   203  	}
   204  
   205  	if d.HasChange("setting") {
   206  		if err := resourceAwsElasticBeanstalkEnvironmentOptionSettingsUpdate(conn, d); err != nil {
   207  			return err
   208  		}
   209  	}
   210  
   211  	return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
   212  }
   213  
   214  func resourceAwsElasticBeanstalkEnvironmentDescriptionUpdate(conn *elasticbeanstalk.ElasticBeanstalk, d *schema.ResourceData) error {
   215  	name := d.Get("name").(string)
   216  	desc := d.Get("description").(string)
   217  	envId := d.Id()
   218  
   219  	log.Printf("[DEBUG] Elastic Beanstalk application: %s, update description: %s", name, desc)
   220  
   221  	_, err := conn.UpdateEnvironment(&elasticbeanstalk.UpdateEnvironmentInput{
   222  		EnvironmentId: aws.String(envId),
   223  		Description:   aws.String(desc),
   224  	})
   225  
   226  	return err
   227  }
   228  
   229  func resourceAwsElasticBeanstalkEnvironmentOptionSettingsUpdate(conn *elasticbeanstalk.ElasticBeanstalk, d *schema.ResourceData) error {
   230  	name := d.Get("name").(string)
   231  	envId := d.Id()
   232  
   233  	log.Printf("[DEBUG] Elastic Beanstalk application: %s, update options", name)
   234  
   235  	req := &elasticbeanstalk.UpdateEnvironmentInput{
   236  		EnvironmentId: aws.String(envId),
   237  	}
   238  
   239  	if d.HasChange("setting") {
   240  		o, n := d.GetChange("setting")
   241  		if o == nil {
   242  			o = &schema.Set{F: optionSettingValueHash}
   243  		}
   244  		if n == nil {
   245  			n = &schema.Set{F: optionSettingValueHash}
   246  		}
   247  
   248  		os := o.(*schema.Set)
   249  		ns := n.(*schema.Set)
   250  
   251  		req.OptionSettings = extractOptionSettings(ns.Difference(os))
   252  	}
   253  
   254  	if _, err := conn.UpdateEnvironment(req); err != nil {
   255  		return err
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func resourceAwsElasticBeanstalkEnvironmentSolutionStackUpdate(conn *elasticbeanstalk.ElasticBeanstalk, d *schema.ResourceData) error {
   262  	name := d.Get("name").(string)
   263  	solutionStack := d.Get("solution_stack_name").(string)
   264  	envId := d.Id()
   265  
   266  	log.Printf("[DEBUG] Elastic Beanstalk application: %s, update solution_stack_name: %s", name, solutionStack)
   267  
   268  	_, err := conn.UpdateEnvironment(&elasticbeanstalk.UpdateEnvironmentInput{
   269  		EnvironmentId:     aws.String(envId),
   270  		SolutionStackName: aws.String(solutionStack),
   271  	})
   272  
   273  	return err
   274  }
   275  
   276  func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta interface{}) error {
   277  	conn := meta.(*AWSClient).elasticbeanstalkconn
   278  
   279  	app := d.Get("application").(string)
   280  	envId := d.Id()
   281  
   282  	log.Printf("[DEBUG] Elastic Beanstalk environment read %s: id %s", d.Get("name").(string), d.Id())
   283  
   284  	resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
   285  		ApplicationName: aws.String(app),
   286  		EnvironmentIds:  []*string{aws.String(envId)},
   287  	})
   288  
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	if len(resp.Environments) == 0 {
   294  		log.Printf("[DEBUG] Elastic Beanstalk environment properties: could not find environment %s", d.Id())
   295  
   296  		d.SetId("")
   297  		return nil
   298  	} else if len(resp.Environments) != 1 {
   299  		return fmt.Errorf("Error reading application properties: found %d environments, expected 1", len(resp.Environments))
   300  	}
   301  
   302  	env := resp.Environments[0]
   303  
   304  	if *env.Status == "Terminated" {
   305  		log.Printf("[DEBUG] Elastic Beanstalk environment %s was terminated", d.Id())
   306  
   307  		d.SetId("")
   308  		return nil
   309  	}
   310  
   311  	if err := d.Set("description", env.Description); err != nil {
   312  		return err
   313  	}
   314  
   315  	if err := d.Set("cname", env.CNAME); err != nil {
   316  		return err
   317  	}
   318  
   319  	return resourceAwsElasticBeanstalkEnvironmentSettingsRead(d, meta)
   320  }
   321  
   322  func fetchAwsElasticBeanstalkEnvironmentSettings(d *schema.ResourceData, meta interface{}) (*schema.Set, error) {
   323  	conn := meta.(*AWSClient).elasticbeanstalkconn
   324  
   325  	app := d.Get("application").(string)
   326  	name := d.Get("name").(string)
   327  
   328  	resp, err := conn.DescribeConfigurationSettings(&elasticbeanstalk.DescribeConfigurationSettingsInput{
   329  		ApplicationName: aws.String(app),
   330  		EnvironmentName: aws.String(name),
   331  	})
   332  
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	if len(resp.ConfigurationSettings) != 1 {
   338  		return nil, fmt.Errorf("Error reading environment settings: received %d settings groups, expected 1", len(resp.ConfigurationSettings))
   339  	}
   340  
   341  	settings := &schema.Set{F: optionSettingValueHash}
   342  	for _, optionSetting := range resp.ConfigurationSettings[0].OptionSettings {
   343  		m := map[string]interface{}{}
   344  
   345  		if optionSetting.Namespace != nil {
   346  			m["namespace"] = *optionSetting.Namespace
   347  		} else {
   348  			return nil, fmt.Errorf("Error reading environment settings: option setting with no namespace: %v", optionSetting)
   349  		}
   350  
   351  		if optionSetting.OptionName != nil {
   352  			m["name"] = *optionSetting.OptionName
   353  		} else {
   354  			return nil, fmt.Errorf("Error reading environment settings: option setting with no name: %v", optionSetting)
   355  		}
   356  
   357  		if optionSetting.Value != nil {
   358  			switch *optionSetting.OptionName {
   359  			case "SecurityGroups":
   360  				m["value"] = dropGeneratedSecurityGroup(*optionSetting.Value, meta)
   361  			case "Subnets", "ELBSubnets":
   362  				m["value"] = sortValues(*optionSetting.Value)
   363  			default:
   364  				m["value"] = *optionSetting.Value
   365  			}
   366  		}
   367  
   368  		settings.Add(m)
   369  	}
   370  
   371  	return settings, nil
   372  }
   373  
   374  func resourceAwsElasticBeanstalkEnvironmentSettingsRead(d *schema.ResourceData, meta interface{}) error {
   375  	log.Printf("[DEBUG] Elastic Beanstalk environment settings read %s: id %s", d.Get("name").(string), d.Id())
   376  
   377  	allSettings, err := fetchAwsElasticBeanstalkEnvironmentSettings(d, meta)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	settings := d.Get("setting").(*schema.Set)
   383  
   384  	log.Printf("[DEBUG] Elastic Beanstalk allSettings: %s", allSettings.GoString())
   385  	log.Printf("[DEBUG] Elastic Beanstalk settings: %s", settings.GoString())
   386  
   387  	// perform the set operation with only name/namespace as keys, excluding value
   388  	// this is so we override things in the settings resource data key with updated values
   389  	// from the api.  we skip values we didn't know about before because there are so many
   390  	// defaults set by the eb api that we would delete many useful defaults.
   391  	//
   392  	// there is likely a better way to do this
   393  	allSettingsKeySet := schema.NewSet(optionSettingKeyHash, allSettings.List())
   394  	settingsKeySet := schema.NewSet(optionSettingKeyHash, settings.List())
   395  	updatedSettingsKeySet := allSettingsKeySet.Intersection(settingsKeySet)
   396  
   397  	log.Printf("[DEBUG] Elastic Beanstalk updatedSettingsKeySet: %s", updatedSettingsKeySet.GoString())
   398  
   399  	updatedSettings := schema.NewSet(optionSettingValueHash, updatedSettingsKeySet.List())
   400  
   401  	log.Printf("[DEBUG] Elastic Beanstalk updatedSettings: %s", updatedSettings.GoString())
   402  
   403  	if err := d.Set("all_settings", allSettings.List()); err != nil {
   404  		return err
   405  	}
   406  
   407  	if err := d.Set("setting", updatedSettings.List()); err != nil {
   408  		return err
   409  	}
   410  
   411  	return nil
   412  }
   413  
   414  func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error {
   415  	conn := meta.(*AWSClient).elasticbeanstalkconn
   416  
   417  	opts := elasticbeanstalk.TerminateEnvironmentInput{
   418  		EnvironmentId:      aws.String(d.Id()),
   419  		TerminateResources: aws.Bool(true),
   420  	}
   421  
   422  	log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts)
   423  	_, err := conn.TerminateEnvironment(&opts)
   424  
   425  	if err != nil {
   426  		return err
   427  	}
   428  
   429  	stateConf := &resource.StateChangeConf{
   430  		Pending:    []string{"Terminating"},
   431  		Target:     []string{"Terminated"},
   432  		Refresh:    environmentStateRefreshFunc(conn, d.Id()),
   433  		Timeout:    10 * time.Minute,
   434  		Delay:      10 * time.Second,
   435  		MinTimeout: 3 * time.Second,
   436  	}
   437  
   438  	_, err = stateConf.WaitForState()
   439  	if err != nil {
   440  		return fmt.Errorf(
   441  			"Error waiting for Elastic Beanstalk Environment (%s) to become terminated: %s",
   442  			d.Id(), err)
   443  	}
   444  
   445  	return nil
   446  }
   447  
   448  // environmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   449  // the creation of the Beanstalk Environment
   450  func environmentStateRefreshFunc(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string) resource.StateRefreshFunc {
   451  	return func() (interface{}, string, error) {
   452  		resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
   453  			EnvironmentIds: []*string{aws.String(environmentId)},
   454  		})
   455  		if err != nil {
   456  			log.Printf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
   457  			return -1, "failed", fmt.Errorf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
   458  		}
   459  
   460  		if resp == nil || len(resp.Environments) == 0 {
   461  			// Sometimes AWS just has consistency issues and doesn't see
   462  			// our instance yet. Return an empty state.
   463  			return nil, "", nil
   464  		}
   465  
   466  		var env *elasticbeanstalk.EnvironmentDescription
   467  		for _, e := range resp.Environments {
   468  			if environmentId == *e.EnvironmentId {
   469  				env = e
   470  			}
   471  		}
   472  
   473  		if env == nil {
   474  			return -1, "failed", fmt.Errorf("[Err] Error finding Elastic Beanstalk Environment, environment not found")
   475  		}
   476  
   477  		return env, *env.Status, nil
   478  	}
   479  }
   480  
   481  // we use the following two functions to allow us to split out defaults
   482  // as they become overridden from within the template
   483  func optionSettingValueHash(v interface{}) int {
   484  	rd := v.(map[string]interface{})
   485  	namespace := rd["namespace"].(string)
   486  	optionName := rd["name"].(string)
   487  	value, _ := rd["value"].(string)
   488  	hk := fmt.Sprintf("%s:%s=%s", namespace, optionName, sortValues(value))
   489  	log.Printf("[DEBUG] Elastic Beanstalk optionSettingValueHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
   490  	return hashcode.String(hk)
   491  }
   492  
   493  func optionSettingKeyHash(v interface{}) int {
   494  	rd := v.(map[string]interface{})
   495  	namespace := rd["namespace"].(string)
   496  	optionName := rd["name"].(string)
   497  	hk := fmt.Sprintf("%s:%s", namespace, optionName)
   498  	log.Printf("[DEBUG] Elastic Beanstalk optionSettingKeyHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
   499  	return hashcode.String(hk)
   500  }
   501  
   502  func sortValues(v string) string {
   503  	values := strings.Split(v, ",")
   504  	sort.Strings(values)
   505  	return strings.Join(values, ",")
   506  }
   507  
   508  func extractOptionSettings(s *schema.Set) []*elasticbeanstalk.ConfigurationOptionSetting {
   509  	settings := []*elasticbeanstalk.ConfigurationOptionSetting{}
   510  
   511  	if s != nil {
   512  		for _, setting := range s.List() {
   513  			settings = append(settings, &elasticbeanstalk.ConfigurationOptionSetting{
   514  				Namespace:  aws.String(setting.(map[string]interface{})["namespace"].(string)),
   515  				OptionName: aws.String(setting.(map[string]interface{})["name"].(string)),
   516  				Value:      aws.String(setting.(map[string]interface{})["value"].(string)),
   517  			})
   518  		}
   519  	}
   520  
   521  	return settings
   522  }
   523  
   524  func dropGeneratedSecurityGroup(settingValue string, meta interface{}) string {
   525  	conn := meta.(*AWSClient).ec2conn
   526  
   527  	groups := strings.Split(settingValue, ",")
   528  
   529  	resp, err := conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
   530  		GroupIds: aws.StringSlice(groups),
   531  	})
   532  
   533  	if err != nil {
   534  		log.Printf("[DEBUG] Elastic Beanstalk error describing SecurityGroups: %v", err)
   535  		return settingValue
   536  	}
   537  
   538  	var legitGroups []string
   539  	for _, group := range resp.SecurityGroups {
   540  		log.Printf("[DEBUG] Elastic Beanstalk SecurityGroup: %v", *group.GroupName)
   541  		if !strings.HasPrefix(*group.GroupName, "awseb") {
   542  			legitGroups = append(legitGroups, *group.GroupId)
   543  		}
   544  	}
   545  
   546  	sort.Strings(legitGroups)
   547  
   548  	return strings.Join(legitGroups, ",")
   549  }