terraform/builtin/providers/aws/resource_aws_elastic_beanst...

914 lines
26 KiB
Go

package aws
import (
"fmt"
"log"
"regexp"
"sort"
"strings"
"time"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
)
func resourceAwsElasticBeanstalkOptionSetting() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"namespace": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"value": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"resource": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
}
func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
return &schema.Resource{
Create: resourceAwsElasticBeanstalkEnvironmentCreate,
Read: resourceAwsElasticBeanstalkEnvironmentRead,
Update: resourceAwsElasticBeanstalkEnvironmentUpdate,
Delete: resourceAwsElasticBeanstalkEnvironmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
SchemaVersion: 1,
MigrateState: resourceAwsElasticBeanstalkEnvironmentMigrateState,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"application": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"version_label": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cname": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"cname_prefix": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"tier": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "WebServer",
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
switch value {
case
"Worker",
"WebServer":
return
}
errors = append(errors, fmt.Errorf("%s is not a valid tier. Valid options are WebServer or Worker", value))
return
},
ForceNew: true,
},
"setting": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: resourceAwsElasticBeanstalkOptionSetting(),
Set: optionSettingValueHash,
},
"all_settings": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: resourceAwsElasticBeanstalkOptionSetting(),
Set: optionSettingValueHash,
},
"solution_stack_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ConflictsWith: []string{"template_name"},
},
"template_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"wait_for_ready_timeout": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "20m",
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
duration, err := time.ParseDuration(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q cannot be parsed as a duration: %s", k, err))
}
if duration < 0 {
errors = append(errors, fmt.Errorf(
"%q must be greater than zero", k))
}
return
},
},
"poll_interval": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
duration, err := time.ParseDuration(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q cannot be parsed as a duration: %s", k, err))
}
if duration < 10*time.Second || duration > 60*time.Second {
errors = append(errors, fmt.Errorf(
"%q must be between 10s and 180s", k))
}
return
},
},
"autoscaling_groups": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"instances": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"launch_configurations": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"load_balancers": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"queues": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"triggers": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"tags": tagsSchema(),
},
}
}
func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn
// Get values from config
name := d.Get("name").(string)
cnamePrefix := d.Get("cname_prefix").(string)
tier := d.Get("tier").(string)
app := d.Get("application").(string)
desc := d.Get("description").(string)
version := d.Get("version_label").(string)
settings := d.Get("setting").(*schema.Set)
solutionStack := d.Get("solution_stack_name").(string)
templateName := d.Get("template_name").(string)
// TODO set tags
// Note: at time of writing, you cannot view or edit Tags after creation
// d.Set("tags", tagsToMap(instance.Tags))
createOpts := elasticbeanstalk.CreateEnvironmentInput{
EnvironmentName: aws.String(name),
ApplicationName: aws.String(app),
OptionSettings: extractOptionSettings(settings),
Tags: tagsFromMapBeanstalk(d.Get("tags").(map[string]interface{})),
}
if desc != "" {
createOpts.Description = aws.String(desc)
}
if cnamePrefix != "" {
if tier != "WebServer" {
return fmt.Errorf("Cannot set cname_prefix for tier: %s.", tier)
}
createOpts.CNAMEPrefix = aws.String(cnamePrefix)
}
if tier != "" {
var tierType string
switch tier {
case "WebServer":
tierType = "Standard"
case "Worker":
tierType = "SQS/HTTP"
}
environmentTier := elasticbeanstalk.EnvironmentTier{
Name: aws.String(tier),
Type: aws.String(tierType),
}
createOpts.Tier = &environmentTier
}
if solutionStack != "" {
createOpts.SolutionStackName = aws.String(solutionStack)
}
if templateName != "" {
createOpts.TemplateName = aws.String(templateName)
}
if version != "" {
createOpts.VersionLabel = aws.String(version)
}
// Get the current time to filter getBeanstalkEnvironmentErrors messages
t := time.Now()
log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts)
resp, err := conn.CreateEnvironment(&createOpts)
if err != nil {
return err
}
// Assign the application name as the resource ID
d.SetId(*resp.EnvironmentId)
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
if err != nil {
pollInterval = 0
log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
stateConf := &resource.StateChangeConf{
Pending: []string{"Launching", "Updating"},
Target: []string{"Ready"},
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
d.Id(), err)
}
envErrors, err := getBeanstalkEnvironmentErrors(conn, d.Id(), t)
if err != nil {
return err
}
if envErrors != nil {
return envErrors
}
return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
}
func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn
envId := d.Id()
var hasChange bool
updateOpts := elasticbeanstalk.UpdateEnvironmentInput{
EnvironmentId: aws.String(envId),
}
if d.HasChange("description") {
hasChange = true
updateOpts.Description = aws.String(d.Get("description").(string))
}
if d.HasChange("solution_stack_name") {
hasChange = true
if v, ok := d.GetOk("solution_stack_name"); ok {
updateOpts.SolutionStackName = aws.String(v.(string))
}
}
if d.HasChange("setting") {
hasChange = true
o, n := d.GetChange("setting")
if o == nil {
o = &schema.Set{F: optionSettingValueHash}
}
if n == nil {
n = &schema.Set{F: optionSettingValueHash}
}
os := o.(*schema.Set)
ns := n.(*schema.Set)
rm := extractOptionSettings(os.Difference(ns))
add := extractOptionSettings(ns.Difference(os))
// Additions and removals of options are done in a single API call, so we
// can't do our normal "remove these" and then later "add these", re-adding
// any updated settings.
// Because of this, we need to exclude any settings in the "removable"
// settings that are also found in the "add" settings, otherwise they
// conflict. Here we loop through all the initial removables from the set
// difference, and create a new slice `remove` that contains those settings
// found in `rm` but not in `add`
var remove []*elasticbeanstalk.ConfigurationOptionSetting
if len(add) > 0 {
for _, r := range rm {
var update = false
for _, a := range add {
// ResourceNames are optional. Some defaults come with it, some do
// not. We need to guard against nil/empty in state as well as
// nil/empty from the API
if a.ResourceName != nil {
if r.ResourceName == nil {
continue
}
if *r.ResourceName != *a.ResourceName {
continue
}
}
if *r.Namespace == *a.Namespace && *r.OptionName == *a.OptionName {
log.Printf("[DEBUG] Updating Beanstalk setting (%s::%s) \"%s\" => \"%s\"", *a.Namespace, *a.OptionName, *r.Value, *a.Value)
update = true
break
}
}
// Only remove options that are not updates
if !update {
remove = append(remove, r)
}
}
} else {
remove = rm
}
for _, elem := range remove {
updateOpts.OptionsToRemove = append(updateOpts.OptionsToRemove, &elasticbeanstalk.OptionSpecification{
Namespace: elem.Namespace,
OptionName: elem.OptionName,
})
}
updateOpts.OptionSettings = add
}
if d.HasChange("template_name") {
hasChange = true
if v, ok := d.GetOk("template_name"); ok {
updateOpts.TemplateName = aws.String(v.(string))
}
}
if d.HasChange("version_label") {
hasChange = true
updateOpts.VersionLabel = aws.String(d.Get("version_label").(string))
}
if hasChange {
// Get the current time to filter getBeanstalkEnvironmentErrors messages
t := time.Now()
log.Printf("[DEBUG] Elastic Beanstalk Environment update opts: %s", updateOpts)
_, err := conn.UpdateEnvironment(&updateOpts)
if err != nil {
return err
}
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
if err != nil {
pollInterval = 0
log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
stateConf := &resource.StateChangeConf{
Pending: []string{"Launching", "Updating"},
Target: []string{"Ready"},
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
d.Id(), err)
}
envErrors, err := getBeanstalkEnvironmentErrors(conn, d.Id(), t)
if err != nil {
return err
}
if envErrors != nil {
return envErrors
}
}
return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
}
func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn
envId := d.Id()
log.Printf("[DEBUG] Elastic Beanstalk environment read %s: id %s", d.Get("name").(string), d.Id())
resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
EnvironmentIds: []*string{aws.String(envId)},
})
if err != nil {
return err
}
if len(resp.Environments) == 0 {
log.Printf("[DEBUG] Elastic Beanstalk environment properties: could not find environment %s", d.Id())
d.SetId("")
return nil
} else if len(resp.Environments) != 1 {
return fmt.Errorf("Error reading application properties: found %d environments, expected 1", len(resp.Environments))
}
env := resp.Environments[0]
if *env.Status == "Terminated" {
log.Printf("[DEBUG] Elastic Beanstalk environment %s was terminated", d.Id())
d.SetId("")
return nil
}
resources, err := conn.DescribeEnvironmentResources(&elasticbeanstalk.DescribeEnvironmentResourcesInput{
EnvironmentId: aws.String(envId),
})
if err != nil {
return err
}
if err := d.Set("name", env.EnvironmentName); err != nil {
return err
}
if err := d.Set("application", env.ApplicationName); err != nil {
return err
}
if err := d.Set("description", env.Description); err != nil {
return err
}
if err := d.Set("cname", env.CNAME); err != nil {
return err
}
if err := d.Set("version_label", env.VersionLabel); err != nil {
return err
}
if err := d.Set("tier", *env.Tier.Name); err != nil {
return err
}
if env.CNAME != nil {
beanstalkCnamePrefixRegexp := regexp.MustCompile(`(^[^.]+)(.\w{2}-\w{4,9}-\d)?.elasticbeanstalk.com$`)
var cnamePrefix string
cnamePrefixMatch := beanstalkCnamePrefixRegexp.FindStringSubmatch(*env.CNAME)
if cnamePrefixMatch == nil {
cnamePrefix = ""
} else {
cnamePrefix = cnamePrefixMatch[1]
}
if err := d.Set("cname_prefix", cnamePrefix); err != nil {
return err
}
} else {
if err := d.Set("cname_prefix", ""); err != nil {
return err
}
}
if err := d.Set("solution_stack_name", env.SolutionStackName); err != nil {
return err
}
if err := d.Set("autoscaling_groups", flattenBeanstalkAsg(resources.EnvironmentResources.AutoScalingGroups)); err != nil {
return err
}
if err := d.Set("instances", flattenBeanstalkInstances(resources.EnvironmentResources.Instances)); err != nil {
return err
}
if err := d.Set("launch_configurations", flattenBeanstalkLc(resources.EnvironmentResources.LaunchConfigurations)); err != nil {
return err
}
if err := d.Set("load_balancers", flattenBeanstalkElb(resources.EnvironmentResources.LoadBalancers)); err != nil {
return err
}
if err := d.Set("queues", flattenBeanstalkSqs(resources.EnvironmentResources.Queues)); err != nil {
return err
}
if err := d.Set("triggers", flattenBeanstalkTrigger(resources.EnvironmentResources.Triggers)); err != nil {
return err
}
return resourceAwsElasticBeanstalkEnvironmentSettingsRead(d, meta)
}
func fetchAwsElasticBeanstalkEnvironmentSettings(d *schema.ResourceData, meta interface{}) (*schema.Set, error) {
conn := meta.(*AWSClient).elasticbeanstalkconn
app := d.Get("application").(string)
name := d.Get("name").(string)
resp, err := conn.DescribeConfigurationSettings(&elasticbeanstalk.DescribeConfigurationSettingsInput{
ApplicationName: aws.String(app),
EnvironmentName: aws.String(name),
})
if err != nil {
return nil, err
}
if len(resp.ConfigurationSettings) != 1 {
return nil, fmt.Errorf("Error reading environment settings: received %d settings groups, expected 1", len(resp.ConfigurationSettings))
}
settings := &schema.Set{F: optionSettingValueHash}
for _, optionSetting := range resp.ConfigurationSettings[0].OptionSettings {
m := map[string]interface{}{}
if optionSetting.Namespace != nil {
m["namespace"] = *optionSetting.Namespace
} else {
return nil, fmt.Errorf("Error reading environment settings: option setting with no namespace: %v", optionSetting)
}
if optionSetting.OptionName != nil {
m["name"] = *optionSetting.OptionName
} else {
return nil, fmt.Errorf("Error reading environment settings: option setting with no name: %v", optionSetting)
}
if *optionSetting.Namespace == "aws:autoscaling:scheduledaction" && optionSetting.ResourceName != nil {
m["resource"] = *optionSetting.ResourceName
}
if optionSetting.Value != nil {
switch *optionSetting.OptionName {
case "SecurityGroups":
m["value"] = dropGeneratedSecurityGroup(*optionSetting.Value, meta)
case "Subnets", "ELBSubnets":
m["value"] = sortValues(*optionSetting.Value)
default:
m["value"] = *optionSetting.Value
}
}
settings.Add(m)
}
return settings, nil
}
func resourceAwsElasticBeanstalkEnvironmentSettingsRead(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Elastic Beanstalk environment settings read %s: id %s", d.Get("name").(string), d.Id())
allSettings, err := fetchAwsElasticBeanstalkEnvironmentSettings(d, meta)
if err != nil {
return err
}
settings := d.Get("setting").(*schema.Set)
log.Printf("[DEBUG] Elastic Beanstalk allSettings: %s", allSettings.GoString())
log.Printf("[DEBUG] Elastic Beanstalk settings: %s", settings.GoString())
// perform the set operation with only name/namespace as keys, excluding value
// this is so we override things in the settings resource data key with updated values
// from the api. we skip values we didn't know about before because there are so many
// defaults set by the eb api that we would delete many useful defaults.
//
// there is likely a better way to do this
allSettingsKeySet := schema.NewSet(optionSettingKeyHash, allSettings.List())
settingsKeySet := schema.NewSet(optionSettingKeyHash, settings.List())
updatedSettingsKeySet := allSettingsKeySet.Intersection(settingsKeySet)
log.Printf("[DEBUG] Elastic Beanstalk updatedSettingsKeySet: %s", updatedSettingsKeySet.GoString())
updatedSettings := schema.NewSet(optionSettingValueHash, updatedSettingsKeySet.List())
log.Printf("[DEBUG] Elastic Beanstalk updatedSettings: %s", updatedSettings.GoString())
if err := d.Set("all_settings", allSettings.List()); err != nil {
return err
}
if err := d.Set("setting", updatedSettings.List()); err != nil {
return err
}
return nil
}
func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn
opts := elasticbeanstalk.TerminateEnvironmentInput{
EnvironmentId: aws.String(d.Id()),
TerminateResources: aws.Bool(true),
}
// Get the current time to filter getBeanstalkEnvironmentErrors messages
t := time.Now()
log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts)
_, err := conn.TerminateEnvironment(&opts)
if err != nil {
return err
}
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
if err != nil {
pollInterval = 0
log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
stateConf := &resource.StateChangeConf{
Pending: []string{"Terminating"},
Target: []string{"Terminated"},
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for Elastic Beanstalk Environment (%s) to become terminated: %s",
d.Id(), err)
}
envErrors, err := getBeanstalkEnvironmentErrors(conn, d.Id(), t)
if err != nil {
return err
}
if envErrors != nil {
return envErrors
}
return nil
}
// environmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// the creation of the Beanstalk Environment
func environmentStateRefreshFunc(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string, t time.Time) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
EnvironmentIds: []*string{aws.String(environmentId)},
})
if err != nil {
log.Printf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
return -1, "failed", fmt.Errorf("[Err] Error waiting for Elastic Beanstalk Environment state: %s", err)
}
if resp == nil || len(resp.Environments) == 0 {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
var env *elasticbeanstalk.EnvironmentDescription
for _, e := range resp.Environments {
if environmentId == *e.EnvironmentId {
env = e
}
}
if env == nil {
return -1, "failed", fmt.Errorf("[Err] Error finding Elastic Beanstalk Environment, environment not found")
}
envErrors, err := getBeanstalkEnvironmentErrors(conn, environmentId, t)
if err != nil {
return -1, "failed", err
}
if envErrors != nil {
return -1, "failed", envErrors
}
return env, *env.Status, nil
}
}
// we use the following two functions to allow us to split out defaults
// as they become overridden from within the template
func optionSettingValueHash(v interface{}) int {
rd := v.(map[string]interface{})
namespace := rd["namespace"].(string)
optionName := rd["name"].(string)
var resourceName string
if v, ok := rd["resource"].(string); ok {
resourceName = v
}
value, _ := rd["value"].(string)
hk := fmt.Sprintf("%s:%s%s=%s", namespace, optionName, resourceName, sortValues(value))
log.Printf("[DEBUG] Elastic Beanstalk optionSettingValueHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
return hashcode.String(hk)
}
func optionSettingKeyHash(v interface{}) int {
rd := v.(map[string]interface{})
namespace := rd["namespace"].(string)
optionName := rd["name"].(string)
var resourceName string
if v, ok := rd["resource"].(string); ok {
resourceName = v
}
hk := fmt.Sprintf("%s:%s%s", namespace, optionName, resourceName)
log.Printf("[DEBUG] Elastic Beanstalk optionSettingKeyHash(%#v): %s: hk=%s,hc=%d", v, optionName, hk, hashcode.String(hk))
return hashcode.String(hk)
}
func sortValues(v string) string {
values := strings.Split(v, ",")
sort.Strings(values)
return strings.Join(values, ",")
}
func extractOptionSettings(s *schema.Set) []*elasticbeanstalk.ConfigurationOptionSetting {
settings := []*elasticbeanstalk.ConfigurationOptionSetting{}
if s != nil {
for _, setting := range s.List() {
optionSetting := elasticbeanstalk.ConfigurationOptionSetting{
Namespace: aws.String(setting.(map[string]interface{})["namespace"].(string)),
OptionName: aws.String(setting.(map[string]interface{})["name"].(string)),
Value: aws.String(setting.(map[string]interface{})["value"].(string)),
}
if *optionSetting.Namespace == "aws:autoscaling:scheduledaction" {
if v, ok := setting.(map[string]interface{})["resource"].(string); ok && v != "" {
optionSetting.ResourceName = aws.String(v)
}
}
settings = append(settings, &optionSetting)
}
}
return settings
}
func dropGeneratedSecurityGroup(settingValue string, meta interface{}) string {
conn := meta.(*AWSClient).ec2conn
groups := strings.Split(settingValue, ",")
// Check to see if groups are ec2-classic or vpc security groups
ec2Classic := true
beanstalkSGRegexp := "sg-[0-9a-fA-F]{8}"
for _, g := range groups {
if ok, _ := regexp.MatchString(beanstalkSGRegexp, g); ok {
ec2Classic = false
break
}
}
var resp *ec2.DescribeSecurityGroupsOutput
var err error
if ec2Classic {
resp, err = conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
GroupNames: aws.StringSlice(groups),
})
} else {
resp, err = conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
GroupIds: aws.StringSlice(groups),
})
}
if err != nil {
log.Printf("[DEBUG] Elastic Beanstalk error describing SecurityGroups: %v", err)
return settingValue
}
log.Printf("[DEBUG] Elastic Beanstalk using ec2-classic security-groups: %t", ec2Classic)
var legitGroups []string
for _, group := range resp.SecurityGroups {
log.Printf("[DEBUG] Elastic Beanstalk SecurityGroup: %v", *group.GroupName)
if !strings.HasPrefix(*group.GroupName, "awseb") {
if ec2Classic {
legitGroups = append(legitGroups, *group.GroupName)
} else {
legitGroups = append(legitGroups, *group.GroupId)
}
}
}
sort.Strings(legitGroups)
return strings.Join(legitGroups, ",")
}
type beanstalkEnvironmentError struct {
eventDate *time.Time
environmentID string
message *string
}
func (e beanstalkEnvironmentError) Error() string {
return e.eventDate.String() + " (" + e.environmentID + ") : " + *e.message
}
type beanstalkEnvironmentErrors []*beanstalkEnvironmentError
func (e beanstalkEnvironmentErrors) Len() int { return len(e) }
func (e beanstalkEnvironmentErrors) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (e beanstalkEnvironmentErrors) Less(i, j int) bool { return e[i].eventDate.Before(*e[j].eventDate) }
func getBeanstalkEnvironmentErrors(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string, t time.Time) (*multierror.Error, error) {
environmentErrors, err := conn.DescribeEvents(&elasticbeanstalk.DescribeEventsInput{
EnvironmentId: aws.String(environmentId),
Severity: aws.String("ERROR"),
StartTime: aws.Time(t),
})
if err != nil {
return nil, fmt.Errorf("[Err] Unable to get Elastic Beanstalk Evironment events: %s", err)
}
var events beanstalkEnvironmentErrors
for _, event := range environmentErrors.Events {
e := &beanstalkEnvironmentError{
eventDate: event.EventDate,
environmentID: environmentId,
message: event.Message,
}
events = append(events, e)
}
sort.Sort(beanstalkEnvironmentErrors(events))
var result *multierror.Error
for _, event := range events {
result = multierror.Append(result, event)
}
return result, nil
}