github.com/Kevinklinger/open_terraform@v0.11.12-beta1/communicator/communicator.go (about)

     1  package communicator
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/communicator/remote"
    12  	"github.com/hashicorp/terraform/communicator/ssh"
    13  	"github.com/hashicorp/terraform/communicator/winrm"
    14  	"github.com/hashicorp/terraform/terraform"
    15  )
    16  
    17  // Communicator is an interface that must be implemented by all communicators
    18  // used for any of the provisioners
    19  type Communicator interface {
    20  	// Connect is used to setup the connection
    21  	Connect(terraform.UIOutput) error
    22  
    23  	// Disconnect is used to terminate the connection
    24  	Disconnect() error
    25  
    26  	// Timeout returns the configured connection timeout
    27  	Timeout() time.Duration
    28  
    29  	// ScriptPath returns the configured script path
    30  	ScriptPath() string
    31  
    32  	// Start executes a remote command in a new session
    33  	Start(*remote.Cmd) error
    34  
    35  	// Upload is used to upload a single file
    36  	Upload(string, io.Reader) error
    37  
    38  	// UploadScript is used to upload a file as a executable script
    39  	UploadScript(string, io.Reader) error
    40  
    41  	// UploadDir is used to upload a directory
    42  	UploadDir(string, string) error
    43  }
    44  
    45  // New returns a configured Communicator or an error if the connection type is not supported
    46  func New(s *terraform.InstanceState) (Communicator, error) {
    47  	connType := s.Ephemeral.ConnInfo["type"]
    48  	switch connType {
    49  	case "ssh", "": // The default connection type is ssh, so if connType is empty use ssh
    50  		return ssh.New(s)
    51  	case "winrm":
    52  		return winrm.New(s)
    53  	default:
    54  		return nil, fmt.Errorf("connection type '%s' not supported", connType)
    55  	}
    56  }
    57  
    58  // maxBackoffDelay is the maximum delay between retry attempts
    59  var maxBackoffDelay = 20 * time.Second
    60  var initialBackoffDelay = time.Second
    61  
    62  // Fatal is an interface that error values can return to halt Retry
    63  type Fatal interface {
    64  	FatalError() error
    65  }
    66  
    67  // Retry retries the function f until it returns a nil error, a Fatal error, or
    68  // the context expires.
    69  func Retry(ctx context.Context, f func() error) error {
    70  	// container for atomic error value
    71  	type errWrap struct {
    72  		E error
    73  	}
    74  
    75  	// Try the function in a goroutine
    76  	var errVal atomic.Value
    77  	doneCh := make(chan struct{})
    78  	go func() {
    79  		defer close(doneCh)
    80  
    81  		delay := time.Duration(0)
    82  		for {
    83  			// If our context ended, we want to exit right away.
    84  			select {
    85  			case <-ctx.Done():
    86  				return
    87  			case <-time.After(delay):
    88  			}
    89  
    90  			// Try the function call
    91  			err := f()
    92  
    93  			// return if we have no error, or a FatalError
    94  			done := false
    95  			switch e := err.(type) {
    96  			case nil:
    97  				done = true
    98  			case Fatal:
    99  				err = e.FatalError()
   100  				done = true
   101  			}
   102  
   103  			errVal.Store(errWrap{err})
   104  
   105  			if done {
   106  				return
   107  			}
   108  
   109  			log.Printf("[WARN] retryable error: %v", err)
   110  
   111  			delay *= 2
   112  
   113  			if delay == 0 {
   114  				delay = initialBackoffDelay
   115  			}
   116  
   117  			if delay > maxBackoffDelay {
   118  				delay = maxBackoffDelay
   119  			}
   120  
   121  			log.Printf("[INFO] sleeping for %s", delay)
   122  		}
   123  	}()
   124  
   125  	// Wait for completion
   126  	select {
   127  	case <-ctx.Done():
   128  	case <-doneCh:
   129  	}
   130  
   131  	var lastErr error
   132  	// Check if we got an error executing
   133  	if ev, ok := errVal.Load().(errWrap); ok {
   134  		lastErr = ev.E
   135  	}
   136  
   137  	// Check if we have a context error to check if we're interrupted or timeout
   138  	switch ctx.Err() {
   139  	case context.Canceled:
   140  		return fmt.Errorf("interrupted - last error: %v", lastErr)
   141  	case context.DeadlineExceeded:
   142  		return fmt.Errorf("timeout - last error: %v", lastErr)
   143  	}
   144  
   145  	if lastErr != nil {
   146  		return lastErr
   147  	}
   148  	return nil
   149  }