github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/cos/client.go (about) 1 package cos 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/md5" 7 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "log" 11 "net/http" 12 "strings" 13 "time" 14 15 multierror "github.com/hashicorp/go-multierror" 16 "github.com/hashicorp/terraform/state" 17 "github.com/hashicorp/terraform/state/remote" 18 tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" 19 "github.com/tencentyun/cos-go-sdk-v5" 20 ) 21 22 const ( 23 lockTagKey = "tencentcloud-terraform-lock" 24 ) 25 26 // RemoteClient implements the client of remote state 27 type remoteClient struct { 28 cosContext context.Context 29 cosClient *cos.Client 30 tagClient *tag.Client 31 32 bucket string 33 stateFile string 34 lockFile string 35 encrypt bool 36 acl string 37 } 38 39 // Get returns remote state file 40 func (c *remoteClient) Get() (*remote.Payload, error) { 41 log.Printf("[DEBUG] get remote state file %s", c.stateFile) 42 43 exists, data, checksum, err := c.getObject(c.stateFile) 44 if err != nil { 45 return nil, err 46 } 47 48 if !exists { 49 return nil, nil 50 } 51 52 payload := &remote.Payload{ 53 Data: data, 54 MD5: []byte(checksum), 55 } 56 57 return payload, nil 58 } 59 60 // Put put state file to remote 61 func (c *remoteClient) Put(data []byte) error { 62 log.Printf("[DEBUG] put remote state file %s", c.stateFile) 63 64 return c.putObject(c.stateFile, data) 65 } 66 67 // Delete delete remote state file 68 func (c *remoteClient) Delete() error { 69 log.Printf("[DEBUG] delete remote state file %s", c.stateFile) 70 71 return c.deleteObject(c.stateFile) 72 } 73 74 // Lock lock remote state file for writing 75 func (c *remoteClient) Lock(info *state.LockInfo) (string, error) { 76 log.Printf("[DEBUG] lock remote state file %s", c.lockFile) 77 78 err := c.cosLock(c.bucket, c.lockFile) 79 if err != nil { 80 return "", c.lockError(err) 81 } 82 defer c.cosUnlock(c.bucket, c.lockFile) 83 84 exists, _, _, err := c.getObject(c.lockFile) 85 if err != nil { 86 return "", c.lockError(err) 87 } 88 89 if exists { 90 return "", c.lockError(fmt.Errorf("lock file %s exists", c.lockFile)) 91 } 92 93 info.Path = c.lockFile 94 data, err := json.Marshal(info) 95 if err != nil { 96 return "", c.lockError(err) 97 } 98 99 check := fmt.Sprintf("%x", md5.Sum(data)) 100 err = c.putObject(c.lockFile, data) 101 if err != nil { 102 return "", c.lockError(err) 103 } 104 105 return check, nil 106 } 107 108 // Unlock unlock remote state file 109 func (c *remoteClient) Unlock(check string) error { 110 log.Printf("[DEBUG] unlock remote state file %s", c.lockFile) 111 112 info, err := c.lockInfo() 113 if err != nil { 114 return c.lockError(err) 115 } 116 117 if info.ID != check { 118 return c.lockError(fmt.Errorf("lock id mismatch, %v != %v", info.ID, check)) 119 } 120 121 err = c.deleteObject(c.lockFile) 122 if err != nil { 123 return c.lockError(err) 124 } 125 126 return nil 127 } 128 129 // lockError returns state.LockError 130 func (c *remoteClient) lockError(err error) *state.LockError { 131 log.Printf("[DEBUG] failed to lock or unlock %s: %v", c.lockFile, err) 132 133 lockErr := &state.LockError{ 134 Err: err, 135 } 136 137 info, infoErr := c.lockInfo() 138 if infoErr != nil { 139 lockErr.Err = multierror.Append(lockErr.Err, infoErr) 140 } else { 141 lockErr.Info = info 142 } 143 144 return lockErr 145 } 146 147 // lockInfo returns LockInfo from lock file 148 func (c *remoteClient) lockInfo() (*state.LockInfo, error) { 149 exists, data, checksum, err := c.getObject(c.lockFile) 150 if err != nil { 151 return nil, err 152 } 153 154 if !exists { 155 return nil, fmt.Errorf("lock file %s not exists", c.lockFile) 156 } 157 158 info := &state.LockInfo{} 159 if err := json.Unmarshal(data, info); err != nil { 160 return nil, err 161 } 162 163 info.ID = checksum 164 165 return info, nil 166 } 167 168 // getObject get remote object 169 func (c *remoteClient) getObject(cosFile string) (exists bool, data []byte, checksum string, err error) { 170 rsp, err := c.cosClient.Object.Get(c.cosContext, cosFile, nil) 171 if rsp == nil { 172 log.Printf("[DEBUG] getObject %s: error: %v", cosFile, err) 173 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 174 return 175 } 176 defer rsp.Body.Close() 177 178 log.Printf("[DEBUG] getObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 179 if err != nil { 180 if rsp.StatusCode == 404 { 181 err = nil 182 } else { 183 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 184 } 185 return 186 } 187 188 checksum = rsp.Header.Get("X-Cos-Meta-Md5") 189 log.Printf("[DEBUG] getObject %s: checksum: %s", cosFile, checksum) 190 if len(checksum) != 32 { 191 err = fmt.Errorf("failed to open file at %v: checksum %s invalid", cosFile, checksum) 192 return 193 } 194 195 exists = true 196 data, err = ioutil.ReadAll(rsp.Body) 197 log.Printf("[DEBUG] getObject %s: data length: %d", cosFile, len(data)) 198 if err != nil { 199 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 200 return 201 } 202 203 check := fmt.Sprintf("%x", md5.Sum(data)) 204 log.Printf("[DEBUG] getObject %s: check: %s", cosFile, check) 205 if check != checksum { 206 err = fmt.Errorf("failed to open file at %v: checksum mismatch, %s != %s", cosFile, check, checksum) 207 return 208 } 209 210 return 211 } 212 213 // putObject put object to remote 214 func (c *remoteClient) putObject(cosFile string, data []byte) error { 215 opt := &cos.ObjectPutOptions{ 216 ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ 217 XCosMetaXXX: &http.Header{ 218 "X-Cos-Meta-Md5": []string{fmt.Sprintf("%x", md5.Sum(data))}, 219 }, 220 }, 221 ACLHeaderOptions: &cos.ACLHeaderOptions{ 222 XCosACL: c.acl, 223 }, 224 } 225 226 if c.encrypt { 227 opt.ObjectPutHeaderOptions.XCosServerSideEncryption = "AES256" 228 } 229 230 r := bytes.NewReader(data) 231 rsp, err := c.cosClient.Object.Put(c.cosContext, cosFile, r, opt) 232 if rsp == nil { 233 log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err) 234 return fmt.Errorf("failed to save file to %v: %v", cosFile, err) 235 } 236 defer rsp.Body.Close() 237 238 log.Printf("[DEBUG] putObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 239 if err != nil { 240 return fmt.Errorf("failed to save file to %v: %v", cosFile, err) 241 } 242 243 return nil 244 } 245 246 // deleteObject delete remote object 247 func (c *remoteClient) deleteObject(cosFile string) error { 248 rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile) 249 if rsp == nil { 250 log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err) 251 return fmt.Errorf("failed to delete file %v: %v", cosFile, err) 252 } 253 defer rsp.Body.Close() 254 255 log.Printf("[DEBUG] deleteObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 256 if rsp.StatusCode == 404 { 257 return nil 258 } 259 260 if err != nil { 261 return fmt.Errorf("failed to delete file %v: %v", cosFile, err) 262 } 263 264 return nil 265 } 266 267 // getBucket list bucket by prefix 268 func (c *remoteClient) getBucket(prefix string) (obs []cos.Object, err error) { 269 fs, rsp, err := c.cosClient.Bucket.Get(c.cosContext, &cos.BucketGetOptions{Prefix: prefix}) 270 if rsp == nil { 271 log.Printf("[DEBUG] getBucket %s/%s: error: %v", c.bucket, prefix, err) 272 err = fmt.Errorf("bucket %s not exists", c.bucket) 273 return 274 } 275 defer rsp.Body.Close() 276 277 log.Printf("[DEBUG] getBucket %s/%s: code: %d, error: %v", c.bucket, prefix, rsp.StatusCode, err) 278 if rsp.StatusCode == 404 { 279 err = fmt.Errorf("bucket %s not exists", c.bucket) 280 return 281 } 282 283 if err != nil { 284 return 285 } 286 287 return fs.Contents, nil 288 } 289 290 // putBucket create cos bucket 291 func (c *remoteClient) putBucket() error { 292 rsp, err := c.cosClient.Bucket.Put(c.cosContext, nil) 293 if rsp == nil { 294 log.Printf("[DEBUG] putBucket %s: error: %v", c.bucket, err) 295 return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err) 296 } 297 defer rsp.Body.Close() 298 299 log.Printf("[DEBUG] putBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err) 300 if rsp.StatusCode == 409 { 301 return nil 302 } 303 304 if err != nil { 305 return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err) 306 } 307 308 return nil 309 } 310 311 // deleteBucket delete cos bucket 312 func (c *remoteClient) deleteBucket(recursive bool) error { 313 if recursive { 314 obs, err := c.getBucket("") 315 if err != nil { 316 if strings.Contains(err.Error(), "not exists") { 317 return nil 318 } 319 log.Printf("[DEBUG] deleteBucket %s: empty bucket error: %v", c.bucket, err) 320 return fmt.Errorf("failed to empty bucket %v: %v", c.bucket, err) 321 } 322 for _, v := range obs { 323 c.deleteObject(v.Key) 324 } 325 } 326 327 rsp, err := c.cosClient.Bucket.Delete(c.cosContext) 328 if rsp == nil { 329 log.Printf("[DEBUG] deleteBucket %s: error: %v", c.bucket, err) 330 return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err) 331 } 332 defer rsp.Body.Close() 333 334 log.Printf("[DEBUG] deleteBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err) 335 if rsp.StatusCode == 404 { 336 return nil 337 } 338 339 if err != nil { 340 return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err) 341 } 342 343 return nil 344 } 345 346 // cosLock lock cos for writing 347 func (c *remoteClient) cosLock(bucket, cosFile string) error { 348 log.Printf("[DEBUG] lock cos file %s:%s", bucket, cosFile) 349 350 cosPath := fmt.Sprintf("%s:%s", bucket, cosFile) 351 lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath))) 352 353 return c.CreateTag(lockTagKey, lockTagValue) 354 } 355 356 // cosUnlock unlock cos writing 357 func (c *remoteClient) cosUnlock(bucket, cosFile string) error { 358 log.Printf("[DEBUG] unlock cos file %s:%s", bucket, cosFile) 359 360 cosPath := fmt.Sprintf("%s:%s", bucket, cosFile) 361 lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath))) 362 363 var err error 364 for i := 0; i < 30; i++ { 365 err = c.DeleteTag(lockTagKey, lockTagValue) 366 if err == nil { 367 return nil 368 } 369 time.Sleep(1 * time.Second) 370 } 371 372 return err 373 } 374 375 // CreateTag create tag by key and value 376 func (c *remoteClient) CreateTag(key, value string) error { 377 request := tag.NewCreateTagRequest() 378 request.TagKey = &key 379 request.TagValue = &value 380 381 _, err := c.tagClient.CreateTag(request) 382 log.Printf("[DEBUG] create tag %s:%s: error: %v", key, value, err) 383 if err != nil { 384 return fmt.Errorf("failed to create tag: %s -> %s: %s", key, value, err) 385 } 386 387 return nil 388 } 389 390 // DeleteTag create tag by key and value 391 func (c *remoteClient) DeleteTag(key, value string) error { 392 request := tag.NewDeleteTagRequest() 393 request.TagKey = &key 394 request.TagValue = &value 395 396 _, err := c.tagClient.DeleteTag(request) 397 log.Printf("[DEBUG] delete tag %s:%s: error: %v", key, value, err) 398 if err != nil { 399 return fmt.Errorf("failed to delete tag: %s -> %s: %s", key, value, err) 400 } 401 402 return nil 403 }