github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/internal/states/remote" 17 "github.com/hashicorp/terraform/internal/states/statemgr" 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 *statemgr.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 err = c.cosUnlock(c.bucket, c.lockFile) 127 if err != nil { 128 return c.lockError(err) 129 } 130 131 return nil 132 } 133 134 // lockError returns statemgr.LockError 135 func (c *remoteClient) lockError(err error) *statemgr.LockError { 136 log.Printf("[DEBUG] failed to lock or unlock %s: %v", c.lockFile, err) 137 138 lockErr := &statemgr.LockError{ 139 Err: err, 140 } 141 142 info, infoErr := c.lockInfo() 143 if infoErr != nil { 144 lockErr.Err = multierror.Append(lockErr.Err, infoErr) 145 } else { 146 lockErr.Info = info 147 } 148 149 return lockErr 150 } 151 152 // lockInfo returns LockInfo from lock file 153 func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) { 154 exists, data, checksum, err := c.getObject(c.lockFile) 155 if err != nil { 156 return nil, err 157 } 158 159 if !exists { 160 return nil, fmt.Errorf("lock file %s not exists", c.lockFile) 161 } 162 163 info := &statemgr.LockInfo{} 164 if err := json.Unmarshal(data, info); err != nil { 165 return nil, err 166 } 167 168 info.ID = checksum 169 170 return info, nil 171 } 172 173 // getObject get remote object 174 func (c *remoteClient) getObject(cosFile string) (exists bool, data []byte, checksum string, err error) { 175 rsp, err := c.cosClient.Object.Get(c.cosContext, cosFile, nil) 176 if rsp == nil { 177 log.Printf("[DEBUG] getObject %s: error: %v", cosFile, err) 178 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 179 return 180 } 181 defer rsp.Body.Close() 182 183 log.Printf("[DEBUG] getObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 184 if err != nil { 185 if rsp.StatusCode == 404 { 186 err = nil 187 } else { 188 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 189 } 190 return 191 } 192 193 checksum = rsp.Header.Get("X-Cos-Meta-Md5") 194 log.Printf("[DEBUG] getObject %s: checksum: %s", cosFile, checksum) 195 if len(checksum) != 32 { 196 err = fmt.Errorf("failed to open file at %v: checksum %s invalid", cosFile, checksum) 197 return 198 } 199 200 exists = true 201 data, err = ioutil.ReadAll(rsp.Body) 202 log.Printf("[DEBUG] getObject %s: data length: %d", cosFile, len(data)) 203 if err != nil { 204 err = fmt.Errorf("failed to open file at %v: %v", cosFile, err) 205 return 206 } 207 208 check := fmt.Sprintf("%x", md5.Sum(data)) 209 log.Printf("[DEBUG] getObject %s: check: %s", cosFile, check) 210 if check != checksum { 211 err = fmt.Errorf("failed to open file at %v: checksum mismatch, %s != %s", cosFile, check, checksum) 212 return 213 } 214 215 return 216 } 217 218 // putObject put object to remote 219 func (c *remoteClient) putObject(cosFile string, data []byte) error { 220 opt := &cos.ObjectPutOptions{ 221 ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ 222 XCosMetaXXX: &http.Header{ 223 "X-Cos-Meta-Md5": []string{fmt.Sprintf("%x", md5.Sum(data))}, 224 }, 225 }, 226 ACLHeaderOptions: &cos.ACLHeaderOptions{ 227 XCosACL: c.acl, 228 }, 229 } 230 231 if c.encrypt { 232 opt.ObjectPutHeaderOptions.XCosServerSideEncryption = "AES256" 233 } 234 235 r := bytes.NewReader(data) 236 rsp, err := c.cosClient.Object.Put(c.cosContext, cosFile, r, opt) 237 if rsp == nil { 238 log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err) 239 return fmt.Errorf("failed to save file to %v: %v", cosFile, err) 240 } 241 defer rsp.Body.Close() 242 243 log.Printf("[DEBUG] putObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 244 if err != nil { 245 return fmt.Errorf("failed to save file to %v: %v", cosFile, err) 246 } 247 248 return nil 249 } 250 251 // deleteObject delete remote object 252 func (c *remoteClient) deleteObject(cosFile string) error { 253 rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile) 254 if rsp == nil { 255 log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err) 256 return fmt.Errorf("failed to delete file %v: %v", cosFile, err) 257 } 258 defer rsp.Body.Close() 259 260 log.Printf("[DEBUG] deleteObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err) 261 if rsp.StatusCode == 404 { 262 return nil 263 } 264 265 if err != nil { 266 return fmt.Errorf("failed to delete file %v: %v", cosFile, err) 267 } 268 269 return nil 270 } 271 272 // getBucket list bucket by prefix 273 func (c *remoteClient) getBucket(prefix string) (obs []cos.Object, err error) { 274 fs, rsp, err := c.cosClient.Bucket.Get(c.cosContext, &cos.BucketGetOptions{Prefix: prefix}) 275 if rsp == nil { 276 log.Printf("[DEBUG] getBucket %s/%s: error: %v", c.bucket, prefix, err) 277 err = fmt.Errorf("bucket %s not exists", c.bucket) 278 return 279 } 280 defer rsp.Body.Close() 281 282 log.Printf("[DEBUG] getBucket %s/%s: code: %d, error: %v", c.bucket, prefix, rsp.StatusCode, err) 283 if rsp.StatusCode == 404 { 284 err = fmt.Errorf("bucket %s not exists", c.bucket) 285 return 286 } 287 288 if err != nil { 289 return 290 } 291 292 return fs.Contents, nil 293 } 294 295 // putBucket create cos bucket 296 func (c *remoteClient) putBucket() error { 297 rsp, err := c.cosClient.Bucket.Put(c.cosContext, nil) 298 if rsp == nil { 299 log.Printf("[DEBUG] putBucket %s: error: %v", c.bucket, err) 300 return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err) 301 } 302 defer rsp.Body.Close() 303 304 log.Printf("[DEBUG] putBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err) 305 if rsp.StatusCode == 409 { 306 return nil 307 } 308 309 if err != nil { 310 return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err) 311 } 312 313 return nil 314 } 315 316 // deleteBucket delete cos bucket 317 func (c *remoteClient) deleteBucket(recursive bool) error { 318 if recursive { 319 obs, err := c.getBucket("") 320 if err != nil { 321 if strings.Contains(err.Error(), "not exists") { 322 return nil 323 } 324 log.Printf("[DEBUG] deleteBucket %s: empty bucket error: %v", c.bucket, err) 325 return fmt.Errorf("failed to empty bucket %v: %v", c.bucket, err) 326 } 327 for _, v := range obs { 328 c.deleteObject(v.Key) 329 } 330 } 331 332 rsp, err := c.cosClient.Bucket.Delete(c.cosContext) 333 if rsp == nil { 334 log.Printf("[DEBUG] deleteBucket %s: error: %v", c.bucket, err) 335 return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err) 336 } 337 defer rsp.Body.Close() 338 339 log.Printf("[DEBUG] deleteBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err) 340 if rsp.StatusCode == 404 { 341 return nil 342 } 343 344 if err != nil { 345 return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err) 346 } 347 348 return nil 349 } 350 351 // cosLock lock cos for writing 352 func (c *remoteClient) cosLock(bucket, cosFile string) error { 353 log.Printf("[DEBUG] lock cos file %s:%s", bucket, cosFile) 354 355 cosPath := fmt.Sprintf("%s:%s", bucket, cosFile) 356 lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath))) 357 358 return c.CreateTag(lockTagKey, lockTagValue) 359 } 360 361 // cosUnlock unlock cos writing 362 func (c *remoteClient) cosUnlock(bucket, cosFile string) error { 363 log.Printf("[DEBUG] unlock cos file %s:%s", bucket, cosFile) 364 365 cosPath := fmt.Sprintf("%s:%s", bucket, cosFile) 366 lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath))) 367 368 var err error 369 for i := 0; i < 30; i++ { 370 tagExists, err := c.CheckTag(lockTagKey, lockTagValue) 371 372 if err != nil { 373 return err 374 } 375 376 if !tagExists { 377 return nil 378 } 379 380 err = c.DeleteTag(lockTagKey, lockTagValue) 381 if err == nil { 382 return nil 383 } 384 time.Sleep(1 * time.Second) 385 } 386 387 return err 388 } 389 390 // CheckTag checks if tag key:value exists 391 func (c *remoteClient) CheckTag(key, value string) (exists bool, err error) { 392 request := tag.NewDescribeTagsRequest() 393 request.TagKey = &key 394 request.TagValue = &value 395 396 response, err := c.tagClient.DescribeTags(request) 397 log.Printf("[DEBUG] create tag %s:%s: error: %v", key, value, err) 398 if err != nil { 399 return 400 } 401 402 if len(response.Response.Tags) == 0 { 403 return 404 } 405 406 tagKey := response.Response.Tags[0].TagKey 407 tagValue := response.Response.Tags[0].TagValue 408 409 exists = key == *tagKey && value == *tagValue 410 411 return 412 } 413 414 // CreateTag create tag by key and value 415 func (c *remoteClient) CreateTag(key, value string) error { 416 request := tag.NewCreateTagRequest() 417 request.TagKey = &key 418 request.TagValue = &value 419 420 _, err := c.tagClient.CreateTag(request) 421 log.Printf("[DEBUG] create tag %s:%s: error: %v", key, value, err) 422 if err != nil { 423 return fmt.Errorf("failed to create tag: %s -> %s: %s", key, value, err) 424 } 425 426 return nil 427 } 428 429 // DeleteTag create tag by key and value 430 func (c *remoteClient) DeleteTag(key, value string) error { 431 request := tag.NewDeleteTagRequest() 432 request.TagKey = &key 433 request.TagValue = &value 434 435 _, err := c.tagClient.DeleteTag(request) 436 log.Printf("[DEBUG] delete tag %s:%s: error: %v", key, value, err) 437 if err != nil { 438 return fmt.Errorf("failed to delete tag: %s -> %s: %s", key, value, err) 439 } 440 441 return nil 442 }