| package kubernetes |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "sort" |
| |
| "github.com/hashicorp/terraform/internal/backend" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/states/remote" |
| "github.com/hashicorp/terraform/internal/states/statemgr" |
| 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( |
| context.Background(), |
| 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, _ bool) 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() |
| } |
| |
| func (b *Backend) StateMgr(name string) (statemgr.Full, error) { |
| 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 { |
| |
| lockInfo := statemgr.NewLockInfo() |
| 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(nil); 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{} |
| } |