github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/azure/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 azure 7 8 import ( 9 "context" 10 "fmt" 11 "sort" 12 "strings" 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 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" 19 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" 20 ) 21 22 const ( 23 // This will be used as directory name, the odd looking colon is simply to 24 // reduce the chance of name conflicts with existing objects. 25 keyEnvPrefix = "env:" 26 ) 27 28 func (b *Backend) Workspaces() ([]string, error) { 29 prefix := b.keyName + keyEnvPrefix 30 params := containers.ListBlobsInput{ 31 Prefix: &prefix, 32 } 33 34 ctx := context.TODO() 35 client, err := b.armClient.getContainersClient(ctx) 36 if err != nil { 37 return nil, err 38 } 39 resp, err := client.ListBlobs(ctx, b.armClient.storageAccountName, b.containerName, params) 40 if err != nil { 41 return nil, err 42 } 43 44 envs := map[string]struct{}{} 45 for _, obj := range resp.Blobs.Blobs { 46 key := obj.Name 47 if strings.HasPrefix(key, prefix) { 48 name := strings.TrimPrefix(key, prefix) 49 // we store the state in a key, not a directory 50 if strings.Contains(name, "/") { 51 continue 52 } 53 54 envs[name] = struct{}{} 55 } 56 } 57 58 result := []string{backend.DefaultStateName} 59 for name := range envs { 60 result = append(result, name) 61 } 62 sort.Strings(result[1:]) 63 return result, 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 ctx := context.TODO() 72 client, err := b.armClient.getBlobClient(ctx) 73 if err != nil { 74 return err 75 } 76 77 if resp, err := client.Delete(ctx, b.armClient.storageAccountName, b.containerName, b.path(name), blobs.DeleteInput{}); err != nil { 78 if resp.Response.StatusCode != 404 { 79 return err 80 } 81 } 82 83 return nil 84 } 85 86 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 87 ctx := context.TODO() 88 blobClient, err := b.armClient.getBlobClient(ctx) 89 if err != nil { 90 return nil, err 91 } 92 93 client := &RemoteClient{ 94 giovanniBlobClient: *blobClient, 95 containerName: b.containerName, 96 keyName: b.path(name), 97 accountName: b.accountName, 98 snapshot: b.snapshot, 99 } 100 101 stateMgr := remote.NewState(client, b.encryption) 102 103 // Grab the value 104 if err := stateMgr.RefreshState(); err != nil { 105 return nil, err 106 } 107 //if this isn't the default state name, we need to create the object so 108 //it's listed by States. 109 if v := stateMgr.State(); v == nil { 110 // take a lock on this state while we write it 111 lockInfo := statemgr.NewLockInfo() 112 lockInfo.Operation = "init" 113 lockId, err := client.Lock(lockInfo) 114 if err != nil { 115 return nil, fmt.Errorf("failed to lock azure state: %w", err) 116 } 117 118 // Local helper function so we can call it multiple places 119 lockUnlock := func(parent error) error { 120 if err := stateMgr.Unlock(lockId); err != nil { 121 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 122 } 123 return parent 124 } 125 126 // Grab the value 127 if err := stateMgr.RefreshState(); err != nil { 128 err = lockUnlock(err) 129 return nil, err 130 } 131 //if this isn't the default state name, we need to create the object so 132 //it's listed by States. 133 if v := stateMgr.State(); v == nil { 134 // If we have no state, we have to create an empty state 135 if err := stateMgr.WriteState(states.NewState()); err != nil { 136 err = lockUnlock(err) 137 return nil, err 138 } 139 if err := stateMgr.PersistState(nil); err != nil { 140 err = lockUnlock(err) 141 return nil, err 142 } 143 144 // Unlock, the state should now be initialized 145 if err := lockUnlock(nil); err != nil { 146 return nil, err 147 } 148 } 149 } 150 151 return stateMgr, nil 152 } 153 154 func (b *Backend) client() *RemoteClient { 155 return &RemoteClient{} 156 } 157 158 func (b *Backend) path(name string) string { 159 if name == backend.DefaultStateName { 160 return b.keyName 161 } 162 163 return b.keyName + keyEnvPrefix + name 164 } 165 166 const errStateUnlock = ` 167 Error unlocking Azure state. Lock ID: %s 168 169 Error: %w 170 171 You may have to force-unlock this state in order to use it again. 172 `