github.com/pulumi/terraform@v1.4.0/pkg/communicator/communicator.go (about)

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