github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/kubernetes/backend_state.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package kubernetes 7 8 import ( 9 "context" 10 "errors" 11 "fmt" 12 "sort" 13 14 "github.com/opentofu/opentofu/internal/backend" 15 "github.com/opentofu/opentofu/internal/states" 16 "github.com/opentofu/opentofu/internal/states/remote" 17 "github.com/opentofu/opentofu/internal/states/statemgr" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 ) 20 21 // Workspaces returns a list of names for the workspaces found in k8s. The default 22 // workspace is always returned as the first element in the slice. 23 func (b *Backend) Workspaces() ([]string, error) { 24 secretClient, err := b.getKubernetesSecretClient() 25 if err != nil { 26 return nil, err 27 } 28 29 secrets, err := secretClient.List( 30 context.Background(), 31 metav1.ListOptions{ 32 LabelSelector: tfstateKey + "=true", 33 }, 34 ) 35 if err != nil { 36 return nil, err 37 } 38 39 // Use a map so there aren't duplicate workspaces 40 m := make(map[string]struct{}) 41 for _, secret := range secrets.Items { 42 sl := secret.GetLabels() 43 ws, ok := sl[tfstateWorkspaceKey] 44 if !ok { 45 continue 46 } 47 48 key, ok := sl[tfstateSecretSuffixKey] 49 if !ok { 50 continue 51 } 52 53 // Make sure it isn't default and the key matches 54 if ws != backend.DefaultStateName && key == b.nameSuffix { 55 m[ws] = struct{}{} 56 } 57 } 58 59 states := []string{backend.DefaultStateName} 60 for k := range m { 61 states = append(states, k) 62 } 63 64 sort.Strings(states[1:]) 65 return states, nil 66 } 67 68 func (b *Backend) DeleteWorkspace(name string, _ bool) error { 69 if name == backend.DefaultStateName || name == "" { 70 return fmt.Errorf("can't delete default state") 71 } 72 73 client, err := b.remoteClient(name) 74 if err != nil { 75 return err 76 } 77 78 return client.Delete() 79 } 80 81 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 82 c, err := b.remoteClient(name) 83 if err != nil { 84 return nil, err 85 } 86 87 stateMgr := remote.NewState(c, b.encryption) 88 89 // Grab the value 90 if err := stateMgr.RefreshState(); err != nil { 91 return nil, err 92 } 93 94 // If we have no state, we have to create an empty state 95 if v := stateMgr.State(); v == nil { 96 97 lockInfo := statemgr.NewLockInfo() 98 lockInfo.Operation = "init" 99 lockID, err := stateMgr.Lock(lockInfo) 100 if err != nil { 101 return nil, err 102 } 103 104 secretName, err := c.createSecretName() 105 if err != nil { 106 return nil, err 107 } 108 109 // Local helper function so we can call it multiple places 110 unlock := func(baseErr error) error { 111 if err := stateMgr.Unlock(lockID); err != nil { 112 const unlockErrMsg = `%v 113 Additionally, unlocking the state in Kubernetes failed: 114 115 Error message: %w 116 Lock ID (gen): %v 117 Secret Name: %v 118 119 You may have to force-unlock this state in order to use it again. 120 The Kubernetes backend acquires a lock during initialization to ensure 121 the initial state file is created.` 122 return fmt.Errorf(unlockErrMsg, baseErr, err, lockID, secretName) 123 } 124 125 return baseErr 126 } 127 128 if err := stateMgr.WriteState(states.NewState()); err != nil { 129 return nil, unlock(err) 130 } 131 if err := stateMgr.PersistState(nil); err != nil { 132 return nil, unlock(err) 133 } 134 135 // Unlock, the state should now be initialized 136 if err := unlock(nil); err != nil { 137 return nil, err 138 } 139 140 } 141 142 return stateMgr, nil 143 } 144 145 // get a remote client configured for this state 146 func (b *Backend) remoteClient(name string) (*RemoteClient, error) { 147 if name == "" { 148 return nil, errors.New("missing state name") 149 } 150 151 secretClient, err := b.getKubernetesSecretClient() 152 if err != nil { 153 return nil, err 154 } 155 156 leaseClient, err := b.getKubernetesLeaseClient() 157 if err != nil { 158 return nil, err 159 } 160 161 client := &RemoteClient{ 162 kubernetesSecretClient: secretClient, 163 kubernetesLeaseClient: leaseClient, 164 namespace: b.namespace, 165 labels: b.labels, 166 nameSuffix: b.nameSuffix, 167 workspace: name, 168 } 169 170 return client, nil 171 }