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