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  }