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