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