mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
e81162c4e1
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
162 lines
3.6 KiB
Go
162 lines
3.6 KiB
Go
package communicator
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/internal/communicator/remote"
|
|
"github.com/hashicorp/terraform/internal/communicator/shared"
|
|
"github.com/hashicorp/terraform/internal/communicator/ssh"
|
|
"github.com/hashicorp/terraform/internal/communicator/winrm"
|
|
"github.com/hashicorp/terraform/internal/provisioners"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// Communicator is an interface that must be implemented by all communicators
|
|
// used for any of the provisioners
|
|
type Communicator interface {
|
|
// Connect is used to set up the connection
|
|
Connect(provisioners.UIOutput) error
|
|
|
|
// Disconnect is used to terminate the connection
|
|
Disconnect() error
|
|
|
|
// Timeout returns the configured connection timeout
|
|
Timeout() time.Duration
|
|
|
|
// ScriptPath returns the configured script path
|
|
ScriptPath() string
|
|
|
|
// Start executes a remote command in a new session
|
|
Start(*remote.Cmd) error
|
|
|
|
// Upload is used to upload a single file
|
|
Upload(string, io.Reader) error
|
|
|
|
// UploadScript is used to upload a file as an executable script
|
|
UploadScript(string, io.Reader) error
|
|
|
|
// UploadDir is used to upload a directory
|
|
UploadDir(string, string) error
|
|
}
|
|
|
|
// New returns a configured Communicator or an error if the connection type is not supported
|
|
func New(v cty.Value) (Communicator, error) {
|
|
v, err := shared.ConnectionBlockSupersetSchema.CoerceValue(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
typeVal := v.GetAttr("type")
|
|
connType := ""
|
|
if !typeVal.IsNull() {
|
|
connType = typeVal.AsString()
|
|
}
|
|
|
|
switch connType {
|
|
case "ssh", "": // The default connection type is ssh, so if connType is empty use ssh
|
|
return ssh.New(v)
|
|
case "winrm":
|
|
return winrm.New(v)
|
|
default:
|
|
return nil, fmt.Errorf("connection type '%s' not supported", connType)
|
|
}
|
|
}
|
|
|
|
// maxBackoffDelay is the maximum delay between retry attempts
|
|
var maxBackoffDelay = 20 * time.Second
|
|
var initialBackoffDelay = time.Second
|
|
|
|
// Fatal is an interface that error values can return to halt Retry
|
|
type Fatal interface {
|
|
FatalError() error
|
|
}
|
|
|
|
// Retry retries the function f until it returns a nil error, a Fatal error, or
|
|
// the context expires.
|
|
func Retry(ctx context.Context, f func() error) error {
|
|
// container for atomic error value
|
|
type errWrap struct {
|
|
E error
|
|
}
|
|
|
|
// Try the function in a goroutine
|
|
var errVal atomic.Value
|
|
doneCh := make(chan struct{})
|
|
go func() {
|
|
defer close(doneCh)
|
|
|
|
delay := time.Duration(0)
|
|
for {
|
|
// If our context ended, we want to exit right away.
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(delay):
|
|
}
|
|
|
|
// Try the function call
|
|
err := f()
|
|
|
|
// return if we have no error, or a FatalError
|
|
done := false
|
|
switch e := err.(type) {
|
|
case nil:
|
|
done = true
|
|
case Fatal:
|
|
err = e.FatalError()
|
|
done = true
|
|
}
|
|
|
|
errVal.Store(errWrap{err})
|
|
|
|
if done {
|
|
return
|
|
}
|
|
|
|
log.Printf("[WARN] retryable error: %v", err)
|
|
|
|
delay *= 2
|
|
|
|
if delay == 0 {
|
|
delay = initialBackoffDelay
|
|
}
|
|
|
|
if delay > maxBackoffDelay {
|
|
delay = maxBackoffDelay
|
|
}
|
|
|
|
log.Printf("[INFO] sleeping for %s", delay)
|
|
}
|
|
}()
|
|
|
|
// Wait for completion
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-doneCh:
|
|
}
|
|
|
|
var lastErr error
|
|
// Check if we got an error executing
|
|
if ev, ok := errVal.Load().(errWrap); ok {
|
|
lastErr = ev.E
|
|
}
|
|
|
|
// Check if we have a context error to check if we're interrupted or timeout
|
|
switch ctx.Err() {
|
|
case context.Canceled:
|
|
return fmt.Errorf("interrupted - last error: %v", lastErr)
|
|
case context.DeadlineExceeded:
|
|
return fmt.Errorf("timeout - last error: %v", lastErr)
|
|
}
|
|
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return nil
|
|
}
|