github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/cos/backend.go (about) 1 package cos 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "net/url" 8 "os" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/terraform/internal/backend" 14 "github.com/hashicorp/terraform/internal/legacy/helper/schema" 15 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 16 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" 17 sts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813" 18 tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" 19 "github.com/tencentyun/cos-go-sdk-v5" 20 ) 21 22 // Default value from environment variable 23 const ( 24 PROVIDER_SECRET_ID = "TENCENTCLOUD_SECRET_ID" 25 PROVIDER_SECRET_KEY = "TENCENTCLOUD_SECRET_KEY" 26 PROVIDER_SECURITY_TOKEN = "TENCENTCLOUD_SECURITY_TOKEN" 27 PROVIDER_REGION = "TENCENTCLOUD_REGION" 28 PROVIDER_ASSUME_ROLE_ARN = "TENCENTCLOUD_ASSUME_ROLE_ARN" 29 PROVIDER_ASSUME_ROLE_SESSION_NAME = "TENCENTCLOUD_ASSUME_ROLE_SESSION_NAME" 30 PROVIDER_ASSUME_ROLE_SESSION_DURATION = "TENCENTCLOUD_ASSUME_ROLE_SESSION_DURATION" 31 ) 32 33 // Backend implements "backend".Backend for tencentCloud cos 34 type Backend struct { 35 *schema.Backend 36 credential *common.Credential 37 38 cosContext context.Context 39 cosClient *cos.Client 40 tagClient *tag.Client 41 stsClient *sts.Client 42 43 region string 44 bucket string 45 prefix string 46 key string 47 encrypt bool 48 acl string 49 } 50 51 // New creates a new backend for TencentCloud cos remote state. 52 func New() backend.Backend { 53 s := &schema.Backend{ 54 Schema: map[string]*schema.Schema{ 55 "secret_id": { 56 Type: schema.TypeString, 57 Optional: true, 58 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_ID, nil), 59 Description: "Secret id of Tencent Cloud", 60 }, 61 "secret_key": { 62 Type: schema.TypeString, 63 Optional: true, 64 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_KEY, nil), 65 Description: "Secret key of Tencent Cloud", 66 Sensitive: true, 67 }, 68 "security_token": { 69 Type: schema.TypeString, 70 Optional: true, 71 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECURITY_TOKEN, nil), 72 Description: "TencentCloud Security Token of temporary access credentials. It can be sourced from the `TENCENTCLOUD_SECURITY_TOKEN` environment variable. Notice: for supported products, please refer to: [temporary key supported products](https://intl.cloud.tencent.com/document/product/598/10588).", 73 Sensitive: true, 74 }, 75 "region": { 76 Type: schema.TypeString, 77 Required: true, 78 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_REGION, nil), 79 Description: "The region of the COS bucket", 80 InputDefault: "ap-guangzhou", 81 }, 82 "bucket": { 83 Type: schema.TypeString, 84 Required: true, 85 Description: "The name of the COS bucket", 86 }, 87 "prefix": { 88 Type: schema.TypeString, 89 Optional: true, 90 Description: "The directory for saving the state file in bucket", 91 ValidateFunc: func(v interface{}, s string) ([]string, []error) { 92 prefix := v.(string) 93 if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") { 94 return nil, []error{fmt.Errorf("prefix must not start with '/' or './'")} 95 } 96 return nil, nil 97 }, 98 }, 99 "key": { 100 Type: schema.TypeString, 101 Optional: true, 102 Description: "The path for saving the state file in bucket", 103 Default: "terraform.tfstate", 104 ValidateFunc: func(v interface{}, s string) ([]string, []error) { 105 if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") { 106 return nil, []error{fmt.Errorf("key can not start and end with '/'")} 107 } 108 return nil, nil 109 }, 110 }, 111 "encrypt": { 112 Type: schema.TypeBool, 113 Optional: true, 114 Description: "Whether to enable server side encryption of the state file", 115 Default: true, 116 }, 117 "acl": { 118 Type: schema.TypeString, 119 Optional: true, 120 Description: "Object ACL to be applied to the state file", 121 Default: "private", 122 ValidateFunc: func(v interface{}, s string) ([]string, []error) { 123 value := v.(string) 124 if value != "private" && value != "public-read" { 125 return nil, []error{fmt.Errorf( 126 "acl value invalid, expected %s or %s, got %s", 127 "private", "public-read", value)} 128 } 129 return nil, nil 130 }, 131 }, 132 "accelerate": { 133 Type: schema.TypeBool, 134 Optional: true, 135 Description: "Whether to enable global Acceleration", 136 Default: false, 137 }, 138 "assume_role": { 139 Type: schema.TypeSet, 140 Optional: true, 141 MaxItems: 1, 142 Description: "The `assume_role` block. If provided, terraform will attempt to assume this role using the supplied credentials.", 143 Elem: &schema.Resource{ 144 Schema: map[string]*schema.Schema{ 145 "role_arn": { 146 Type: schema.TypeString, 147 Required: true, 148 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_ASSUME_ROLE_ARN, nil), 149 Description: "The ARN of the role to assume. It can be sourced from the `TENCENTCLOUD_ASSUME_ROLE_ARN`.", 150 }, 151 "session_name": { 152 Type: schema.TypeString, 153 Required: true, 154 DefaultFunc: schema.EnvDefaultFunc(PROVIDER_ASSUME_ROLE_SESSION_NAME, nil), 155 Description: "The session name to use when making the AssumeRole call. It can be sourced from the `TENCENTCLOUD_ASSUME_ROLE_SESSION_NAME`.", 156 }, 157 "session_duration": { 158 Type: schema.TypeInt, 159 Required: true, 160 DefaultFunc: func() (interface{}, error) { 161 if v := os.Getenv(PROVIDER_ASSUME_ROLE_SESSION_DURATION); v != "" { 162 return strconv.Atoi(v) 163 } 164 return 7200, nil 165 }, 166 ValidateFunc: validateIntegerInRange(0, 43200), 167 Description: "The duration of the session when making the AssumeRole call. Its value ranges from 0 to 43200(seconds), and default is 7200 seconds. It can be sourced from the `TENCENTCLOUD_ASSUME_ROLE_SESSION_DURATION`.", 168 }, 169 "policy": { 170 Type: schema.TypeString, 171 Optional: true, 172 Description: "A more restrictive policy when making the AssumeRole call. Its content must not contains `principal` elements. Notice: more syntax references, please refer to: [policies syntax logic](https://intl.cloud.tencent.com/document/product/598/10603).", 173 }, 174 }, 175 }, 176 }, 177 }, 178 } 179 180 result := &Backend{Backend: s} 181 result.Backend.ConfigureFunc = result.configure 182 183 return result 184 } 185 186 func validateIntegerInRange(min, max int64) schema.SchemaValidateFunc { 187 return func(v interface{}, k string) (ws []string, errors []error) { 188 value := int64(v.(int)) 189 if value < min { 190 errors = append(errors, fmt.Errorf( 191 "%q cannot be lower than %d: %d", k, min, value)) 192 } 193 if value > max { 194 errors = append(errors, fmt.Errorf( 195 "%q cannot be higher than %d: %d", k, max, value)) 196 } 197 return 198 } 199 } 200 201 // configure init cos client 202 func (b *Backend) configure(ctx context.Context) error { 203 if b.cosClient != nil { 204 return nil 205 } 206 207 b.cosContext = ctx 208 data := schema.FromContextBackendConfig(b.cosContext) 209 210 b.region = data.Get("region").(string) 211 b.bucket = data.Get("bucket").(string) 212 b.prefix = data.Get("prefix").(string) 213 b.key = data.Get("key").(string) 214 b.encrypt = data.Get("encrypt").(bool) 215 b.acl = data.Get("acl").(string) 216 217 var ( 218 u *url.URL 219 err error 220 ) 221 accelerate := data.Get("accelerate").(bool) 222 if accelerate { 223 u, err = url.Parse(fmt.Sprintf("https://%s.cos.accelerate.myqcloud.com", b.bucket)) 224 } else { 225 u, err = url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region)) 226 } 227 if err != nil { 228 return err 229 } 230 231 secretId := data.Get("secret_id").(string) 232 secretKey := data.Get("secret_key").(string) 233 securityToken := data.Get("security_token").(string) 234 235 // init credential by AKSK & TOKEN 236 b.credential = common.NewTokenCredential(secretId, secretKey, securityToken) 237 // update credential if assume role exist 238 err = handleAssumeRole(data, b) 239 if err != nil { 240 return err 241 } 242 243 b.cosClient = cos.NewClient( 244 &cos.BaseURL{BucketURL: u}, 245 &http.Client{ 246 Timeout: 60 * time.Second, 247 Transport: &cos.AuthorizationTransport{ 248 SecretID: b.credential.SecretId, 249 SecretKey: b.credential.SecretKey, 250 SessionToken: b.credential.Token, 251 }, 252 }, 253 ) 254 255 b.tagClient = b.UseTagClient() 256 return err 257 } 258 259 func handleAssumeRole(data *schema.ResourceData, b *Backend) error { 260 assumeRoleList := data.Get("assume_role").(*schema.Set).List() 261 if len(assumeRoleList) == 1 { 262 assumeRole := assumeRoleList[0].(map[string]interface{}) 263 assumeRoleArn := assumeRole["role_arn"].(string) 264 assumeRoleSessionName := assumeRole["session_name"].(string) 265 assumeRoleSessionDuration := assumeRole["session_duration"].(int) 266 assumeRolePolicy := assumeRole["policy"].(string) 267 268 err := b.updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName, assumeRoleSessionDuration, assumeRolePolicy) 269 if err != nil { 270 return err 271 } 272 } 273 return nil 274 } 275 276 func (b *Backend) updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName string, assumeRoleSessionDuration int, assumeRolePolicy string) error { 277 // assume role by STS 278 request := sts.NewAssumeRoleRequest() 279 request.RoleArn = &assumeRoleArn 280 request.RoleSessionName = &assumeRoleSessionName 281 duration := uint64(assumeRoleSessionDuration) 282 request.DurationSeconds = &duration 283 if assumeRolePolicy != "" { 284 policy := url.QueryEscape(assumeRolePolicy) 285 request.Policy = &policy 286 } 287 288 response, err := b.UseStsClient().AssumeRole(request) 289 if err != nil { 290 return err 291 } 292 // update credentials by result of assume role 293 b.credential = common.NewTokenCredential( 294 *response.Response.Credentials.TmpSecretId, 295 *response.Response.Credentials.TmpSecretKey, 296 *response.Response.Credentials.Token, 297 ) 298 299 return nil 300 } 301 302 // UseStsClient returns sts client for service 303 func (b *Backend) UseStsClient() *sts.Client { 304 if b.stsClient != nil { 305 return b.stsClient 306 } 307 cpf := b.NewClientProfile(300) 308 b.stsClient, _ = sts.NewClient(b.credential, b.region, cpf) 309 b.stsClient.WithHttpTransport(&LogRoundTripper{}) 310 311 return b.stsClient 312 } 313 314 // UseTagClient returns tag client for service 315 func (b *Backend) UseTagClient() *tag.Client { 316 if b.tagClient != nil { 317 return b.tagClient 318 } 319 cpf := b.NewClientProfile(300) 320 cpf.Language = "en-US" 321 b.tagClient, _ = tag.NewClient(b.credential, b.region, cpf) 322 return b.tagClient 323 } 324 325 // NewClientProfile returns a new ClientProfile 326 func (b *Backend) NewClientProfile(timeout int) *profile.ClientProfile { 327 cpf := profile.NewClientProfile() 328 329 // all request use method POST 330 cpf.HttpProfile.ReqMethod = "POST" 331 // request timeout 332 cpf.HttpProfile.ReqTimeout = timeout 333 334 return cpf 335 }