github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/amazon/common/state.go (about)

     1  package common
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/mitchellh/goamz/ec2"
     7  	"github.com/mitchellh/multistep"
     8  	"log"
     9  	"time"
    10  )
    11  
    12  // StateRefreshFunc is a function type used for StateChangeConf that is
    13  // responsible for refreshing the item being watched for a state change.
    14  //
    15  // It returns three results. `result` is any object that will be returned
    16  // as the final object after waiting for state change. This allows you to
    17  // return the final updated object, for example an EC2 instance after refreshing
    18  // it.
    19  //
    20  // `state` is the latest state of that object. And `err` is any error that
    21  // may have happened while refreshing the state.
    22  type StateRefreshFunc func() (result interface{}, state string, err error)
    23  
    24  // StateChangeConf is the configuration struct used for `WaitForState`.
    25  type StateChangeConf struct {
    26  	Pending   []string
    27  	Refresh   StateRefreshFunc
    28  	StepState multistep.StateBag
    29  	Target    string
    30  }
    31  
    32  // AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
    33  // an AMI for state changes.
    34  func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
    35  	return func() (interface{}, string, error) {
    36  		resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
    37  		if err != nil {
    38  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
    39  				// Set this to nil as if we didn't find anything.
    40  				resp = nil
    41  			} else {
    42  				log.Printf("Error on AMIStateRefresh: %s", err)
    43  				return nil, "", err
    44  			}
    45  		}
    46  
    47  		if resp == nil || len(resp.Images) == 0 {
    48  			// Sometimes AWS has consistency issues and doesn't see the
    49  			// AMI. Return an empty state.
    50  			return nil, "", nil
    51  		}
    52  
    53  		i := resp.Images[0]
    54  		return i, i.State, nil
    55  	}
    56  }
    57  
    58  // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
    59  // an EC2 instance.
    60  func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
    61  	return func() (interface{}, string, error) {
    62  		resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
    63  		if err != nil {
    64  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
    65  				// Set this to nil as if we didn't find anything.
    66  				resp = nil
    67  			} else {
    68  				log.Printf("Error on InstanceStateRefresh: %s", err)
    69  				return nil, "", err
    70  			}
    71  		}
    72  
    73  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
    74  			// Sometimes AWS just has consistency issues and doesn't see
    75  			// our instance yet. Return an empty state.
    76  			return nil, "", nil
    77  		}
    78  
    79  		i = &resp.Reservations[0].Instances[0]
    80  		return i, i.State.Name, nil
    81  	}
    82  }
    83  
    84  // SpotRequestStateRefreshFunc returns a StateRefreshFunc that is used to watch
    85  // a spot request for state changes.
    86  func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc {
    87  	return func() (interface{}, string, error) {
    88  		resp, err := conn.DescribeSpotRequests([]string{spotRequestId}, ec2.NewFilter())
    89  		if err != nil {
    90  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" {
    91  				// Set this to nil as if we didn't find anything.
    92  				resp = nil
    93  			} else {
    94  				log.Printf("Error on SpotRequestStateRefresh: %s", err)
    95  				return nil, "", err
    96  			}
    97  		}
    98  
    99  		if resp == nil || len(resp.SpotRequestResults) == 0 {
   100  			// Sometimes AWS has consistency issues and doesn't see the
   101  			// SpotRequest. Return an empty state.
   102  			return nil, "", nil
   103  		}
   104  
   105  		i := resp.SpotRequestResults[0]
   106  		return i, i.State, nil
   107  	}
   108  }
   109  
   110  // WaitForState watches an object and waits for it to achieve a certain
   111  // state.
   112  func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
   113  	log.Printf("Waiting for state to become: %s", conf.Target)
   114  
   115  	notfoundTick := 0
   116  
   117  	for {
   118  		var currentState string
   119  		i, currentState, err = conf.Refresh()
   120  		if err != nil {
   121  			return
   122  		}
   123  
   124  		if i == nil {
   125  			// If we didn't find the resource, check if we have been
   126  			// not finding it for awhile, and if so, report an error.
   127  			notfoundTick += 1
   128  			if notfoundTick > 20 {
   129  				return nil, errors.New("couldn't find resource")
   130  			}
   131  		} else {
   132  			// Reset the counter for when a resource isn't found
   133  			notfoundTick = 0
   134  
   135  			if currentState == conf.Target {
   136  				return
   137  			}
   138  
   139  			if conf.StepState != nil {
   140  				if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
   141  					return nil, errors.New("interrupted")
   142  				}
   143  			}
   144  
   145  			found := false
   146  			for _, allowed := range conf.Pending {
   147  				if currentState == allowed {
   148  					found = true
   149  					break
   150  				}
   151  			}
   152  
   153  			if !found {
   154  				err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
   155  				return nil, err
   156  			}
   157  		}
   158  
   159  		time.Sleep(2 * time.Second)
   160  	}
   161  
   162  	return
   163  }