github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/cos/backend.go (about)

     1  package cos
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
    14  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
    15  	tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813"
    16  	"github.com/tencentyun/cos-go-sdk-v5"
    17  )
    18  
    19  // Default value from environment variable
    20  const (
    21  	PROVIDER_SECRET_ID  = "TENCENTCLOUD_SECRET_ID"
    22  	PROVIDER_SECRET_KEY = "TENCENTCLOUD_SECRET_KEY"
    23  	PROVIDER_REGION     = "TENCENTCLOUD_REGION"
    24  )
    25  
    26  // Backend implements "backend".Backend for tencentCloud cos
    27  type Backend struct {
    28  	*schema.Backend
    29  
    30  	cosContext context.Context
    31  	cosClient  *cos.Client
    32  	tagClient  *tag.Client
    33  
    34  	region  string
    35  	bucket  string
    36  	prefix  string
    37  	key     string
    38  	encrypt bool
    39  	acl     string
    40  }
    41  
    42  // New creates a new backend for TencentCloud cos remote state.
    43  func New() backend.Backend {
    44  	s := &schema.Backend{
    45  		Schema: map[string]*schema.Schema{
    46  			"secret_id": {
    47  				Type:        schema.TypeString,
    48  				Required:    true,
    49  				DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_ID, nil),
    50  				Description: "Secret id of Tencent Cloud",
    51  			},
    52  			"secret_key": {
    53  				Type:        schema.TypeString,
    54  				Required:    true,
    55  				DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_KEY, nil),
    56  				Description: "Secret key of Tencent Cloud",
    57  				Sensitive:   true,
    58  			},
    59  			"region": {
    60  				Type:         schema.TypeString,
    61  				Required:     true,
    62  				DefaultFunc:  schema.EnvDefaultFunc(PROVIDER_REGION, nil),
    63  				Description:  "The region of the COS bucket",
    64  				InputDefault: "ap-guangzhou",
    65  			},
    66  			"bucket": {
    67  				Type:        schema.TypeString,
    68  				Required:    true,
    69  				Description: "The name of the COS bucket",
    70  			},
    71  			"prefix": {
    72  				Type:        schema.TypeString,
    73  				Optional:    true,
    74  				Description: "The directory for saving the state file in bucket",
    75  				ValidateFunc: func(v interface{}, s string) ([]string, []error) {
    76  					prefix := v.(string)
    77  					if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") {
    78  						return nil, []error{fmt.Errorf("prefix must not start with '/' or './'")}
    79  					}
    80  					return nil, nil
    81  				},
    82  			},
    83  			"key": {
    84  				Type:        schema.TypeString,
    85  				Optional:    true,
    86  				Description: "The path for saving the state file in bucket",
    87  				Default:     "terraform.tfstate",
    88  				ValidateFunc: func(v interface{}, s string) ([]string, []error) {
    89  					if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") {
    90  						return nil, []error{fmt.Errorf("key can not start and end with '/'")}
    91  					}
    92  					return nil, nil
    93  				},
    94  			},
    95  			"encrypt": {
    96  				Type:        schema.TypeBool,
    97  				Optional:    true,
    98  				Description: "Whether to enable server side encryption of the state file",
    99  				Default:     true,
   100  			},
   101  			"acl": {
   102  				Type:        schema.TypeString,
   103  				Optional:    true,
   104  				Description: "Object ACL to be applied to the state file",
   105  				Default:     "private",
   106  				ValidateFunc: func(v interface{}, s string) ([]string, []error) {
   107  					value := v.(string)
   108  					if value != "private" && value != "public-read" {
   109  						return nil, []error{fmt.Errorf(
   110  							"acl value invalid, expected %s or %s, got %s",
   111  							"private", "public-read", value)}
   112  					}
   113  					return nil, nil
   114  				},
   115  			},
   116  		},
   117  	}
   118  
   119  	result := &Backend{Backend: s}
   120  	result.Backend.ConfigureFunc = result.configure
   121  
   122  	return result
   123  }
   124  
   125  // configure init cos client
   126  func (b *Backend) configure(ctx context.Context) error {
   127  	if b.cosClient != nil {
   128  		return nil
   129  	}
   130  
   131  	b.cosContext = ctx
   132  	data := schema.FromContextBackendConfig(b.cosContext)
   133  
   134  	b.region = data.Get("region").(string)
   135  	b.bucket = data.Get("bucket").(string)
   136  	b.prefix = data.Get("prefix").(string)
   137  	b.key = data.Get("key").(string)
   138  	b.encrypt = data.Get("encrypt").(bool)
   139  	b.acl = data.Get("acl").(string)
   140  
   141  	u, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	b.cosClient = cos.NewClient(
   147  		&cos.BaseURL{BucketURL: u},
   148  		&http.Client{
   149  			Timeout: 60 * time.Second,
   150  			Transport: &cos.AuthorizationTransport{
   151  				SecretID:  data.Get("secret_id").(string),
   152  				SecretKey: data.Get("secret_key").(string),
   153  			},
   154  		},
   155  	)
   156  
   157  	credential := common.NewCredential(
   158  		data.Get("secret_id").(string),
   159  		data.Get("secret_key").(string),
   160  	)
   161  
   162  	cpf := profile.NewClientProfile()
   163  	cpf.HttpProfile.ReqMethod = "POST"
   164  	cpf.HttpProfile.ReqTimeout = 300
   165  	cpf.Language = "en-US"
   166  	b.tagClient, err = tag.NewClient(credential, b.region, cpf)
   167  
   168  	return err
   169  }