github.com/jbronn/packer@v0.1.6-0.20140120165540-8a1364dbd817/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  // WaitForState watches an object and waits for it to achieve a certain
    85  // state.
    86  func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
    87  	log.Printf("Waiting for state to become: %s", conf.Target)
    88  
    89  	notfoundTick := 0
    90  
    91  	for {
    92  		var currentState string
    93  		i, currentState, err = conf.Refresh()
    94  		if err != nil {
    95  			return
    96  		}
    97  
    98  		if i == nil {
    99  			// If we didn't find the resource, check if we have been
   100  			// not finding it for awhile, and if so, report an error.
   101  			notfoundTick += 1
   102  			if notfoundTick > 20 {
   103  				return nil, errors.New("couldn't find resource")
   104  			}
   105  		} else {
   106  			// Reset the counter for when a resource isn't found
   107  			notfoundTick = 0
   108  
   109  			if currentState == conf.Target {
   110  				return
   111  			}
   112  
   113  			if conf.StepState != nil {
   114  				if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
   115  					return nil, errors.New("interrupted")
   116  				}
   117  			}
   118  
   119  			found := false
   120  			for _, allowed := range conf.Pending {
   121  				if currentState == allowed {
   122  					found = true
   123  					break
   124  				}
   125  			}
   126  
   127  			if !found {
   128  				fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
   129  				return
   130  			}
   131  		}
   132  
   133  		time.Sleep(2 * time.Second)
   134  	}
   135  
   136  	return
   137  }