209 lines
5.1 KiB
Go
209 lines
5.1 KiB
Go
package swift
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/backend"
|
|
"github.com/hashicorp/terraform/states"
|
|
"github.com/hashicorp/terraform/states/remote"
|
|
"github.com/hashicorp/terraform/states/statemgr"
|
|
)
|
|
|
|
const (
|
|
objectEnvPrefix = "env-"
|
|
delimiter = "/"
|
|
)
|
|
|
|
func (b *Backend) Workspaces() ([]string, error) {
|
|
client := &RemoteClient{
|
|
client: b.client,
|
|
container: b.container,
|
|
archive: b.archive,
|
|
archiveContainer: b.archiveContainer,
|
|
expireSecs: b.expireSecs,
|
|
lockState: b.lock,
|
|
}
|
|
|
|
// List our container objects
|
|
objectNames, err := client.ListObjectsNames(objectEnvPrefix, delimiter)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Find the envs, we use a map since we can get duplicates with
|
|
// path suffixes.
|
|
envs := map[string]struct{}{}
|
|
for _, object := range objectNames {
|
|
object = strings.TrimPrefix(object, objectEnvPrefix)
|
|
object = strings.TrimSuffix(object, delimiter)
|
|
|
|
// Ignore objects that still contain a "/"
|
|
// as we dont store states in subdirectories
|
|
if idx := strings.Index(object, delimiter); idx >= 0 {
|
|
continue
|
|
}
|
|
|
|
// swift is eventually consistent, thus a deleted object may
|
|
// be listed in objectList. To ensure consistency, we query
|
|
// each object with a "newest" arg set to true
|
|
payload, err := client.get(b.objectName(object))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if payload == nil {
|
|
// object doesn't exist anymore. skipping.
|
|
continue
|
|
}
|
|
|
|
envs[object] = struct{}{}
|
|
}
|
|
|
|
result := make([]string, 1, len(envs)+1)
|
|
result[0] = backend.DefaultStateName
|
|
|
|
for k := range envs {
|
|
result = append(result, k)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (b *Backend) DeleteWorkspace(name string) error {
|
|
if name == backend.DefaultStateName || name == "" {
|
|
return fmt.Errorf("can't delete default state")
|
|
}
|
|
|
|
client := &RemoteClient{
|
|
client: b.client,
|
|
container: b.container,
|
|
archive: b.archive,
|
|
archiveContainer: b.archiveContainer,
|
|
expireSecs: b.expireSecs,
|
|
objectName: b.objectName(name),
|
|
lockState: b.lock,
|
|
}
|
|
|
|
// Delete our object
|
|
err := client.Delete()
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("missing state name")
|
|
}
|
|
|
|
client := &RemoteClient{
|
|
client: b.client,
|
|
container: b.container,
|
|
archive: b.archive,
|
|
archiveContainer: b.archiveContainer,
|
|
expireSecs: b.expireSecs,
|
|
objectName: b.objectName(name),
|
|
lockState: b.lock,
|
|
}
|
|
|
|
var stateMgr statemgr.Full = &remote.State{Client: client}
|
|
|
|
// If we're not locking, disable it
|
|
if !b.lock {
|
|
stateMgr = &statemgr.LockDisabled{Inner: stateMgr}
|
|
}
|
|
|
|
// Check to see if this state already exists.
|
|
// If we're trying to force-unlock a state, we can't take the lock before
|
|
// fetching the state. If the state doesn't exist, we have to assume this
|
|
// is a normal create operation, and take the lock at that point.
|
|
//
|
|
// If we need to force-unlock, but for some reason the state no longer
|
|
// exists, the user will have to use openstack tools to manually fix the
|
|
// situation.
|
|
existing, err := b.Workspaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
exists := false
|
|
for _, s := range existing {
|
|
if s == name {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// We need to create the object so it's listed by States.
|
|
if !exists {
|
|
// the default state always exists
|
|
if name == backend.DefaultStateName {
|
|
return stateMgr, nil
|
|
}
|
|
|
|
// Grab a lock, we use this to write an empty state if one doesn't
|
|
// exist already. We have to write an empty state as a sentinel value
|
|
// so States() knows it exists.
|
|
lockInfo := statemgr.NewLockInfo()
|
|
lockInfo.Operation = "init"
|
|
lockId, err := stateMgr.Lock(lockInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to lock state in Swift: %s", err)
|
|
}
|
|
|
|
// Local helper function so we can call it multiple places
|
|
lockUnlock := func(parent error) error {
|
|
if err := stateMgr.Unlock(lockId); err != nil {
|
|
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
|
}
|
|
|
|
return parent
|
|
}
|
|
|
|
// Grab the value
|
|
if err := stateMgr.RefreshState(); err != nil {
|
|
err = lockUnlock(err)
|
|
return nil, err
|
|
}
|
|
|
|
// If we have no state, we have to create an empty state
|
|
if v := stateMgr.State(); v == nil {
|
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
|
err = lockUnlock(err)
|
|
return nil, err
|
|
}
|
|
if err := stateMgr.PersistState(); err != nil {
|
|
err = lockUnlock(err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Unlock, the state should now be initialized
|
|
if err := lockUnlock(nil); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return stateMgr, nil
|
|
}
|
|
|
|
func (b *Backend) objectName(name string) string {
|
|
if name != backend.DefaultStateName {
|
|
name = fmt.Sprintf("%s%s/%s", objectEnvPrefix, name, b.stateName)
|
|
} else {
|
|
name = b.stateName
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
const errStateUnlock = `
|
|
Error unlocking Swift state. Lock ID: %s
|
|
|
|
Error: %s
|
|
|
|
You may have to force-unlock this state in order to use it again.
|
|
The Swift backend acquires a lock during initialization to ensure
|
|
the minimum required keys are prepared.
|
|
`
|