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 }