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