github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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  	NotFoundChecks int              // Number of times to allow not found
    32  
    33  	// This is to work around inconsistent APIs
    34  	ContinuousTargetOccurence int // Number of times the Target state has to occur continuously
    35  }
    36  
    37  // WaitForState watches an object and waits for it to achieve the state
    38  // specified in the configuration using the specified Refresh() func,
    39  // waiting the number of seconds specified in the timeout configuration.
    40  //
    41  // If the Refresh function returns a error, exit immediately with that error.
    42  //
    43  // If the Refresh function returns a state other than the Target state or one
    44  // listed in Pending, return immediately with an error.
    45  //
    46  // If the Timeout is exceeded before reaching the Target state, return an
    47  // error.
    48  //
    49  // Otherwise, result the result of the first call to the Refresh function to
    50  // reach the target state.
    51  func (conf *StateChangeConf) WaitForState() (interface{}, error) {
    52  	log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target)
    53  
    54  	notfoundTick := 0
    55  	targetOccurence := 0
    56  
    57  	// Set a default for times to check for not found
    58  	if conf.NotFoundChecks == 0 {
    59  		conf.NotFoundChecks = 20
    60  	}
    61  
    62  	if conf.ContinuousTargetOccurence == 0 {
    63  		conf.ContinuousTargetOccurence = 1
    64  	}
    65  
    66  	var result interface{}
    67  	var resulterr error
    68  
    69  	doneCh := make(chan struct{})
    70  	go func() {
    71  		defer close(doneCh)
    72  
    73  		// Wait for the delay
    74  		time.Sleep(conf.Delay)
    75  
    76  		var err error
    77  		for tries := 0; ; tries++ {
    78  			// Wait between refreshes using an exponential backoff
    79  			wait := time.Duration(math.Pow(2, float64(tries))) *
    80  				100 * time.Millisecond
    81  			if wait < conf.MinTimeout {
    82  				wait = conf.MinTimeout
    83  			} else if wait > 10*time.Second {
    84  				wait = 10 * time.Second
    85  			}
    86  
    87  			log.Printf("[TRACE] Waiting %s before next try", wait)
    88  			time.Sleep(wait)
    89  
    90  			var currentState string
    91  			result, currentState, err = conf.Refresh()
    92  			if err != nil {
    93  				resulterr = err
    94  				return
    95  			}
    96  
    97  			// If we're waiting for the absence of a thing, then return
    98  			if result == nil && len(conf.Target) == 0 {
    99  				targetOccurence += 1
   100  				if conf.ContinuousTargetOccurence == targetOccurence {
   101  					return
   102  				} else {
   103  					continue
   104  				}
   105  			}
   106  
   107  			if result == nil {
   108  				// If we didn't find the resource, check if we have been
   109  				// not finding it for awhile, and if so, report an error.
   110  				notfoundTick += 1
   111  				if notfoundTick > conf.NotFoundChecks {
   112  					resulterr = errors.New("couldn't find resource")
   113  					return
   114  				}
   115  			} else {
   116  				// Reset the counter for when a resource isn't found
   117  				notfoundTick = 0
   118  				found := false
   119  
   120  				for _, allowed := range conf.Target {
   121  					if currentState == allowed {
   122  						found = true
   123  						targetOccurence += 1
   124  						if conf.ContinuousTargetOccurence == targetOccurence {
   125  							return
   126  						} else {
   127  							continue
   128  						}
   129  					}
   130  				}
   131  
   132  				for _, allowed := range conf.Pending {
   133  					if currentState == allowed {
   134  						found = true
   135  						targetOccurence = 0
   136  						break
   137  					}
   138  				}
   139  
   140  				if !found {
   141  					resulterr = fmt.Errorf(
   142  						"unexpected state '%s', wanted target '%s'",
   143  						currentState,
   144  						conf.Target)
   145  					return
   146  				}
   147  			}
   148  		}
   149  	}()
   150  
   151  	select {
   152  	case <-doneCh:
   153  		return result, resulterr
   154  	case <-time.After(conf.Timeout):
   155  		return nil, fmt.Errorf(
   156  			"timeout while waiting for state to become '%s'",
   157  			conf.Target)
   158  	}
   159  }