Skip to content

Commit

Permalink
provider/aws: Return errors from Elastic Beanstalk (#12425)
Browse files Browse the repository at this point in the history
In the event that an unexpected state is returned from
`environmentStateRefreshFunc` errors in the Elastic Beanstalk console
will not be returned to the user.
  • Loading branch information
dharrisio authored and stack72 committed Mar 5, 2017
1 parent 15097ee commit c6aa3c5
Showing 1 changed file with 60 additions and 19 deletions.
79 changes: 60 additions & 19 deletions builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"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"
Expand Down Expand Up @@ -255,7 +256,7 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
createOpts.VersionLabel = aws.String(version)
}

// Get the current time to filter describeBeanstalkEvents messages
// 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)
Expand All @@ -280,7 +281,7 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
stateConf := &resource.StateChangeConf{
Pending: []string{"Launching", "Updating"},
Target: []string{"Ready"},
Refresh: environmentStateRefreshFunc(conn, d.Id()),
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
Expand All @@ -294,10 +295,13 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
d.Id(), err)
}

err = describeBeanstalkEvents(conn, d.Id(), t)
envErrors, err := getBeanstalkEnvironmentErrors(conn, d.Id(), t)
if err != nil {
return err
}
if envErrors != nil {
return envErrors
}

return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
}
Expand Down Expand Up @@ -403,7 +407,7 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
}

if hasChange {
// Get the current time to filter describeBeanstalkEvents messages
// 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)
Expand All @@ -424,7 +428,7 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
stateConf := &resource.StateChangeConf{
Pending: []string{"Launching", "Updating"},
Target: []string{"Ready"},
Refresh: environmentStateRefreshFunc(conn, d.Id()),
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
Expand All @@ -438,10 +442,13 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
d.Id(), err)
}

err = describeBeanstalkEvents(conn, d.Id(), t)
envErrors, err := getBeanstalkEnvironmentErrors(conn, d.Id(), t)
if err != nil {
return err
}
if envErrors != nil {
return envErrors
}
}

return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
Expand Down Expand Up @@ -663,7 +670,7 @@ func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta i
TerminateResources: aws.Bool(true),
}

// Get the current time to filter describeBeanstalkEvents messages
// 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)
Expand All @@ -685,7 +692,7 @@ func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta i
stateConf := &resource.StateChangeConf{
Pending: []string{"Terminating"},
Target: []string{"Terminated"},
Refresh: environmentStateRefreshFunc(conn, d.Id()),
Refresh: environmentStateRefreshFunc(conn, d.Id(), t),
Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second,
PollInterval: pollInterval,
Expand All @@ -699,17 +706,20 @@ func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta i
d.Id(), err)
}

err = describeBeanstalkEvents(conn, d.Id(), t)
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) resource.StateRefreshFunc {
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)},
Expand All @@ -736,6 +746,14 @@ func environmentStateRefreshFunc(conn *elasticbeanstalk.ElasticBeanstalk, enviro
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
}
}
Expand Down Expand Up @@ -848,25 +866,48 @@ func dropGeneratedSecurityGroup(settingValue string, meta interface{}) string {
return strings.Join(legitGroups, ",")
}

func describeBeanstalkEvents(conn *elasticbeanstalk.ElasticBeanstalk, environmentId string, t time.Time) error {
beanstalkErrors, err := conn.DescribeEvents(&elasticbeanstalk.DescribeEventsInput{
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 {
log.Printf("[Err] Unable to get Elastic Beanstalk Evironment events: %s", err)
return nil, fmt.Errorf("[Err] Unable to get Elastic Beanstalk Evironment events: %s", err)
}

events := ""
for _, event := range beanstalkErrors.Events {
events = events + "\n" + event.EventDate.String() + ": " + *event.Message
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))

if events != "" {
return fmt.Errorf("%s", events)
var result *multierror.Error
for _, event := range events {
result = multierror.Append(result, event)
}

return nil
return result, nil
}

0 comments on commit c6aa3c5

Please sign in to comment.