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  }