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 {
|
if op.LockState {
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Info: op.Type.String(),
|
lockInfo.Operation = op.Type.String()
|
||||||
}
|
|
||||||
|
|
||||||
_, err := clistate.Lock(s, lockInfo, b.CLI, b.Colorize())
|
_, err := clistate.Lock(s, lockInfo, b.CLI, b.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
|
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
|
// Lock the state if we can
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Operation: "plan",
|
lockInfo.Operation = "backend from plan"
|
||||||
Info: "backend from plan",
|
|
||||||
}
|
|
||||||
|
|
||||||
lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize())
|
lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -991,9 +989,8 @@ func (m *Meta) backend_C_r_s(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the state if we can
|
// Lock the state if we can
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Info: "backend from config",
|
lockInfo.Operation = "backend from config"
|
||||||
}
|
|
||||||
|
|
||||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1100,9 +1097,9 @@ func (m *Meta) backend_C_r_S_changed(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the state if we can
|
// Lock the state if we can
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Info: "backend from config",
|
lockInfo.Operation = "backend from config"
|
||||||
}
|
|
||||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error locking state: %s", err)
|
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
|
// Lock the state if we can
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Info: "backend from config",
|
lockInfo.Operation = "backend from config"
|
||||||
}
|
|
||||||
|
|
||||||
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -24,9 +24,8 @@ import (
|
||||||
//
|
//
|
||||||
// This will attempt to lock both states for the migration.
|
// This will attempt to lock both states for the migration.
|
||||||
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
lockInfoOne := &state.LockInfo{
|
lockInfoOne := state.NewLockInfo()
|
||||||
Info: "migration source state",
|
lockInfoOne.Operation = "migration source state"
|
||||||
}
|
|
||||||
|
|
||||||
lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize())
|
lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,9 +33,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
}
|
}
|
||||||
defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize())
|
defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize())
|
||||||
|
|
||||||
lockInfoTwo := &state.LockInfo{
|
lockInfoTwo := state.NewLockInfo()
|
||||||
Info: "migration source state",
|
lockInfoTwo.Operation = "migration source state"
|
||||||
}
|
|
||||||
|
|
||||||
lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize())
|
lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -74,9 +74,8 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Meta.stateLock {
|
if c.Meta.stateLock {
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Operation: "taint",
|
lockInfo.Operation = "taint"
|
||||||
}
|
|
||||||
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
|
|
|
@ -23,7 +23,11 @@ func main() {
|
||||||
Path: os.Args[1],
|
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 {
|
if err != nil {
|
||||||
io.WriteString(os.Stderr, err.Error())
|
io.WriteString(os.Stderr, err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
@ -62,9 +62,8 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Meta.stateLock {
|
if c.Meta.stateLock {
|
||||||
lockInfo := &state.LockInfo{
|
lockInfo := state.NewLockInfo()
|
||||||
Operation: "untaint",
|
lockInfo.Operation = "untaint"
|
||||||
}
|
|
||||||
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
|
|
|
@ -20,9 +20,8 @@ func TestLocalStateLocks(t *testing.T) {
|
||||||
defer os.Remove(s.Path)
|
defer os.Remove(s.Path)
|
||||||
|
|
||||||
// lock first
|
// lock first
|
||||||
info := &LockInfo{
|
info := NewLockInfo()
|
||||||
Operation: "test",
|
info.Operation = "test"
|
||||||
}
|
|
||||||
lockID, err := s.Lock(info)
|
lockID, err := s.Lock(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"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)
|
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.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{
|
putParams := &dynamodb.PutItemInput{
|
||||||
Item: map[string]*dynamodb.AttributeValue{
|
Item: map[string]*dynamodb.AttributeValue{
|
||||||
|
@ -228,7 +228,7 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
||||||
TableName: aws.String(c.lockTable),
|
TableName: aws.String(c.lockTable),
|
||||||
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
||||||
}
|
}
|
||||||
_, err = c.dynClient.PutItem(putParams)
|
_, err := c.dynClient.PutItem(putParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getParams := &dynamodb.GetItemInput{
|
getParams := &dynamodb.GetItemInput{
|
||||||
|
@ -241,7 +241,7 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
||||||
|
|
||||||
resp, err := c.dynClient.GetItem(getParams)
|
resp, err := c.dynClient.GetItem(getParams)
|
||||||
if err != nil {
|
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
|
var infoData string
|
||||||
|
@ -252,12 +252,12 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
|
||||||
lockInfo := &state.LockInfo{}
|
lockInfo := &state.LockInfo{}
|
||||||
err = json.Unmarshal([]byte(infoData), lockInfo)
|
err = json.Unmarshal([]byte(infoData), lockInfo)
|
||||||
if err != nil {
|
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 {
|
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")
|
t.Fatal("client B not a state.Locker")
|
||||||
}
|
}
|
||||||
|
|
||||||
infoA := &state.LockInfo{
|
infoA := state.NewLockInfo()
|
||||||
Operation: "test",
|
infoA.Operation = "test"
|
||||||
Who: "client A",
|
infoA.Who = "clientA"
|
||||||
}
|
|
||||||
infoB := &state.LockInfo{
|
infoB := state.NewLockInfo()
|
||||||
Operation: "test",
|
infoB.Operation = "test"
|
||||||
Who: "client B",
|
infoB.Who = "clientB"
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := lockerA.Lock(infoA); err != nil {
|
if _, err := lockerA.Lock(infoA); err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
t.Fatal("unable to get initial lock:", err)
|
||||||
|
|
|
@ -3,12 +3,22 @@ package state
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"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.
|
// State is the collection of all state interfaces.
|
||||||
type State interface {
|
type State interface {
|
||||||
StateReader
|
StateReader
|
||||||
|
@ -59,15 +69,56 @@ type Locker interface {
|
||||||
Unlock(id string) error
|
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 {
|
type LockInfo struct {
|
||||||
ID string // unique ID
|
// Unique ID for the lock. NewLockInfo provides a random ID, but this may
|
||||||
Path string // Path to the state file
|
// be overridden by the lock implementation. The final value if ID will be
|
||||||
Created time.Time // The time the lock was taken
|
// returned by the call to Lock.
|
||||||
Version string // Terraform version
|
ID string
|
||||||
Operation string // Terraform operation
|
|
||||||
Who string // user@hostname when available
|
// Terraform operation, provided by the caller.
|
||||||
Info string // Extra info field
|
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
|
// Err returns the lock info formatted in an error
|
||||||
|
|
|
@ -21,3 +21,24 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
os.Exit(m.Run())
|
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],
|
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 {
|
if err != nil {
|
||||||
io.WriteString(os.Stderr, "lock failed")
|
io.WriteString(os.Stderr, "lock failed")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue