github.com/scottwinkler/terraform@v0.11.6-0.20180329211809-05143987aea8/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 }