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  }