github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/helper/resource/state.go (about)

     1  package resource
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"math"
     8  	"time"
     9  )
    10  
    11  // StateRefreshFunc is a function type used for StateChangeConf that is
    12  // responsible for refreshing the item being watched for a state change.
    13  //
    14  // It returns three results. `result` is any object that will be returned
    15  // as the final object after waiting for state change. This allows you to
    16  // return the final updated object, for example an EC2 instance after refreshing
    17  // it.
    18  //
    19  // `state` is the latest state of that object. And `err` is any error that
    20  // may have happened while refreshing the state.
    21  type StateRefreshFunc func() (result interface{}, state string, err error)
    22  
    23  // StateChangeConf is the configuration struct used for `WaitForState`.
    24  type StateChangeConf struct {
    25  	Delay      time.Duration    // Wait this time before starting checks
    26  	Pending    []string         // States that are "allowed" and will continue trying
    27  	Refresh    StateRefreshFunc // Refreshes the current state
    28  	Target     string           // Target state
    29  	Timeout    time.Duration    // The amount of time to wait before timeout
    30  	MinTimeout time.Duration    // Smallest time to wait before refreshes
    31  }
    32  
    33  // WaitForState watches an object and waits for it to achieve the state
    34  // specified in the configuration using the specified Refresh() func,
    35  // waiting the number of seconds specified in the timeout configuration.
    36  func (conf *StateChangeConf) WaitForState() (interface{}, error) {
    37  	log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target)
    38  
    39  	notfoundTick := 0
    40  
    41  	var result interface{}
    42  	var resulterr error
    43  
    44  	doneCh := make(chan struct{})
    45  	go func() {
    46  		defer close(doneCh)
    47  
    48  		// Wait for the delay
    49  		time.Sleep(conf.Delay)
    50  
    51  		var err error
    52  		for tries := 0; ; tries++ {
    53  			// Wait between refreshes using an exponential backoff
    54  			wait := time.Duration(math.Pow(2, float64(tries))) *
    55  				100 * time.Millisecond
    56  			if wait < conf.MinTimeout {
    57  				wait = conf.MinTimeout
    58  			} else if wait > 10*time.Second {
    59  				wait = 10 * time.Second
    60  			}
    61  
    62  			log.Printf("[TRACE] Waiting %s before next try", wait)
    63  			time.Sleep(wait)
    64  
    65  			var currentState string
    66  			result, currentState, err = conf.Refresh()
    67  			if err != nil {
    68  				resulterr = err
    69  				return
    70  			}
    71  
    72  			// If we're waiting for the absence of a thing, then return
    73  			if result == nil && conf.Target == "" {
    74  				return
    75  			}
    76  
    77  			if result == nil {
    78  				// If we didn't find the resource, check if we have been
    79  				// not finding it for awhile, and if so, report an error.
    80  				notfoundTick += 1
    81  				if notfoundTick > 20 {
    82  					resulterr = errors.New("couldn't find resource")
    83  					return
    84  				}
    85  			} else {
    86  				// Reset the counter for when a resource isn't found
    87  				notfoundTick = 0
    88  
    89  				if currentState == conf.Target {
    90  					return
    91  				}
    92  
    93  				found := false
    94  				for _, allowed := range conf.Pending {
    95  					if currentState == allowed {
    96  						found = true
    97  						break
    98  					}
    99  				}
   100  
   101  				if !found {
   102  					resulterr = fmt.Errorf(
   103  						"unexpected state '%s', wanted target '%s'",
   104  						currentState,
   105  						conf.Target)
   106  					return
   107  				}
   108  			}
   109  		}
   110  	}()
   111  
   112  	select {
   113  	case <-doneCh:
   114  		return result, resulterr
   115  	case <-time.After(conf.Timeout):
   116  		return nil, fmt.Errorf(
   117  			"timeout while waiting for state to become '%s'",
   118  			conf.Target)
   119  	}
   120  }