github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/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  	Conn      *ec2.EC2
    27  	Pending   []string
    28  	Refresh   StateRefreshFunc
    29  	StepState multistep.StateBag
    30  	Target    string
    31  }
    32  
    33  // AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
    34  // an AMI for state changes.
    35  func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
    36  	return func() (interface{}, string, error) {
    37  		resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
    38  		if err != nil {
    39  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
    40  				// Set this to nil as if we didn't find anything.
    41  				resp = nil
    42  			} else {
    43  				log.Printf("Error on AMIStateRefresh: %s", err)
    44  				return nil, "", err
    45  			}
    46  		}
    47  
    48  		if resp == nil || len(resp.Images) == 0 {
    49  			// Sometimes AWS has consistency issues and doesn't see the
    50  			// AMI. Return an empty state.
    51  			return nil, "", nil
    52  		}
    53  
    54  		i := resp.Images[0]
    55  		return i, i.State, nil
    56  	}
    57  }
    58  
    59  // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
    60  // an EC2 instance.
    61  func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
    62  	return func() (interface{}, string, error) {
    63  		resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
    64  		if err != nil {
    65  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
    66  				// Set this to nil as if we didn't find anything.
    67  				resp = nil
    68  			} else {
    69  				log.Printf("Error on InstanceStateRefresh: %s", err)
    70  				return nil, "", err
    71  			}
    72  		}
    73  
    74  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
    75  			// Sometimes AWS just has consistency issues and doesn't see
    76  			// our instance yet. Return an empty state.
    77  			return nil, "", nil
    78  		}
    79  
    80  		i = &resp.Reservations[0].Instances[0]
    81  		return i, i.State.Name, nil
    82  	}
    83  }
    84  
    85  // WaitForState watches an object and waits for it to achieve a certain
    86  // state.
    87  func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
    88  	log.Printf("Waiting for state to become: %s", conf.Target)
    89  
    90  	notfoundTick := 0
    91  
    92  	for {
    93  		var currentState string
    94  		i, currentState, err = conf.Refresh()
    95  		if err != nil {
    96  			return
    97  		}
    98  
    99  		if i == nil {
   100  			// If we didn't find the resource, check if we have been
   101  			// not finding it for awhile, and if so, report an error.
   102  			notfoundTick += 1
   103  			if notfoundTick > 20 {
   104  				return nil, errors.New("couldn't find resource")
   105  			}
   106  		} else {
   107  			// Reset the counter for when a resource isn't found
   108  			notfoundTick = 0
   109  
   110  			if currentState == conf.Target {
   111  				return
   112  			}
   113  
   114  			if conf.StepState != nil {
   115  				if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
   116  					return nil, errors.New("interrupted")
   117  				}
   118  			}
   119  
   120  			found := false
   121  			for _, allowed := range conf.Pending {
   122  				if currentState == allowed {
   123  					found = true
   124  					break
   125  				}
   126  			}
   127  
   128  			if !found {
   129  				fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
   130  				return
   131  			}
   132  		}
   133  
   134  		time.Sleep(2 * time.Second)
   135  	}
   136  
   137  	return
   138  }