github.com/profects/terraform@v0.9.0-beta1.0.20170227135739-92d4809db30d/state/state.go (about)

     1  package state
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"os"
    10  	"os/user"
    11  	"strings"
    12  	"text/template"
    13  	"time"
    14  
    15  	uuid "github.com/hashicorp/go-uuid"
    16  	"github.com/hashicorp/terraform/terraform"
    17  )
    18  
    19  var rngSource *rand.Rand
    20  
    21  func init() {
    22  	rngSource = rand.New(rand.NewSource(time.Now().UnixNano()))
    23  }
    24  
    25  // State is the collection of all state interfaces.
    26  type State interface {
    27  	StateReader
    28  	StateWriter
    29  	StateRefresher
    30  	StatePersister
    31  }
    32  
    33  // StateReader is the interface for things that can return a state. Retrieving
    34  // the state here must not error. Loading the state fresh (an operation that
    35  // can likely error) should be implemented by RefreshState. If a state hasn't
    36  // been loaded yet, it is okay for State to return nil.
    37  type StateReader interface {
    38  	State() *terraform.State
    39  }
    40  
    41  // StateWriter is the interface that must be implemented by something that
    42  // can write a state. Writing the state can be cached or in-memory, as
    43  // full persistence should be implemented by StatePersister.
    44  type StateWriter interface {
    45  	WriteState(*terraform.State) error
    46  }
    47  
    48  // StateRefresher is the interface that is implemented by something that
    49  // can load a state. This might be refreshing it from a remote location or
    50  // it might simply be reloading it from disk.
    51  type StateRefresher interface {
    52  	RefreshState() error
    53  }
    54  
    55  // StatePersister is implemented to truly persist a state. Whereas StateWriter
    56  // is allowed to perhaps be caching in memory, PersistState must write the
    57  // state to some durable storage.
    58  type StatePersister interface {
    59  	PersistState() error
    60  }
    61  
    62  // Locker is implemented to lock state during command execution.
    63  // The info parameter can be recorded with the lock, but the
    64  // implementation should not depend in its value. The string returned by Lock
    65  // is an ID corresponding to the lock acquired, and must be passed to Unlock to
    66  // ensure that the correct lock is being released.
    67  //
    68  // Lock and Unlock may return an error value of type LockError which in turn
    69  // can contain the LockInfo of a conflicting lock.
    70  type Locker interface {
    71  	Lock(info *LockInfo) (string, error)
    72  	Unlock(id string) error
    73  }
    74  
    75  // Generate a LockInfo structure, populating the required fields.
    76  func NewLockInfo() *LockInfo {
    77  	// this doesn't need to be cryptographically secure, just unique.
    78  	// Using math/rand alleviates the need to check handle the read error.
    79  	// Use a uuid format to match other IDs used throughout Terraform.
    80  	buf := make([]byte, 16)
    81  	rngSource.Read(buf)
    82  
    83  	id, err := uuid.FormatUUID(buf)
    84  	if err != nil {
    85  		// this of course shouldn't happen
    86  		panic(err)
    87  	}
    88  
    89  	// don't error out on user and hostname, as we don't require them
    90  	userName := ""
    91  	if userInfo, err := user.Current(); err == nil {
    92  		userName = userInfo.Username
    93  	}
    94  	host, _ := os.Hostname()
    95  
    96  	info := &LockInfo{
    97  		ID:      id,
    98  		Who:     fmt.Sprintf("%s@%s", userName, host),
    99  		Version: terraform.Version,
   100  		Created: time.Now().UTC(),
   101  	}
   102  	return info
   103  }
   104  
   105  // LockInfo stores lock metadata.
   106  //
   107  // Only Operation and Info are required to be set by the caller of Lock.
   108  type LockInfo struct {
   109  	// Unique ID for the lock. NewLockInfo provides a random ID, but this may
   110  	// be overridden by the lock implementation. The final value if ID will be
   111  	// returned by the call to Lock.
   112  	ID string
   113  
   114  	// Terraform operation, provided by the caller.
   115  	Operation string
   116  	// Extra information to store with the lock, provided by the caller.
   117  	Info string
   118  
   119  	// user@hostname when available
   120  	Who string
   121  	// Terraform version
   122  	Version string
   123  	// Time that the lock was taken.
   124  	Created time.Time
   125  
   126  	// Path to the state file when applicable. Set by the Lock implementation.
   127  	Path string
   128  }
   129  
   130  // Err returns the lock info formatted in an error
   131  func (l *LockInfo) Err() error {
   132  	return errors.New(l.String())
   133  }
   134  
   135  // Marshal returns a string json representation of the LockInfo
   136  func (l *LockInfo) Marshal() []byte {
   137  	js, err := json.Marshal(l)
   138  	if err != nil {
   139  		panic(err)
   140  	}
   141  	return js
   142  }
   143  
   144  // String return a multi-line string representation of LockInfo
   145  func (l *LockInfo) String() string {
   146  	tmpl := `Lock Info:
   147    ID:        {{.ID}}
   148    Path:      {{.Path}}
   149    Operation: {{.Operation}}
   150    Who:       {{.Who}}
   151    Version:   {{.Version}}
   152    Created:   {{.Created}}
   153    Info:      {{.Info}}
   154  `
   155  
   156  	t := template.Must(template.New("LockInfo").Parse(tmpl))
   157  	var out bytes.Buffer
   158  	if err := t.Execute(&out, l); err != nil {
   159  		panic(err)
   160  	}
   161  	return out.String()
   162  }
   163  
   164  type LockError struct {
   165  	Info *LockInfo
   166  	Err  error
   167  }
   168  
   169  func (e *LockError) Error() string {
   170  	var out []string
   171  	if e.Err != nil {
   172  		out = append(out, e.Err.Error())
   173  	}
   174  
   175  	if e.Info != nil {
   176  		out = append(out, e.Info.String())
   177  	}
   178  	return strings.Join(out, "\n")
   179  }