From 01f995fed50b7cbe24f18bc80bbeac52dec2b4b7 Mon Sep 17 00:00:00 2001 From: David Harris Date: Sun, 5 Mar 2017 06:28:37 -0700 Subject: [PATCH] provider/aws: Return errors from Elastic Beanstalk (#12425) In the event that an unexpected state is returned from `environmentStateRefreshFunc` errors in the Elastic Beanstalk console will not be returned to the user. --- ...ource_aws_elastic_beanstalk_environment.go | 83 ++++++++++++++----- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go b/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go index 0d0548ad4..1e016a8e3 100644 --- a/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go +++ b/builtin/providers/aws/resource_aws_elastic_beanstalk_environment.go @@ -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" @@ -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) @@ -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, @@ -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) } @@ -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) @@ -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, @@ -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) @@ -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) @@ -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, @@ -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)}, @@ -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 } } @@ -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)) + + var result *multierror.Error + for _, event := range events { + result = multierror.Append(result, event) } - if events != "" { - return fmt.Errorf("%s", events) - } - - return nil + return result, nil }