2020-04-13 22:17:17 +02:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2021-05-07 21:03:42 +02:00
|
|
|
"context"
|
2020-04-13 22:17:17 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
|
2021-05-17 17:42:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
|
|
"github.com/hashicorp/terraform/internal/states/remote"
|
|
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
2020-04-13 22:17:17 +02:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Workspaces returns a list of names for the workspaces found in k8s. The default
|
|
|
|
// workspace is always returned as the first element in the slice.
|
|
|
|
func (b *Backend) Workspaces() ([]string, error) {
|
|
|
|
secretClient, err := b.KubernetesSecretClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
secrets, err := secretClient.List(
|
2021-05-07 21:03:42 +02:00
|
|
|
context.Background(),
|
2020-04-13 22:17:17 +02:00
|
|
|
metav1.ListOptions{
|
|
|
|
LabelSelector: tfstateKey + "=true",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use a map so there aren't duplicate workspaces
|
|
|
|
m := make(map[string]struct{})
|
|
|
|
for _, secret := range secrets.Items {
|
|
|
|
sl := secret.GetLabels()
|
|
|
|
ws, ok := sl[tfstateWorkspaceKey]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
key, ok := sl[tfstateSecretSuffixKey]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure it isn't default and the key matches
|
|
|
|
if ws != backend.DefaultStateName && key == b.nameSuffix {
|
|
|
|
m[ws] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
states := []string{backend.DefaultStateName}
|
|
|
|
for k := range m {
|
|
|
|
states = append(states, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(states[1:])
|
|
|
|
return states, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Backend) DeleteWorkspace(name string) error {
|
|
|
|
if name == backend.DefaultStateName || name == "" {
|
|
|
|
return fmt.Errorf("can't delete default state")
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := b.remoteClient(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.Delete()
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:43:01 +02:00
|
|
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
2020-04-13 22:17:17 +02:00
|
|
|
c, err := b.remoteClient(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMgr := &remote.State{Client: c}
|
|
|
|
|
|
|
|
// Grab the value
|
|
|
|
if err := stateMgr.RefreshState(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have no state, we have to create an empty state
|
|
|
|
if v := stateMgr.State(); v == nil {
|
|
|
|
|
2020-08-11 17:43:01 +02:00
|
|
|
lockInfo := statemgr.NewLockInfo()
|
2020-04-13 22:17:17 +02:00
|
|
|
lockInfo.Operation = "init"
|
|
|
|
lockID, err := stateMgr.Lock(lockInfo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
secretName, err := c.createSecretName()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Local helper function so we can call it multiple places
|
|
|
|
unlock := func(baseErr error) error {
|
|
|
|
if err := stateMgr.Unlock(lockID); err != nil {
|
|
|
|
const unlockErrMsg = `%v
|
|
|
|
Additionally, unlocking the state in Kubernetes failed:
|
|
|
|
|
|
|
|
Error message: %q
|
|
|
|
Lock ID (gen): %v
|
|
|
|
Secret Name: %v
|
|
|
|
|
|
|
|
You may have to force-unlock this state in order to use it again.
|
|
|
|
The Kubernetes backend acquires a lock during initialization to ensure
|
|
|
|
the initial state file is created.`
|
|
|
|
return fmt.Errorf(unlockErrMsg, baseErr, err.Error(), lockID, secretName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return baseErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
|
|
|
return nil, unlock(err)
|
|
|
|
}
|
|
|
|
if err := stateMgr.PersistState(); err != nil {
|
|
|
|
return nil, unlock(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock, the state should now be initialized
|
|
|
|
if err := unlock(nil); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return stateMgr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// get a remote client configured for this state
|
|
|
|
func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
|
|
|
|
if name == "" {
|
|
|
|
return nil, errors.New("missing state name")
|
|
|
|
}
|
|
|
|
|
|
|
|
secretClient, err := b.KubernetesSecretClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
leaseClient, err := b.KubernetesLeaseClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
client := &RemoteClient{
|
|
|
|
kubernetesSecretClient: secretClient,
|
|
|
|
kubernetesLeaseClient: leaseClient,
|
|
|
|
namespace: b.namespace,
|
|
|
|
labels: b.labels,
|
|
|
|
nameSuffix: b.nameSuffix,
|
|
|
|
workspace: name,
|
|
|
|
}
|
|
|
|
|
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Backend) client() *RemoteClient {
|
|
|
|
return &RemoteClient{}
|
|
|
|
}
|