Use NewLockInfo to get a pre-populated value
Using NewLockInfo ensure we start with all required fields filled.
This commit is contained in:
parent
52b2343672
commit
f5ed8cd288
|
@ -30,10 +30,8 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
}
|
||||
|
||||
if op.LockState {
|
||||
lockInfo := &state.LockInfo{
|
||||
Info: op.Type.String(),
|
||||
}
|
||||
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = op.Type.String()
|
||||
_, err := clistate.Lock(s, lockInfo, b.CLI, b.Colorize())
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
|
||||
|
|
|
@ -533,10 +533,8 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) {
|
|||
}
|
||||
|
||||
// Lock the state if we can
|
||||
lockInfo := &state.LockInfo{
|
||||
Operation: "plan",
|
||||
Info: "backend from plan",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "backend from plan"
|
||||
|
||||
lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
|
@ -991,9 +989,8 @@ func (m *Meta) backend_C_r_s(
|
|||
}
|
||||
|
||||
// Lock the state if we can
|
||||
lockInfo := &state.LockInfo{
|
||||
Info: "backend from config",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "backend from config"
|
||||
|
||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
|
@ -1100,9 +1097,9 @@ func (m *Meta) backend_C_r_S_changed(
|
|||
}
|
||||
|
||||
// Lock the state if we can
|
||||
lockInfo := &state.LockInfo{
|
||||
Info: "backend from config",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "backend from config"
|
||||
|
||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error locking state: %s", err)
|
||||
|
@ -1261,9 +1258,8 @@ func (m *Meta) backend_C_R_S_unchanged(
|
|||
}
|
||||
|
||||
// Lock the state if we can
|
||||
lockInfo := &state.LockInfo{
|
||||
Info: "backend from config",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "backend from config"
|
||||
|
||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
|
|
|
@ -24,9 +24,8 @@ import (
|
|||
//
|
||||
// This will attempt to lock both states for the migration.
|
||||
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||
lockInfoOne := &state.LockInfo{
|
||||
Info: "migration source state",
|
||||
}
|
||||
lockInfoOne := state.NewLockInfo()
|
||||
lockInfoOne.Operation = "migration source state"
|
||||
|
||||
lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
|
@ -34,9 +33,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
|||
}
|
||||
defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize())
|
||||
|
||||
lockInfoTwo := &state.LockInfo{
|
||||
Info: "migration source state",
|
||||
}
|
||||
lockInfoTwo := state.NewLockInfo()
|
||||
lockInfoTwo.Operation = "migration source state"
|
||||
|
||||
lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
|
|
|
@ -74,9 +74,8 @@ func (c *TaintCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if c.Meta.stateLock {
|
||||
lockInfo := &state.LockInfo{
|
||||
Operation: "taint",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "taint"
|
||||
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||
|
|
|
@ -23,7 +23,11 @@ func main() {
|
|||
Path: os.Args[1],
|
||||
}
|
||||
|
||||
lockID, err := s.Lock(&state.LockInfo{Operation: "test", Info: "state locker"})
|
||||
info := state.NewLockInfo()
|
||||
info.Operation = "test"
|
||||
info.Info = "state locker"
|
||||
|
||||
lockID, err := s.Lock(info)
|
||||
if err != nil {
|
||||
io.WriteString(os.Stderr, err.Error())
|
||||
return
|
||||
|
|
|
@ -62,9 +62,8 @@ func (c *UntaintCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if c.Meta.stateLock {
|
||||
lockInfo := &state.LockInfo{
|
||||
Operation: "untaint",
|
||||
}
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "untaint"
|
||||
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||
|
|
|
@ -20,9 +20,8 @@ func TestLocalStateLocks(t *testing.T) {
|
|||
defer os.Remove(s.Path)
|
||||
|
||||
// lock first
|
||||
info := &LockInfo{
|
||||
Operation: "test",
|
||||
}
|
||||
info := NewLockInfo()
|
||||
info.Operation = "test"
|
||||
lockID, err := s.Lock(info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
|
@ -210,15 +209,16 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
|||
}
|
||||
|
||||
stateName := fmt.Sprintf("%s/%s", c.bucketName, c.keyName)
|
||||
|
||||
lockID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
info.ID = lockID
|
||||
info.Path = stateName
|
||||
info.Created = time.Now().UTC()
|
||||
|
||||
if info.ID == "" {
|
||||
lockID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
info.ID = lockID
|
||||
}
|
||||
|
||||
putParams := &dynamodb.PutItemInput{
|
||||
Item: map[string]*dynamodb.AttributeValue{
|
||||
|
@ -228,7 +228,7 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
|||
TableName: aws.String(c.lockTable),
|
||||
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
||||
}
|
||||
_, err = c.dynClient.PutItem(putParams)
|
||||
_, err := c.dynClient.PutItem(putParams)
|
||||
|
||||
if err != nil {
|
||||
getParams := &dynamodb.GetItemInput{
|
||||
|
@ -241,7 +241,7 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
|||
|
||||
resp, err := c.dynClient.GetItem(getParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("s3 state file %q locked, failed to retrieve info: %s", stateName, err)
|
||||
return info.ID, fmt.Errorf("s3 state file %q locked, failed to retrieve info: %s", stateName, err)
|
||||
}
|
||||
|
||||
var infoData string
|
||||
|
@ -252,12 +252,12 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
|||
lockInfo := &state.LockInfo{}
|
||||
err = json.Unmarshal([]byte(infoData), lockInfo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("s3 state file %q locked, failed get lock info: %s", stateName, err)
|
||||
return info.ID, fmt.Errorf("s3 state file %q locked, failed get lock info: %s", stateName, err)
|
||||
}
|
||||
|
||||
return "", lockInfo.Err()
|
||||
return info.ID, lockInfo.Err()
|
||||
}
|
||||
return "", nil
|
||||
return info.ID, nil
|
||||
}
|
||||
|
||||
func (c *S3Client) Unlock(string) error {
|
||||
|
|
|
@ -57,14 +57,13 @@ func TestRemoteLocks(t *testing.T, a, b Client) {
|
|||
t.Fatal("client B not a state.Locker")
|
||||
}
|
||||
|
||||
infoA := &state.LockInfo{
|
||||
Operation: "test",
|
||||
Who: "client A",
|
||||
}
|
||||
infoB := &state.LockInfo{
|
||||
Operation: "test",
|
||||
Who: "client B",
|
||||
}
|
||||
infoA := state.NewLockInfo()
|
||||
infoA.Operation = "test"
|
||||
infoA.Who = "clientA"
|
||||
|
||||
infoB := state.NewLockInfo()
|
||||
infoB.Operation = "test"
|
||||
infoB.Who = "clientB"
|
||||
|
||||
if _, err := lockerA.Lock(infoA); err != nil {
|
||||
t.Fatal("unable to get initial lock:", err)
|
||||
|
|
|
@ -3,12 +3,22 @@ package state
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var rngSource *rand.Rand
|
||||
|
||||
func init() {
|
||||
rngSource = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// State is the collection of all state interfaces.
|
||||
type State interface {
|
||||
StateReader
|
||||
|
@ -59,15 +69,56 @@ type Locker interface {
|
|||
Unlock(id string) error
|
||||
}
|
||||
|
||||
// LockInfo stores metadata for locks taken.
|
||||
// Generate a LockInfo structure, populating the required fields.
|
||||
func NewLockInfo() *LockInfo {
|
||||
// this doesn't need to be cryptographically secure, just unique.
|
||||
// Using math/rand alleviates the need to check handle the read error.
|
||||
// Use a uuid format to match other IDs used throughout Terraform.
|
||||
buf := make([]byte, 16)
|
||||
rngSource.Read(buf)
|
||||
|
||||
id, err := uuid.FormatUUID(buf)
|
||||
if err != nil {
|
||||
// this of course shouldn't happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// don't error out on user and hostname, as we don't require them
|
||||
username, _ := user.Current()
|
||||
host, _ := os.Hostname()
|
||||
|
||||
info := &LockInfo{
|
||||
ID: id,
|
||||
Who: fmt.Sprintf("%s@%s", username, host),
|
||||
Version: terraform.Version,
|
||||
Created: time.Now().UTC(),
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// LockInfo stores lock metadata.
|
||||
//
|
||||
// Only Operation and Info are required to be set by the caller of Lock.
|
||||
type LockInfo struct {
|
||||
ID string // unique ID
|
||||
Path string // Path to the state file
|
||||
Created time.Time // The time the lock was taken
|
||||
Version string // Terraform version
|
||||
Operation string // Terraform operation
|
||||
Who string // user@hostname when available
|
||||
Info string // Extra info field
|
||||
// Unique ID for the lock. NewLockInfo provides a random ID, but this may
|
||||
// be overridden by the lock implementation. The final value if ID will be
|
||||
// returned by the call to Lock.
|
||||
ID string
|
||||
|
||||
// Terraform operation, provided by the caller.
|
||||
Operation string
|
||||
// Extra information to store with the lock, provided by the caller.
|
||||
Info string
|
||||
|
||||
// user@hostname when available
|
||||
Who string
|
||||
// Terraform version
|
||||
Version string
|
||||
// Time that the lock was taken.
|
||||
Created time.Time
|
||||
|
||||
// Path to the state file when applicable. Set by the Lock implementation.
|
||||
Path string
|
||||
}
|
||||
|
||||
// Err returns the lock info formatted in an error
|
||||
|
|
|
@ -21,3 +21,24 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestNewLockInfo(t *testing.T) {
|
||||
info1 := NewLockInfo()
|
||||
info2 := NewLockInfo()
|
||||
|
||||
if info1.ID == "" {
|
||||
t.Fatal("LockInfo missing ID")
|
||||
}
|
||||
|
||||
if info1.Version == "" {
|
||||
t.Fatal("LockInfo missing version")
|
||||
}
|
||||
|
||||
if info1.Created.IsZero() {
|
||||
t.Fatal("LockInfo missing Created")
|
||||
}
|
||||
|
||||
if info1.ID == info2.ID {
|
||||
t.Fatal("multiple LockInfo with identical IDs")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,11 @@ func main() {
|
|||
Path: os.Args[1],
|
||||
}
|
||||
|
||||
_, err := s.Lock(&state.LockInfo{Operation: "test", Info: "state locker"})
|
||||
info := state.NewLockInfo()
|
||||
info.Operation = "test"
|
||||
info.Info = "state locker"
|
||||
|
||||
_, err := s.Lock(info)
|
||||
if err != nil {
|
||||
io.WriteString(os.Stderr, "lock failed")
|
||||
|
||||
|
|
Loading…
Reference in New Issue