github.com/KevinKlinger/open_terraform@v0.11.12-beta1/state/state.go (about)

     1  package state
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"math/rand"
    10  	"os"
    11  	"os/user"
    12  	"strings"
    13  	"text/template"
    14  	"time"
    15  
    16  	uuid "github.com/hashicorp/go-uuid"
    17  	"github.com/hashicorp/terraform/terraform"
    18  	"github.com/hashicorp/terraform/version"
    19  )
    20  
    21  var rngSource *rand.Rand
    22  
    23  func init() {
    24  	rngSource = rand.New(rand.NewSource(time.Now().UnixNano()))
    25  }
    26  
    27  // State is the collection of all state interfaces.
    28  type State interface {
    29  	StateReader
    30  	StateWriter
    31  	StateRefresher
    32  	StatePersister
    33  	Locker
    34  }
    35  
    36  // StateReader is the interface for things that can return a state. Retrieving
    37  // the state here must not error. Loading the state fresh (an operation that
    38  // can likely error) should be implemented by RefreshState. If a state hasn't
    39  // been loaded yet, it is okay for State to return nil.
    40  //
    41  // Each caller of this function must get a distinct copy of the state, and
    42  // it must also be distinct from any instance cached inside the reader, to
    43  // ensure that mutations of the returned state will not affect the values
    44  // returned to other callers.
    45  type StateReader interface {
    46  	State() *terraform.State
    47  }
    48  
    49  // StateWriter is the interface that must be implemented by something that
    50  // can write a state. Writing the state can be cached or in-memory, as
    51  // full persistence should be implemented by StatePersister.
    52  //
    53  // Implementors that cache the state in memory _must_ take a copy of it
    54  // before returning, since the caller may continue to modify it once
    55  // control returns. The caller must ensure that the state instance is not
    56  // concurrently modified _during_ the call, or behavior is undefined.
    57  //
    58  // If an object implements StatePersister in conjunction with StateReader
    59  // then these methods must coordinate such that a subsequent read returns
    60  // a copy of the most recent write, even if it has not yet been persisted.
    61  type StateWriter interface {
    62  	WriteState(*terraform.State) error
    63  }
    64  
    65  // StateRefresher is the interface that is implemented by something that
    66  // can load a state. This might be refreshing it from a remote location or
    67  // it might simply be reloading it from disk.
    68  type StateRefresher interface {
    69  	RefreshState() error
    70  }
    71  
    72  // StatePersister is implemented to truly persist a state. Whereas StateWriter
    73  // is allowed to perhaps be caching in memory, PersistState must write the
    74  // state to some durable storage.
    75  //
    76  // If an object implements StatePersister in conjunction with StateReader
    77  // and/or StateRefresher then these methods must coordinate such that
    78  // subsequent reads after a persist return an updated value.
    79  type StatePersister interface {
    80  	PersistState() error
    81  }
    82  
    83  // Locker is implemented to lock state during command execution.
    84  // The info parameter can be recorded with the lock, but the
    85  // implementation should not depend in its value. The string returned by Lock
    86  // is an ID corresponding to the lock acquired, and must be passed to Unlock to
    87  // ensure that the correct lock is being released.
    88  //
    89  // Lock and Unlock may return an error value of type LockError which in turn
    90  // can contain the LockInfo of a conflicting lock.
    91  type Locker interface {
    92  	Lock(info *LockInfo) (string, error)
    93  	Unlock(id string) error
    94  }
    95  
    96  // test hook to verify that LockWithContext has attempted a lock
    97  var postLockHook func()
    98  
    99  // Lock the state, using the provided context for timeout and cancellation.
   100  // This backs off slightly to an upper limit.
   101  func LockWithContext(ctx context.Context, s State, info *LockInfo) (string, error) {
   102  	delay := time.Second
   103  	maxDelay := 16 * time.Second
   104  	for {
   105  		id, err := s.Lock(info)
   106  		if err == nil {
   107  			return id, nil
   108  		}
   109  
   110  		le, ok := err.(*LockError)
   111  		if !ok {
   112  			// not a lock error, so we can't retry
   113  			return "", err
   114  		}
   115  
   116  		if le == nil || le.Info == nil || le.Info.ID == "" {
   117  			// If we dont' have a complete LockError, there's something wrong with the lock
   118  			return "", err
   119  		}
   120  
   121  		if postLockHook != nil {
   122  			postLockHook()
   123  		}
   124  
   125  		// there's an existing lock, wait and try again
   126  		select {
   127  		case <-ctx.Done():
   128  			// return the last lock error with the info
   129  			return "", err
   130  		case <-time.After(delay):
   131  			if delay < maxDelay {
   132  				delay *= 2
   133  			}
   134  		}
   135  	}
   136  }
   137  
   138  // Generate a LockInfo structure, populating the required fields.
   139  func NewLockInfo() *LockInfo {
   140  	// this doesn't need to be cryptographically secure, just unique.
   141  	// Using math/rand alleviates the need to check handle the read error.
   142  	// Use a uuid format to match other IDs used throughout Terraform.
   143  	buf := make([]byte, 16)
   144  	rngSource.Read(buf)
   145  
   146  	id, err := uuid.FormatUUID(buf)
   147  	if err != nil {
   148  		// this of course shouldn't happen
   149  		panic(err)
   150  	}
   151  
   152  	// don't error out on user and hostname, as we don't require them
   153  	userName := ""
   154  	if userInfo, err := user.Current(); err == nil {
   155  		userName = userInfo.Username
   156  	}
   157  	host, _ := os.Hostname()
   158  
   159  	info := &LockInfo{
   160  		ID:      id,
   161  		Who:     fmt.Sprintf("%s@%s", userName, host),
   162  		Version: version.Version,
   163  		Created: time.Now().UTC(),
   164  	}
   165  	return info
   166  }
   167  
   168  // LockInfo stores lock metadata.
   169  //
   170  // Only Operation and Info are required to be set by the caller of Lock.
   171  type LockInfo struct {
   172  	// Unique ID for the lock. NewLockInfo provides a random ID, but this may
   173  	// be overridden by the lock implementation. The final value if ID will be
   174  	// returned by the call to Lock.
   175  	ID string
   176  
   177  	// Terraform operation, provided by the caller.
   178  	Operation string
   179  	// Extra information to store with the lock, provided by the caller.
   180  	Info string
   181  
   182  	// user@hostname when available
   183  	Who string
   184  	// Terraform version
   185  	Version string
   186  	// Time that the lock was taken.
   187  	Created time.Time
   188  
   189  	// Path to the state file when applicable. Set by the Lock implementation.
   190  	Path string
   191  }
   192  
   193  // Err returns the lock info formatted in an error
   194  func (l *LockInfo) Err() error {
   195  	return errors.New(l.String())
   196  }
   197  
   198  // Marshal returns a string json representation of the LockInfo
   199  func (l *LockInfo) Marshal() []byte {
   200  	js, err := json.Marshal(l)
   201  	if err != nil {
   202  		panic(err)
   203  	}
   204  	return js
   205  }
   206  
   207  // String return a multi-line string representation of LockInfo
   208  func (l *LockInfo) String() string {
   209  	tmpl := `Lock Info:
   210    ID:        {{.ID}}
   211    Path:      {{.Path}}
   212    Operation: {{.Operation}}
   213    Who:       {{.Who}}
   214    Version:   {{.Version}}
   215    Created:   {{.Created}}
   216    Info:      {{.Info}}
   217  `
   218  
   219  	t := template.Must(template.New("LockInfo").Parse(tmpl))
   220  	var out bytes.Buffer
   221  	if err := t.Execute(&out, l); err != nil {
   222  		panic(err)
   223  	}
   224  	return out.String()
   225  }
   226  
   227  type LockError struct {
   228  	Info *LockInfo
   229  	Err  error
   230  }
   231  
   232  func (e *LockError) Error() string {
   233  	var out []string
   234  	if e.Err != nil {
   235  		out = append(out, e.Err.Error())
   236  	}
   237  
   238  	if e.Info != nil {
   239  		out = append(out, e.Info.String())
   240  	}
   241  	return strings.Join(out, "\n")
   242  }