github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/azure/client.go (about) 1 package azure 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 11 "github.com/Azure/azure-sdk-for-go/storage" 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/go-uuid" 14 "github.com/hashicorp/terraform/state" 15 "github.com/hashicorp/terraform/state/remote" 16 "github.com/hashicorp/terraform/states" 17 ) 18 19 const ( 20 leaseHeader = "x-ms-lease-id" 21 // Must be lower case 22 lockInfoMetaKey = "terraformlockid" 23 ) 24 25 type RemoteClient struct { 26 blobClient storage.BlobStorageClient 27 containerName string 28 keyName string 29 leaseID string 30 } 31 32 func (c *RemoteClient) Get() (*remote.Payload, error) { 33 containerReference := c.blobClient.GetContainerReference(c.containerName) 34 blobReference := containerReference.GetBlobReference(c.keyName) 35 options := &storage.GetBlobOptions{} 36 37 if c.leaseID != "" { 38 options.LeaseID = c.leaseID 39 } 40 41 blob, err := blobReference.Get(options) 42 if err != nil { 43 if storErr, ok := err.(storage.AzureStorageServiceError); ok { 44 if storErr.Code == "BlobNotFound" { 45 return nil, nil 46 } 47 } 48 return nil, err 49 } 50 51 defer blob.Close() 52 53 buf := bytes.NewBuffer(nil) 54 if _, err := io.Copy(buf, blob); err != nil { 55 return nil, fmt.Errorf("Failed to read remote state: %s", err) 56 } 57 58 payload := &remote.Payload{ 59 Data: buf.Bytes(), 60 } 61 62 // If there was no data, then return nil 63 if len(payload.Data) == 0 { 64 return nil, nil 65 } 66 67 return payload, nil 68 } 69 70 func (c *RemoteClient) Put(data []byte) error { 71 getOptions := &storage.GetBlobMetadataOptions{} 72 setOptions := &storage.SetBlobPropertiesOptions{} 73 putOptions := &storage.PutBlobOptions{} 74 75 containerReference := c.blobClient.GetContainerReference(c.containerName) 76 blobReference := containerReference.GetBlobReference(c.keyName) 77 78 blobReference.Properties.ContentType = "application/json" 79 blobReference.Properties.ContentLength = int64(len(data)) 80 81 if c.leaseID != "" { 82 getOptions.LeaseID = c.leaseID 83 setOptions.LeaseID = c.leaseID 84 putOptions.LeaseID = c.leaseID 85 } 86 87 exists, err := blobReference.Exists() 88 if err != nil { 89 return err 90 } 91 92 if exists { 93 err = blobReference.GetMetadata(getOptions) 94 if err != nil { 95 return err 96 } 97 } 98 99 reader := bytes.NewReader(data) 100 101 err = blobReference.CreateBlockBlobFromReader(reader, putOptions) 102 if err != nil { 103 return err 104 } 105 106 return blobReference.SetProperties(setOptions) 107 } 108 109 func (c *RemoteClient) Delete() error { 110 containerReference := c.blobClient.GetContainerReference(c.containerName) 111 blobReference := containerReference.GetBlobReference(c.keyName) 112 options := &storage.DeleteBlobOptions{} 113 114 if c.leaseID != "" { 115 options.LeaseID = c.leaseID 116 } 117 118 return blobReference.Delete(options) 119 } 120 121 func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) { 122 stateName := fmt.Sprintf("%s/%s", c.containerName, c.keyName) 123 info.Path = stateName 124 125 if info.ID == "" { 126 lockID, err := uuid.GenerateUUID() 127 if err != nil { 128 return "", err 129 } 130 131 info.ID = lockID 132 } 133 134 getLockInfoErr := func(err error) error { 135 lockInfo, infoErr := c.getLockInfo() 136 if infoErr != nil { 137 err = multierror.Append(err, infoErr) 138 } 139 140 return &state.LockError{ 141 Err: err, 142 Info: lockInfo, 143 } 144 } 145 146 containerReference := c.blobClient.GetContainerReference(c.containerName) 147 blobReference := containerReference.GetBlobReference(c.keyName) 148 leaseID, err := blobReference.AcquireLease(-1, info.ID, &storage.LeaseOptions{}) 149 if err != nil { 150 if storErr, ok := err.(storage.AzureStorageServiceError); ok && storErr.Code != "BlobNotFound" { 151 return "", getLockInfoErr(err) 152 } 153 154 // failed to lock as there was no state blob, write empty state 155 stateMgr := &remote.State{Client: c} 156 157 // ensure state is actually empty 158 if err := stateMgr.RefreshState(); err != nil { 159 return "", fmt.Errorf("Failed to refresh state before writing empty state for locking: %s", err) 160 } 161 162 log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state") 163 164 if v := stateMgr.State(); v == nil { 165 if err := stateMgr.WriteState(states.NewState()); err != nil { 166 return "", fmt.Errorf("Failed to write empty state for locking: %s", err) 167 } 168 if err := stateMgr.PersistState(); err != nil { 169 return "", fmt.Errorf("Failed to persist empty state for locking: %s", err) 170 } 171 } 172 173 leaseID, err = blobReference.AcquireLease(-1, info.ID, &storage.LeaseOptions{}) 174 if err != nil { 175 return "", getLockInfoErr(err) 176 } 177 } 178 179 info.ID = leaseID 180 c.leaseID = leaseID 181 182 if err := c.writeLockInfo(info); err != nil { 183 return "", err 184 } 185 186 return info.ID, nil 187 } 188 189 func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) { 190 containerReference := c.blobClient.GetContainerReference(c.containerName) 191 blobReference := containerReference.GetBlobReference(c.keyName) 192 err := blobReference.GetMetadata(&storage.GetBlobMetadataOptions{}) 193 if err != nil { 194 return nil, err 195 } 196 197 raw := blobReference.Metadata[lockInfoMetaKey] 198 if raw == "" { 199 return nil, fmt.Errorf("blob metadata %q was empty", lockInfoMetaKey) 200 } 201 202 data, err := base64.StdEncoding.DecodeString(raw) 203 if err != nil { 204 return nil, err 205 } 206 207 lockInfo := &state.LockInfo{} 208 err = json.Unmarshal(data, lockInfo) 209 if err != nil { 210 return nil, err 211 } 212 213 return lockInfo, nil 214 } 215 216 // writes info to blob meta data, deletes metadata entry if info is nil 217 func (c *RemoteClient) writeLockInfo(info *state.LockInfo) error { 218 containerReference := c.blobClient.GetContainerReference(c.containerName) 219 blobReference := containerReference.GetBlobReference(c.keyName) 220 err := blobReference.GetMetadata(&storage.GetBlobMetadataOptions{ 221 LeaseID: c.leaseID, 222 }) 223 if err != nil { 224 return err 225 } 226 227 if info == nil { 228 delete(blobReference.Metadata, lockInfoMetaKey) 229 } else { 230 value := base64.StdEncoding.EncodeToString(info.Marshal()) 231 blobReference.Metadata[lockInfoMetaKey] = value 232 } 233 234 opts := &storage.SetBlobMetadataOptions{ 235 LeaseID: c.leaseID, 236 } 237 return blobReference.SetMetadata(opts) 238 } 239 240 func (c *RemoteClient) Unlock(id string) error { 241 lockErr := &state.LockError{} 242 243 lockInfo, err := c.getLockInfo() 244 if err != nil { 245 lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err) 246 return lockErr 247 } 248 lockErr.Info = lockInfo 249 250 if lockInfo.ID != id { 251 lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id) 252 return lockErr 253 } 254 255 c.leaseID = lockInfo.ID 256 if err := c.writeLockInfo(nil); err != nil { 257 lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %s", err) 258 return lockErr 259 } 260 261 containerReference := c.blobClient.GetContainerReference(c.containerName) 262 blobReference := containerReference.GetBlobReference(c.keyName) 263 err = blobReference.ReleaseLease(id, &storage.LeaseOptions{}) 264 if err != nil { 265 lockErr.Err = err 266 return lockErr 267 } 268 269 c.leaseID = "" 270 271 return nil 272 }