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