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  }