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 }