diff --git a/common/common_volcengine_callback.go b/common/common_volcengine_callback.go index dca35510..1826e94f 100644 --- a/common/common_volcengine_callback.go +++ b/common/common_volcengine_callback.go @@ -17,19 +17,20 @@ type Callback struct { } type SdkCall struct { - Action string - BeforeCall BeforeCallFunc - ExecuteCall ExecuteCallFunc - CallError CallErrorFunc - AfterCall AfterCallFunc - Convert map[string]RequestConvert - ConvertMode RequestConvertMode - SdkParam *map[string]interface{} - RequestIdField string - Refresh *StateRefresh - ExtraRefresh map[ResourceService]*StateRefresh - ContentType RequestContentType - LockId LockId + Action string + BeforeCall BeforeCallFunc + ExecuteCall ExecuteCallFunc + CallError CallErrorFunc + AfterCall AfterCallFunc + Convert map[string]RequestConvert + ConvertMode RequestConvertMode + SdkParam *map[string]interface{} + RequestIdField string + Refresh *StateRefresh + ExtraRefresh map[ResourceService]*StateRefresh + ContentType RequestContentType + LockId LockId + ServiceCategory ServiceCategory } type StateRefresh struct { @@ -69,7 +70,7 @@ func (c *SdkCall) InitWriteCall(resourceData *schema.ResourceData, resource *sch return err } -func sortAndStartTransJson(source map[string]interface{}) map[string]interface{} { +func SortAndStartTransJson(source map[string]interface{}) map[string]interface{} { target := make(map[string]interface{}) var a []string for k := range source { @@ -162,6 +163,19 @@ func CallProcess(calls []SdkCall, d *schema.ResourceData, client *SdkClient, ser resp *map[string]interface{} ) doExecute := true + + switch fn.ServiceCategory { + case ServiceTos: + var trans map[string]interface{} + trans, err = convertToTosParams(fn.Convert, *fn.SdkParam) + if err != nil { + return err + } + fn.SdkParam = &trans + case DefaultServiceCategory: + break + } + if fn.BeforeCall != nil { doExecute, err = fn.BeforeCall(d, client, fn) } @@ -170,7 +184,7 @@ func CallProcess(calls []SdkCall, d *schema.ResourceData, client *SdkClient, ser case ContentTypeDefault: break case ContentTypeJson: - jsonParam := sortAndStartTransJson(*fn.SdkParam) + jsonParam := SortAndStartTransJson(*fn.SdkParam) fn.SdkParam = &jsonParam break } diff --git a/common/common_volcengine_callback_test.go b/common/common_volcengine_callback_test.go index 8644279d..9f519cfe 100644 --- a/common/common_volcengine_callback_test.go +++ b/common/common_volcengine_callback_test.go @@ -16,12 +16,12 @@ func TestSortAndStartTransJson1(t *testing.T) { "ClusterId": "12345", }, } - resp := sortAndStartTransJson(req) + resp := SortAndStartTransJson(req) assert.Equal(t, resp, target) } func TestSortAndStartTransJson2(t *testing.T) { - req := sortAndStartTransJson(map[string]interface{}{ + req := SortAndStartTransJson(map[string]interface{}{ "Filter.Ids.1": "id123", "Filter.Ids.2": "id456", }) @@ -30,12 +30,12 @@ func TestSortAndStartTransJson2(t *testing.T) { "Ids": []interface{}{"id123", "id456"}, }, } - resp := sortAndStartTransJson(req) + resp := SortAndStartTransJson(req) assert.Equal(t, resp, target) } func TestSortAndStartTransJson3(t *testing.T) { - req := sortAndStartTransJson(map[string]interface{}{ + req := SortAndStartTransJson(map[string]interface{}{ "Filter.ClusterId": "12345", "Filter.Ids.1": "id123", "Filter.Ids.2": "id456", @@ -61,7 +61,7 @@ func TestSortAndStartTransJson3(t *testing.T) { }, }, } - resp := sortAndStartTransJson(req) + resp := SortAndStartTransJson(req) assert.Equal(t, resp, target) str := `{"Filter":{"ClusterId":"12345","Ids":["id123","id456"],"Nets":[{"Subnet":"subnet1"},{"Subnet":"subnet2"},{"Subnet":"subnet3"}]}}` diff --git a/common/common_volcengine_client.go b/common/common_volcengine_client.go index 3f36afa7..64e281ee 100644 --- a/common/common_volcengine_client.go +++ b/common/common_volcengine_client.go @@ -24,4 +24,5 @@ type SdkClient struct { RdsClient *rdsmysql.RDSMYSQL RdsClientV2 *rdsmysqlv2.RDSMYSQLV2 UniversalClient *Universal + TosClient *Tos } diff --git a/common/common_volcengine_config.go b/common/common_volcengine_config.go index 36b192c7..74c5f3ea 100644 --- a/common/common_volcengine_config.go +++ b/common/common_volcengine_config.go @@ -75,6 +75,7 @@ func (c *Config) Client() (*SdkClient, error) { client.RdsClient = rdsmysql.New(sess) client.RdsClientV2 = rdsmysqlv2.New(sess) client.UniversalClient = NewUniversalClient(sess) + client.TosClient = NewTosClient(sess) InitLocks() InitSyncLimit() diff --git a/common/common_volcengine_const.go b/common/common_volcengine_const.go index 30227a4c..ea519e86 100644 --- a/common/common_volcengine_const.go +++ b/common/common_volcengine_const.go @@ -29,3 +29,20 @@ const ( ContentTypeDefault RequestContentType = iota ContentTypeJson ) + +type ServiceCategory int + +const ( + DefaultServiceCategory ServiceCategory = iota + ServiceTos +) + +type SpecialParamType int + +const ( + DomainParam SpecialParamType = iota + HeaderParam + PathParam + UrlParam + FilePathParam +) diff --git a/common/common_volcengine_convert.go b/common/common_volcengine_convert.go index d3b87dab..a21d41e7 100644 --- a/common/common_volcengine_convert.go +++ b/common/common_volcengine_convert.go @@ -28,6 +28,11 @@ type RequestConvert struct { TargetField string NextLevelConvert map[string]RequestConvert StartIndex int + SpecialParam *SpecialParam +} +type SpecialParam struct { + Type SpecialParamType + Index int } var supportRequestConvertType = map[RequestContentType]map[RequestConvertType]bool{ @@ -237,7 +242,7 @@ func ResourceDateToRequest(d *schema.ResourceData, resource *schema.Resource, is return req, err } -func Convert(d *schema.ResourceData, k string, v interface{}, t RequestConvert, index int, req *map[string]interface{}, chain string, forceGet bool, contentType RequestContentType, schemaChain string) (err error) { +func Convert(d *schema.ResourceData, k string, v interface{}, t RequestConvert, index int, req *map[string]interface{}, chain string, forceGet bool, contentType RequestContentType, schemaChain string, setIndex []int) (err error) { if !checkRequestConvertTypeSupport(contentType, t.ConvertType) { return fmt.Errorf("Can not support the RequestContentType [%v] when RequestContentType is [%v] ", t.ConvertType, contentType) } @@ -253,19 +258,19 @@ func Convert(d *schema.ResourceData, k string, v interface{}, t RequestConvert, err = RequestConvertWithN(v, k, t, req, chain) break case ConvertListN: - err = RequestConvertListN(v, k, t, req, chain, d, forceGet, false, contentType, schemaChain) + err = RequestConvertListN(v, k, t, req, chain, d, forceGet, false, contentType, schemaChain, setIndex) break case ConvertListUnique: - err = RequestConvertListN(v, k, t, req, chain, d, forceGet, true, contentType, schemaChain) + err = RequestConvertListN(v, k, t, req, chain, d, forceGet, true, contentType, schemaChain, setIndex) break case ConvertJsonObject: //equal ConvertListUnique - err = RequestConvertListN(v, k, t, req, chain, d, forceGet, true, contentType, schemaChain) + err = RequestConvertListN(v, k, t, req, chain, d, forceGet, true, contentType, schemaChain, setIndex) break case ConvertJsonArray: //equal ConvertWithN err = RequestConvertWithN(v, k, t, req, chain) break case ConvertJsonObjectArray: //equal ConvertListN - err = RequestConvertListN(v, k, t, req, chain, d, forceGet, false, contentType, schemaChain) + err = RequestConvertListN(v, k, t, req, chain, d, forceGet, false, contentType, schemaChain, setIndex) break //case ConvertWithFilter: // index, err = RequestConvertWithFilter(v, k, t, index, req) @@ -302,7 +307,7 @@ func RequestCreateConvert(d *schema.ResourceData, k string, t RequestConvert, in } } if ok { - err = Convert(d, k, v, t, index, req, "", forceGet, contentType, "") + err = Convert(d, k, v, t, index, req, "", forceGet, contentType, "", nil) } return index, err } @@ -365,7 +370,7 @@ func RequestConvertWithN(v interface{}, k string, t RequestConvert, req *map[str return nil } -func RequestConvertListN(v interface{}, k string, t RequestConvert, req *map[string]interface{}, chain string, d *schema.ResourceData, forceGet bool, single bool, contentType RequestContentType, schemaChain string) error { +func RequestConvertListN(v interface{}, k string, t RequestConvert, req *map[string]interface{}, chain string, d *schema.ResourceData, forceGet bool, single bool, contentType RequestContentType, schemaChain string, indexes []int) error { var ( err error isSet bool @@ -389,7 +394,13 @@ func RequestConvertListN(v interface{}, k string, t RequestConvert, req *map[str if t.NextLevelConvert != nil && t.NextLevelConvert[k2].ForceGet { flag = true } else { - schemaKey := fmt.Sprintf("%s.%d.%s", schemaChain+k, index, k2) + var schemaKey string + if len(indexes) > 0 { + schemaKey = fmt.Sprintf("%s.%d.%s", schemaChain+k, indexes[index], k2) + } else { + schemaKey = fmt.Sprintf("%s.%d.%s", schemaChain+k, index, k2) + } + if forceGet { if t.ForceGet || (d.HasChange(schemaKey) && !d.IsNewResource()) { flag = true @@ -411,9 +422,9 @@ func RequestConvertListN(v interface{}, k string, t RequestConvert, req *map[str switch reflect.TypeOf(v2).Kind() { case reflect.Slice: if t.NextLevelConvert[k2].Convert != nil { - err = Convert(d, k2, t.NextLevelConvert[k2].Convert(d, v2), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4) + err = Convert(d, k2, t.NextLevelConvert[k2].Convert(d, v2), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4, nil) } else { - err = Convert(d, k2, v2, t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4) + err = Convert(d, k2, v2, t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4, nil) } if err != nil { @@ -422,10 +433,14 @@ func RequestConvertListN(v interface{}, k string, t RequestConvert, req *map[str break case reflect.Ptr: if _v2, ok2 := v2.(*schema.Set); ok2 { + var setIndex []int + for _, mmm := range _v2.List() { + setIndex = append(setIndex, _v2.F(mmm)) + } if t.NextLevelConvert[k2].Convert != nil { - err = Convert(d, k2, t.NextLevelConvert[k2].Convert(d, _v2.List()), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4) + err = Convert(d, k2, t.NextLevelConvert[k2].Convert(d, _v2.List()), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4, setIndex) } else { - err = Convert(d, k2, _v2.List(), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4) + err = Convert(d, k2, _v2.List(), t.NextLevelConvert[k2], 0, req, k3, t.NextLevelConvert[k2].ForceGet, contentType, k4, setIndex) } if err != nil { return err diff --git a/common/common_volcengine_data_source.go b/common/common_volcengine_data_source.go index db088f28..d46bf486 100644 --- a/common/common_volcengine_data_source.go +++ b/common/common_volcengine_data_source.go @@ -26,6 +26,7 @@ type DataSourceInfo struct { CollectField string ContentType RequestContentType ExtraData ExtraData + ServiceCategory ServiceCategory } func DataSourceToRequest(d *schema.ResourceData, r *schema.Resource, info DataSourceInfo) (req map[string]interface{}, err error) { diff --git a/common/common_volcengine_dispatcher.go b/common/common_volcengine_dispatcher.go index 41093239..4a827e09 100644 --- a/common/common_volcengine_dispatcher.go +++ b/common/common_volcengine_dispatcher.go @@ -94,7 +94,16 @@ func (d *Dispatcher) Data(resourceService ResourceService, resourceDate *schema. return err } if info.ContentType == ContentTypeJson { - condition = sortAndStartTransJson(condition) + condition = SortAndStartTransJson(condition) + } + switch info.ServiceCategory { + case ServiceTos: + condition, err = convertToTosParams(info.RequestConverts, condition) + if err != nil { + return err + } + default: + break } collection, err = resourceService.ReadResources(condition) if err != nil { diff --git a/common/common_volcengine_tos_client.go b/common/common_volcengine_tos_client.go new file mode 100644 index 00000000..0aaf6ae6 --- /dev/null +++ b/common/common_volcengine_tos_client.go @@ -0,0 +1,140 @@ +package common + +import ( + "fmt" + "io/ioutil" + + "github.com/volcengine/volcengine-go-sdk/volcengine/client" + "github.com/volcengine/volcengine-go-sdk/volcengine/client/metadata" + "github.com/volcengine/volcengine-go-sdk/volcengine/corehandlers" + "github.com/volcengine/volcengine-go-sdk/volcengine/request" + "github.com/volcengine/volcengine-go-sdk/volcengine/session" +) + +const ( + TosInfoUrlParam = "TOS_URL_PARAM" + TosInfoInput = "TOS_INPUT" +) + +type Tos struct { + Session *session.Session +} + +type TosInfo struct { + ContentType ContentType + HttpMethod HttpMethod + Path []string + UrlParam map[string]string + Header map[string]string + Domain string + ContentPath string +} + +func NewTosClient(session *session.Session) *Tos { + return &Tos{ + Session: session, + } +} + +func (u *Tos) getMethod(m HttpMethod) string { + switch m { + case GET: + return "GET" + case POST: + return "POST" + case PUT: + return "PUT" + case DELETE: + return "DELETE" + case HEAD: + return "HEAD" + default: + return "GET" + } +} + +func (u *Tos) newTosClient(domain string) *client.Client { + svc := "tos" + config := u.Session.ClientConfig(svc) + var ( + endpoint string + ) + if domain == "" { + if config.Config.DisableSSL != nil && *config.Config.DisableSSL { + endpoint = fmt.Sprintf("%s://tos-%s.volces.com", "http", config.SigningRegion) + } else { + endpoint = fmt.Sprintf("%s://tos-%s.volces.com", "https", config.SigningRegion) + } + } else { + if config.Config.DisableSSL != nil && *config.Config.DisableSSL { + endpoint = fmt.Sprintf("%s://%s.tos-%s.volces.com", "http", domain, config.SigningRegion) + } else { + endpoint = fmt.Sprintf("%s://%s.tos-%s.volces.com", "https", domain, config.SigningRegion) + } + + } + + c := client.New( + *config.Config, + metadata.ClientInfo{ + SigningName: config.SigningName, + SigningRegion: config.SigningRegion, + Endpoint: endpoint, + ServiceName: svc, + ServiceID: svc, + }, + config.Handlers, + ) + c.Handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler) + c.Handlers.Build.PushBackNamed(corehandlers.AddHostExecEnvUserAgentHandler) + c.Handlers.Sign.PushBackNamed(tosSignRequestHandler) + c.Handlers.Build.PushBackNamed(tosBuildHandler) + c.Handlers.Unmarshal.PushBackNamed(tosUnmarshalHandler) + c.Handlers.UnmarshalError.PushBackNamed(tosUnmarshalErrorHandler) + + return c +} + +func (u *Tos) DoTosCall(info TosInfo, input *map[string]interface{}) (output *map[string]interface{}, err error) { + c := u.newTosClient(info.Domain) + trueInput := make(map[string]interface{}) + var httpPath string + + if len(info.Path) > 0 { + for _, v := range info.Path { + httpPath = httpPath + "/" + v + } + } + + op := &request.Operation{ + HTTPMethod: u.getMethod(info.HttpMethod), + HTTPPath: httpPath, + } + if input == nil { + input = &map[string]interface{}{} + } + trueInput[TosInfoInput] = input + if len(info.UrlParam) > 0 { + trueInput[TosInfoUrlParam] = info.UrlParam + } + output = &map[string]interface{}{} + req := c.NewRequest(op, &trueInput, output) + + if getContentType(info.ContentType) == "application/json" { + req.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8") + } + + if info.ContentPath != "" && (op.HTTPMethod == "PUT" || op.HTTPMethod == "POST") { + content, _ := ioutil.ReadFile(info.ContentPath) + req.SetBufferBody(content) + } + + if len(info.Header) > 0 { + for k, v := range info.Header { + req.HTTPRequest.Header.Set(k, v) + } + } + + err = req.Send() + return output, err +} diff --git a/common/common_volcengine_tos_handler.go b/common/common_volcengine_tos_handler.go new file mode 100644 index 00000000..c77e8766 --- /dev/null +++ b/common/common_volcengine_tos_handler.go @@ -0,0 +1,411 @@ +package common + +import ( + "bytes" + "crypto/hmac" + "crypto/md5" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "sort" + "strings" + "time" + + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/request" + "github.com/volcengine/volcengine-go-sdk/volcengine/volcenginebody" + "github.com/volcengine/volcengine-go-sdk/volcengine/volcengineerr" +) + +var tosSignRequestHandler = request.NamedHandler{Name: "TosSignRequestHandler", Fn: tosSign} +var tosBuildHandler = request.NamedHandler{Name: "TosBuildHandler", Fn: tosBuild} +var tosUnmarshalHandler = request.NamedHandler{Name: "TosUnmarshalHandler", Fn: tosUnmarshal} +var tosUnmarshalErrorHandler = request.NamedHandler{Name: "TosUnmarshalErrorHandler", Fn: tosUnmarshalError} + +func tosSign(req *request.Request) { + //region := req.ClientInfo.SigningRegion + + var ( + c Credentials + ) + + region := volcengine.StringValue(req.Config.Region) + + //name := req.ClientInfo.SigningName + //if name == "" { + // name = req.ClientInfo.ServiceID + //} + + value, _ := req.Config.Credentials.Get() + + c = Credentials{ + AccessKeyID: value.AccessKeyID, + SecretAccessKey: value.SecretAccessKey, + SessionToken: value.SessionToken, + Region: region, + Service: "tos", + } + r := sign(req.HTTPRequest, c) + req.HTTPRequest.Header = r.Header +} + +func tosBuild(r *request.Request) { + body := url.Values{} + + params := r.Params + if reflect.TypeOf(r.Params) == reflect.TypeOf(&map[string]interface{}{}) { + if v, ok := (*r.Params.(*map[string]interface{}))[TosInfoUrlParam]; ok { + for k1, v1 := range v.(map[string]string) { + body.Add(k1, v1) + } + } + if v, ok := (*r.Params.(*map[string]interface{}))[TosInfoInput]; ok { + params = v + } + } + + r.Params = params + + r.HTTPRequest.Host = r.HTTPRequest.URL.Host + if r.Config.ExtraUserAgent != nil && *r.Config.ExtraUserAgent != "" { + if strings.HasPrefix(*r.Config.ExtraUserAgent, "/") { + request.AddToUserAgent(r, *r.Config.ExtraUserAgent) + } else { + request.AddToUserAgent(r, "/"+*r.Config.ExtraUserAgent) + } + } + contentType := r.HTTPRequest.Header.Get("Content-Type") + if (strings.ToUpper(r.HTTPRequest.Method) == "PUT" || + strings.ToUpper(r.HTTPRequest.Method) == "POST" || + strings.ToUpper(r.HTTPRequest.Method) == "DELETE" || + strings.ToUpper(r.HTTPRequest.Method) == "PATCH") && + strings.Contains(strings.ToLower(contentType), "application/json") { + r.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8") + volcenginebody.BodyJson(&body, r) + } else { + if len(contentType) > 0 && !strings.Contains(strings.ToLower(contentType), "x-www-form-urlencoded") { + r.HTTPRequest.Header.Del("Content-Type") + } + volcenginebody.BodyParam(&body, r) + if len(contentType) > 0 { + r.HTTPRequest.Header.Set("Content-Type", contentType) + } + } +} + +type tosMetadata struct { + algorithm string + credentialScope string + signedHeaders string + date string + region string + service string +} + +type Credentials struct { + AccessKeyID string + SecretAccessKey string + Service string + Region string + SessionToken string +} + +type tosError struct { + Code string + RequestId string + HostId string + Message string +} + +func sign(request *http.Request, c Credentials) *http.Request { + query := request.URL.Query() + + request.URL.RawQuery = query.Encode() + return sign4(request, c) +} + +// Sign4 signs a request with Signed Signature Version 4. +func sign4(request *http.Request, credential Credentials) *http.Request { + keys := credential + + prepareRequestV4(request) + meta := new(tosMetadata) + meta.service, meta.region = keys.Service, keys.Region + + // Task 0 设置SessionToken的header + if credential.SessionToken != "" { + request.Header.Set("X-Tos-Security-Token", credential.SessionToken) + } + + // Task 1 + hashedCanonReq := hashedCanonicalRequestV4(request, meta) + + // Task 2 + stringToSign := stringToSignV4(request, hashedCanonReq, meta) + + // Task 3 + signingKey := signingKeyV4(keys.SecretAccessKey, meta.date, meta.region, meta.service) + signature := signatureV4(signingKey, stringToSign) + + request.Header.Set("Authorization", buildAuthHeaderV4(signature, meta, keys)) + + return request +} + +func hashedCanonicalRequestV4(request *http.Request, meta *tosMetadata) string { + payload := readAndReplaceBody(request) + payloadHash := hashSHA256(payload) + request.Header.Set("X-Tos-Content-Sha256", payloadHash) + + request.Header.Set("Host", request.Host) + + var sortedHeaderKeys []string + for key := range request.Header { + switch key { + case "Content-Type", "Content-Md5", "Host", "X-Tos-Security-Token": + default: + if !strings.HasPrefix(key, "X-") { + continue + } + } + sortedHeaderKeys = append(sortedHeaderKeys, strings.ToLower(key)) + } + sort.Strings(sortedHeaderKeys) + + var headersToSign string + for _, key := range sortedHeaderKeys { + value := strings.TrimSpace(request.Header.Get(key)) + if key == "host" { + if strings.Contains(value, ":") { + split := strings.Split(value, ":") + port := split[1] + if port == "80" || port == "443" { + value = split[0] + } + } + } + headersToSign += key + ":" + value + "\n" + } + meta.signedHeaders = concat(";", sortedHeaderKeys...) + canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(request.URL.Query()), headersToSign, meta.signedHeaders, payloadHash) + + return hashSHA256([]byte(canonicalRequest)) +} + +func stringToSignV4(request *http.Request, hashedCanonReq string, meta *tosMetadata) string { + requestTs := request.Header.Get("X-Tos-Date") + + meta.algorithm = "TOS4-HMAC-SHA256" + meta.date = tsDateV4(requestTs) + meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "request") + + return concat("\n", meta.algorithm, requestTs, meta.credentialScope, hashedCanonReq) +} + +func signatureV4(signingKey []byte, stringToSign string) string { + return hex.EncodeToString(hmacSHA256(signingKey, stringToSign)) +} + +func prepareRequestV4(request *http.Request) *http.Request { + necessaryDefaults := map[string]string{ + "X-Tos-Date": timestampV4(), + } + + for header, value := range necessaryDefaults { + if request.Header.Get(header) == "" { + request.Header.Set(header, value) + } + } + + if request.URL.Path == "" { + request.URL.Path += "/" + } + + return request +} + +func signingKeyV4(secretKey, date, region, service string) []byte { + kDate := hmacSHA256([]byte(secretKey), date) + kRegion := hmacSHA256(kDate, region) + kService := hmacSHA256(kRegion, service) + kSigning := hmacSHA256(kService, "request") + return kSigning +} + +func buildAuthHeaderV4(signature string, meta *tosMetadata, keys Credentials) string { + credential := keys.AccessKeyID + "/" + meta.credentialScope + + return meta.algorithm + + " Credential=" + credential + + ", SignedHeaders=" + meta.signedHeaders + + ", Signature=" + signature +} + +func timestampV4() string { + return now().Format("20060102T150405Z") +} + +func tsDateV4(timestamp string) string { + return timestamp[:8] +} + +func hmacSHA256(key []byte, content string) []byte { + mac := hmac.New(sha256.New, key) + mac.Write([]byte(content)) + return mac.Sum(nil) +} + +func hashSHA256(content []byte) string { + h := sha256.New() + h.Write(content) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func hashMD5(content []byte) string { + h := md5.New() + h.Write(content) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func readAndReplaceBody(request *http.Request) []byte { + if request.Body == nil { + return []byte{} + } + payload, _ := ioutil.ReadAll(request.Body) + request.Body = ioutil.NopCloser(bytes.NewReader(payload)) + return payload +} + +func concat(delim string, str ...string) string { + return strings.Join(str, delim) +} + +var now = func() time.Time { + return time.Now().UTC() +} + +func normuri(uri string) string { + parts := strings.Split(uri, "/") + for i := range parts { + parts[i] = encodePathFrag(parts[i]) + } + return strings.Join(parts, "/") +} + +func encodePathFrag(s string) string { + hexCount := 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c) { + hexCount++ + } + } + t := make([]byte, len(s)+2*hexCount) + j := 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c) { + t[j] = '%' + t[j+1] = "0123456789ABCDEF"[c>>4] + t[j+2] = "0123456789ABCDEF"[c&15] + j += 3 + } else { + t[j] = c + j++ + } + } + return string(t) +} + +func shouldEscape(c byte) bool { + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' { + return false + } + if '0' <= c && c <= '9' { + return false + } + if c == '-' || c == '_' || c == '.' || c == '~' { + return false + } + return true +} + +func normquery(v url.Values) string { + queryString := v.Encode() + + return strings.Replace(queryString, "+", "%20", -1) +} + +func tosUnmarshal(r *request.Request) { + defer r.HTTPResponse.Body.Close() + if r.DataFilled() { + body, err := ioutil.ReadAll(r.HTTPResponse.Body) + if err != nil { + fmt.Printf("read volcenginebody err, %v\n", err) + r.Error = err + return + } + + if reflect.TypeOf(r.Data) == reflect.TypeOf(&map[string]interface{}{}) { + (*r.Data.(*map[string]interface{}))[TosHeader] = r.HTTPResponse.Header + temp := make(map[string]interface{}) + if len(body) == 0 { + (*r.Data.(*map[string]interface{}))[TosResponse] = temp + return + } + + if strings.Contains(strings.ToLower(r.HTTPResponse.Header.Get("Accept")), "application/json") || + strings.Contains(strings.ToLower(r.HTTPResponse.Header.Get("Content-Type")), "application/json") { + if err = json.Unmarshal(body, &temp); err != nil { + fmt.Printf("Unmarshal err, %v\n", err) + r.Error = err + return + } + (*r.Data.(*map[string]interface{}))[TosResponse] = temp + } else { + (*r.Data.(*map[string]interface{}))[TosResponse] = temp + //(*r.Data.(*map[string]interface{}))[TosPlainResponse] = string(body) + } + + } + + } +} + +func tosUnmarshalError(r *request.Request) { + defer r.HTTPResponse.Body.Close() + if r.DataFilled() { + body, err := ioutil.ReadAll(r.HTTPResponse.Body) + if err != nil { + fmt.Printf("read volcenginebody err, %v\n", err) + r.Error = err + return + } + tos := tosError{} + if err = json.Unmarshal(body, &tos); err != nil { + fmt.Printf("Unmarshal err, %v\n", err) + r.Error = err + return + } + r.Error = volcengineerr.NewRequestFailure( + volcengineerr.New(tos.Code, tos.Message, nil), + r.HTTPResponse.StatusCode, + tos.RequestId, + ) + + return + } else { + r.Error = volcengineerr.NewRequestFailure( + volcengineerr.New("ServiceUnavailableException", "service is unavailable", nil), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + return + } +} diff --git a/common/common_volcengine_tos_utils.go b/common/common_volcengine_tos_utils.go new file mode 100644 index 00000000..8d3b75d7 --- /dev/null +++ b/common/common_volcengine_tos_utils.go @@ -0,0 +1,283 @@ +package common + +import ( + "bytes" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + TosPath = "PATH" + TosDomain = "DOMAIN" + TosHeader = "HEADER" + TosParam = "PARAM" + TosUrlParam = "URL_PARAM" + TosResponse = "RESPONSE" + TosFilePath = "FILE_PATH" +) + +func convertToTosParams(convert map[string]RequestConvert, condition map[string]interface{}) (result map[string]interface{}, err error) { + if len(condition) > 0 { + result = map[string]interface{}{ + TosDomain: "", + TosHeader: make(map[string]string), + TosPath: []string{}, + TosParam: condition, + TosUrlParam: make(map[string]string), + } + + if len(convert) > 0 { + + for k, v := range convert { + k1 := DownLineToHump(k) + if v.TargetField != "" { + k1 = v.TargetField + } + if v.SpecialParam != nil { + switch v.SpecialParam.Type { + case DomainParam: + if v1, ok := condition[k1]; ok { + if _, ok1 := v1.(string); !ok1 { + return result, fmt.Errorf("%s must a string type", k) + } + result[TosDomain] = v1 + delete(condition, k1) + } + case UrlParam: + if v1, ok := condition[k1]; ok { + if _, ok1 := v1.(string); !ok1 { + return result, fmt.Errorf("%s must a string type", k) + } + result[TosUrlParam].(map[string]string)[k1] = v1.(string) + delete(condition, k1) + } + case HeaderParam: + if v1, ok := condition[k1]; ok { + if _, ok1 := v1.(string); !ok1 { + return result, fmt.Errorf("%s must a string type", k) + } + if v1.(string) != "" { + result[TosHeader].(map[string]string)[k1] = v1.(string) + } + delete(condition, k1) + } + case PathParam: + if v1, ok := condition[k1]; ok { + if _, ok1 := v1.(string); !ok1 { + return result, fmt.Errorf("%s must a string type", k) + } + temp := result[TosPath].([]string) + temp = append(temp, strconv.Itoa(v.SpecialParam.Index)+":"+v1.(string)) + result[TosPath] = temp + delete(condition, k1) + } + case FilePathParam: + if v1, ok := condition[k1]; ok { + if _, ok1 := v1.(string); !ok1 { + return result, fmt.Errorf("%s must a string type", k) + } + result[TosFilePath] = v1 + delete(condition, k1) + } + } + } + } + //sort + if v, ok := result[TosPath]; ok { + temp := v.([]string) + sort.Strings(temp) + var afterSort []string + for _, v1 := range temp { + afterSort = append(afterSort, v1[strings.Index(v1, ":")+1:]) + } + result[TosPath] = afterSort + } + //query + result[TosParam] = condition + } + } + return result, err +} + +func mergeTosPublicAcl(acl string, param *map[string]interface{}, ownerId string) { + if _, ok := (*param)["Grants"]; !ok { + (*param)["Grants"] = []interface{}{} + } + vs := (*param)["Grants"].([]interface{}) + + defer func() { + (*param)["Grants"] = vs + }() + + switch acl { + case "private": + m := map[string]interface{}{ + "Grantee": map[string]interface{}{ + "Id": ownerId, + "Type": "CanonicalUser", + }, + "Permission": "FULL_CONTROL", + } + vs = append(vs, m) + return + case "public-read": + m := map[string]interface{}{ + "Grantee": map[string]interface{}{ + "Canned": "AllUsers", + "Type": "Group", + }, + "Permission": "READ", + } + vs = append(vs, m) + return + case "public-read-write": + m := map[string]interface{}{ + "Grantee": map[string]interface{}{ + "Canned": "AllUsers", + "Type": "Group", + }, + "Permission": "WRITE", + } + vs = append(vs, m) + return + case "authenticated-read": + m := map[string]interface{}{ + "Grantee": map[string]interface{}{ + "Canned": "AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "READ", + } + vs = append(vs, m) + return + case "bucket-owner-read": + m := map[string]interface{}{ + "Grantee": map[string]interface{}{ + "Id": ownerId, + "Type": "CanonicalUser", + }, + "Permission": "READ", + } + vs = append(vs, m) + return + } +} + +func BeforeTosPutAcl(d *schema.ResourceData, call SdkCall, data *map[string]interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + + sourceAclParam := SortAndStartTransJson((*call.SdkParam)[TosParam].(map[string]interface{})) + ownerId, _ := ObtainSdkValue("Owner.ID", (*data)[TosResponse]) + + grants, _ := ObtainSdkValue("Grants", sourceAclParam) + for _, grant := range grants.([]interface{}) { + id, _ := ObtainSdkValue("Grantee.ID", grant) + p, _ := ObtainSdkValue("Permission", grant) + if id == ownerId && p == "FULL_CONTROL" { + return false, fmt.Errorf("can not set FULL_CONTROL for owner") + } + } + + //merge owner + owner, _ := ObtainSdkValue("Owner", (*data)[TosResponse]) + sourceAclParam["Owner"] = owner + //merge public_acl + mergeTosPublicAcl(d.Get("public_acl").(string), &sourceAclParam, ownerId.(string)) + + (*call.SdkParam)[TosParam] = sourceAclParam + return true, nil +} + +func ConvertTosAccountAcl() FieldResponseConvert { + return func(i interface{}) interface{} { + var accountAcl []interface{} + owner, _ := ObtainSdkValue("Owner.ID", i) + grants, _ := ObtainSdkValue("Grants", i) + for _, grant := range grants.([]interface{}) { + permission, _ := ObtainSdkValue("Permission", grant) + id, _ := ObtainSdkValue("Grantee.ID", grant) + if id == nil { + continue + } + if id == owner && permission == "FULL_CONTROL" { + continue + } + g := map[string]interface{}{ + "AccountId": id, + "AclType": "CanonicalUser", + "Permission": permission, + } + accountAcl = append(accountAcl, g) + } + return accountAcl + } +} + +func ConvertTosPublicAcl() FieldResponseConvert { + return func(i interface{}) interface{} { + owner, _ := ObtainSdkValue("Owner.ID", i) + grants, _ := ObtainSdkValue("Grants", i) + var ( + read bool + write bool + ) + for _, grant := range grants.([]interface{}) { + id, _ := ObtainSdkValue("Grantee.ID", grant) + canned, _ := ObtainSdkValue("Grantee.Canned", grant) + t, _ := ObtainSdkValue("Grantee.Type", grant) + permission, _ := ObtainSdkValue("Permission", grant) + if canned != nil && canned.(string) == "AllUsers" && t.(string) == "Group" { + if permission.(string) == "READ" { + read = true + continue + } else if permission.(string) == "WRITE" { + write = true + continue + } + } + + if canned != nil && canned.(string) == "AuthenticatedUsers" && t.(string) == "Group" { + if permission.(string) == "READ" { + return "authenticated-read" + } + break + } + + if id != nil && id.(string) == owner.(string) && t.(string) == "CanonicalUser" { + if permission.(string) == "FULL_CONTROL" { + return "private" + } else if permission.(string) == "READ" { + return "bucket-owner-read" + } + break + + } + + } + if read && !write { + return "public-read" + } + if read && write { + return "public-read-write" + } + return "" + } +} + +func TosAccountAclHash(v interface{}) int { + if v == nil { + return hashcode.String("") + } + m := v.(map[string]interface{}) + buf := bytes.Buffer{} + buf.WriteString(fmt.Sprintf("%s-", m["account_id"])) + buf.WriteString(fmt.Sprintf("%s", m["permission"])) + return hashcode.String(buf.String()) +} diff --git a/common/common_volcengine_universal_client.go b/common/common_volcengine_universal_client.go index 81ff1ebf..06eae723 100644 --- a/common/common_volcengine_universal_client.go +++ b/common/common_volcengine_universal_client.go @@ -14,6 +14,7 @@ type HttpMethod int const ( GET HttpMethod = iota + HEAD POST PUT DELETE @@ -79,12 +80,14 @@ func (u *Universal) getMethod(m HttpMethod) string { return "PUT" case DELETE: return "DELETE" + case HEAD: + return "HEAD" default: return "GET" } } -func (u *Universal) getContentType(m ContentType) string { +func getContentType(m ContentType) string { switch m { case ApplicationJSON: return "application/json" @@ -106,7 +109,7 @@ func (u *Universal) DoCall(info UniversalInfo, input *map[string]interface{}) (o output = &map[string]interface{}{} req := c.NewRequest(op, input, output) - if u.getContentType(info.ContentType) == "application/json" { + if getContentType(info.ContentType) == "application/json" { req.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8") } err = req.Send() diff --git a/common/common_volcengine_version.go b/common/common_volcengine_version.go index 7d8d8730..be99564f 100644 --- a/common/common_volcengine_version.go +++ b/common/common_volcengine_version.go @@ -2,5 +2,5 @@ package common const ( TerraformProviderName = "terraform-provider-volcengine" - TerraformProviderVersion = "0.0.21" + TerraformProviderVersion = "0.0.22" ) diff --git a/docgen/main.go b/docgen/main.go index 78552430..98609f13 100644 --- a/docgen/main.go +++ b/docgen/main.go @@ -122,6 +122,7 @@ var resourceKeys = map[string]string{ "escloud": "ESCLOUD", "rds": "RDS_MYSQL", "rds_v2": "RDS_MYSQL", + "tos": "TOS(BETA)", } type Products struct { diff --git a/example/dataTosBuckets/main.tf b/example/dataTosBuckets/main.tf new file mode 100644 index 00000000..c3fcb09c --- /dev/null +++ b/example/dataTosBuckets/main.tf @@ -0,0 +1,3 @@ +data "volcengine_tos_buckets" "default" { + name_regex= "test" +} \ No newline at end of file diff --git a/example/dataTosObjects/main.tf b/example/dataTosObjects/main.tf new file mode 100644 index 00000000..e136ce71 --- /dev/null +++ b/example/dataTosObjects/main.tf @@ -0,0 +1,3 @@ +data "volcengine_tos_objects" "default" { + bucket_name= "test" +} \ No newline at end of file diff --git a/example/tosBucket/main.tf b/example/tosBucket/main.tf new file mode 100644 index 00000000..06b74e0f --- /dev/null +++ b/example/tosBucket/main.tf @@ -0,0 +1,14 @@ +resource "volcengine_tos_bucket" "default" { + bucket_name = "test-xym-1" +# storage_class ="IA" + public_acl = "private" + enable_version = true + account_acl { + account_id = "1" + permission = "READ" + } + account_acl { + account_id = "2001" + permission = "WRITE_ACP" + } +} \ No newline at end of file diff --git a/example/tosObject/main.tf b/example/tosObject/main.tf new file mode 100644 index 00000000..0015110b --- /dev/null +++ b/example/tosObject/main.tf @@ -0,0 +1,20 @@ +resource "volcengine_tos_object" "default" { + bucket_name = "test-xym-1" + object_name = "demo_xym" + file_path = "/Users/bytedance/Work/Go/build/test.txt" +# storage_class ="IA" + public_acl = "private" + encryption = "AES256" + #content_type = "text/plain" + account_acl { + account_id = "1" + permission = "READ" + } + account_acl { + account_id = "2001" + permission = "WRITE_ACP" + } +# lifecycle { +# ignore_changes = ["file_path"] +# } +} \ No newline at end of file diff --git a/volcengine/ebs/volume_attach/service_volcengine_volume_attach_test.go b/volcengine/ebs/volume_attach/service_volcengine_volume_attach_test.go index 97888e47..816f42aa 100644 --- a/volcengine/ebs/volume_attach/service_volcengine_volume_attach_test.go +++ b/volcengine/ebs/volume_attach/service_volcengine_volume_attach_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + ve "github.com/volcengine/terraform-provider-volcengine/common" ) func Test_ResourceNotFoundError(t *testing.T) { diff --git a/volcengine/provider.go b/volcengine/provider.go index 4cc3e4e0..8d645ec5 100644 --- a/volcengine/provider.go +++ b/volcengine/provider.go @@ -45,6 +45,8 @@ import ( "github.com/volcengine/terraform-provider-volcengine/volcengine/rds/rds_ip_list" "github.com/volcengine/terraform-provider-volcengine/volcengine/rds/rds_parameter_template" "github.com/volcengine/terraform-provider-volcengine/volcengine/rds_v2/rds_instance_v2" + "github.com/volcengine/terraform-provider-volcengine/volcengine/tos/bucket" + "github.com/volcengine/terraform-provider-volcengine/volcengine/tos/object" "github.com/volcengine/terraform-provider-volcengine/volcengine/vke/cluster" "github.com/volcengine/terraform-provider-volcengine/volcengine/vke/default_node_pool" "github.com/volcengine/terraform-provider-volcengine/volcengine/vke/node" @@ -170,6 +172,10 @@ func Provider() terraform.ResourceProvider { "volcengine_escloud_instances": instance.DataSourceVolcengineESCloudInstances(), "volcengine_escloud_regions": region.DataSourceVolcengineESCloudRegions(), "volcengine_escloud_zones": esZone.DataSourceVolcengineESCloudZones(), + + // ================ TOS ================ + "volcengine_tos_buckets": bucket.DataSourceVolcengineTosBuckets(), + "volcengine_tos_objects": object.DataSourceVolcengineTosObjects(), }, ResourcesMap: map[string]*schema.Resource{ "volcengine_vpc": vpc.ResourceVolcengineVpc(), @@ -240,6 +246,10 @@ func Provider() terraform.ResourceProvider { // ================ ESCloud ================ "volcengine_escloud_instance": instance.ResourceVolcengineESCloudInstance(), + + //================= TOS ================= + "volcengine_tos_bucket": bucket.ResourceVolcengineTosBucket(), + "volcengine_tos_object": object.ResourceVolcengineTosObject(), }, ConfigureFunc: ProviderConfigure, } diff --git a/volcengine/tos/bucket/data_source_volcengine_tos_buckets.go b/volcengine/tos/bucket/data_source_volcengine_tos_buckets.go new file mode 100644 index 00000000..7d7f81e9 --- /dev/null +++ b/volcengine/tos/bucket/data_source_volcengine_tos_buckets.go @@ -0,0 +1,77 @@ +package bucket + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +func DataSourceVolcengineTosBuckets() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVolcengineTosBucketsRead, + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Optional: true, + Description: "The name the TOS bucket.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of TOS bucket.", + }, + + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of TOS bucket query.", + }, + "buckets": { + Description: "The collection of TOS bucket query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name the TOS bucket.", + }, + "is_truncated": { + Type: schema.TypeBool, + Computed: true, + Description: "The truncated the TOS bucket.", + }, + "marker": { + Type: schema.TypeString, + Computed: true, + Description: "The marker the TOS bucket.", + }, + "max_keys": { + Type: schema.TypeInt, + Computed: true, + Description: "The max keys the TOS bucket.", + }, + "prefix": { + Type: schema.TypeString, + Computed: true, + Description: "The prefix the TOS bucket.", + }, + }, + }, + }, + }, + } +} + +func dataSourceVolcengineTosBucketsRead(d *schema.ResourceData, meta interface{}) error { + tosBucketService := NewTosBucketService(meta.(*ve.SdkClient)) + return tosBucketService.Dispatcher.Data(tosBucketService, d, DataSourceVolcengineTosBuckets()) +} diff --git a/volcengine/tos/bucket/resource_volcengine_tos_bucket.go b/volcengine/tos/bucket/resource_volcengine_tos_bucket.go new file mode 100644 index 00000000..aeefd918 --- /dev/null +++ b/volcengine/tos/bucket/resource_volcengine_tos_bucket.go @@ -0,0 +1,156 @@ +package bucket + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +/* + +Import +Tos Bucket can be imported using the id, e.g. +``` +$ terraform import volcengine_tos_bucket.default region:bucketName +``` + +*/ + +func ResourceVolcengineTosBucket() *schema.Resource { + resource := &schema.Resource{ + Create: resourceVolcengineTosBucketCreate, + Read: resourceVolcengineTosBucketRead, + Update: resourceVolcengineTosBucketUpdate, + Delete: resourceVolcengineTosBucketDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(2 * time.Minute), + Update: schema.DefaultTimeout(2 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), + }, + Importer: &schema.ResourceImporter{ + State: func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 1 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must be of the form bucketName") + } + _ = data.Set("bucket_name", items[0]) + return []*schema.ResourceData{data}, nil + }, + }, + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the bucket.", + }, + "public_acl": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "private", + "public-read", + "public-read-write", + "authenticated-read", + "bucket-owner-read", + }, false), + Default: "private", + Description: "The public acl control of object.Valid value is private|public-read|public-read-write|authenticated-read|bucket-owner-read.", + }, + "storage_class": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "STANDARD", + "IA", + }, false), + Default: "STANDARD", + Description: "The storage type of the object.Valid value is STANDARD|IA.", + }, + "enable_version": { + Type: schema.TypeBool, + Optional: true, + Description: "The flag of enable tos version.", + }, + + "account_acl": { + Type: schema.TypeSet, + Optional: true, + Description: "The user set of grant full control.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + Description: "The accountId to control.", + }, + "acl_type": { + Type: schema.TypeString, + Optional: true, + Default: "CanonicalUser", + ValidateFunc: validation.StringInSlice([]string{ + "CanonicalUser", + }, false), + Description: "The acl type to control.Valid value is CanonicalUser.", + }, + "permission": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "FULL_CONTROL", + "READ", + "READ_ACP", + "WRITE", + "WRITE_ACP", + }, false), + Description: "The permission to control.Valid value is FULL_CONTROL|READ|READ_ACP|WRITE|WRITE_ACP.", + }, + }, + }, + Set: ve.TosAccountAclHash, + }, + }, + } + return resource +} + +func resourceVolcengineTosBucketCreate(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosBucketService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Create(tosBucketService, d, ResourceVolcengineTosBucket()) + if err != nil { + return fmt.Errorf("error on creating tos bucket %q, %s", d.Id(), err) + } + return resourceVolcengineTosBucketRead(d, meta) +} + +func resourceVolcengineTosBucketRead(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosBucketService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Read(tosBucketService, d, ResourceVolcengineTosBucket()) + if err != nil { + return fmt.Errorf("error on reading tos bucket %q, %s", d.Id(), err) + } + return err +} + +func resourceVolcengineTosBucketUpdate(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosBucketService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Update(tosBucketService, d, ResourceVolcengineTosBucket()) + if err != nil { + return fmt.Errorf("error on updating tos bucket %q, %s", d.Id(), err) + } + return resourceVolcengineTosBucketRead(d, meta) +} + +func resourceVolcengineTosBucketDelete(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosBucketService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Delete(tosBucketService, d, ResourceVolcengineTosBucket()) + if err != nil { + return fmt.Errorf("error on deleting tos bucket %q, %s", d.Id(), err) + } + return err +} diff --git a/volcengine/tos/bucket/service_volcengine_tos_bucket.go b/volcengine/tos/bucket/service_volcengine_tos_bucket.go new file mode 100644 index 00000000..1bb023f1 --- /dev/null +++ b/volcengine/tos/bucket/service_volcengine_tos_bucket.go @@ -0,0 +1,523 @@ +package bucket + +import ( + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +type VolcengineTosBucketService struct { + Client *ve.SdkClient + Dispatcher *ve.Dispatcher +} + +func NewTosBucketService(c *ve.SdkClient) *VolcengineTosBucketService { + return &VolcengineTosBucketService{ + Client: c, + Dispatcher: &ve.Dispatcher{}, + } +} + +func (s *VolcengineTosBucketService) GetClient() *ve.SdkClient { + return s.Client +} + +func (s *VolcengineTosBucketService) ReadResources(condition map[string]interface{}) (data []interface{}, err error) { + tos := s.Client.TosClient + var ( + action string + resp *map[string]interface{} + results interface{} + ) + action = "ListBuckets" + logger.Debug(logger.ReqFormat, action, nil) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + }, nil) + if err != nil { + return data, err + } + results, err = ve.ObtainSdkValue(ve.TosResponse+".Buckets", *resp) + if err != nil { + return data, err + } + data = results.([]interface{}) + return data, err +} + +func (s *VolcengineTosBucketService) ReadResource(resourceData *schema.ResourceData, instanceId string) (data map[string]interface{}, err error) { + tos := s.Client.TosClient + var ( + action string + resp *map[string]interface{} + ok bool + header http.Header + acl map[string]interface{} + version map[string]interface{} + ) + + if instanceId == "" { + instanceId = s.ReadResourceId(resourceData.Id()) + } else { + instanceId = s.ReadResourceId(instanceId) + } + + action = "HeadBucket" + logger.Debug(logger.ReqFormat, action, instanceId) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.HEAD, + Domain: instanceId, + }, nil) + logger.Debug(logger.ReqFormat, action, *resp) + logger.Debug(logger.ReqFormat, action, err) + if err != nil { + return data, err + } + data = make(map[string]interface{}) + + if header, ok = (*resp)[ve.TosHeader].(http.Header); ok { + if header.Get("X-Tos-Storage-Class") != "" { + data["StorageClass"] = header.Get("X-Tos-Storage-Class") + } + } + + action = "GetBucketAcl" + req := map[string]interface{}{ + "acl": "", + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: instanceId, + }, &req) + if err != nil { + return data, err + } + if acl, ok = (*resp)[ve.TosResponse].(map[string]interface{}); ok { + data["PublicAcl"] = acl + data["AccountAcl"] = acl + } + + action = "GetBucketVersioning" + req = map[string]interface{}{ + "versioning": "", + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: instanceId, + }, &req) + if err != nil { + return data, err + } + if version, ok = (*resp)[ve.TosResponse].(map[string]interface{}); ok { + data["EnableVersion"] = version + } + + if len(data) == 0 { + return data, fmt.Errorf("bucket %s not exist ", instanceId) + } + return data, nil +} + +func (s *VolcengineTosBucketService) RefreshResourceState(data *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 60 * time.Second, + MinTimeout: 60 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + return data, "Success", err + }, + } +} + +func (s *VolcengineTosBucketService) getIdPermission(p string, grants []interface{}) []interface{} { + var result []interface{} + for _, grant := range grants { + permission, _ := ve.ObtainSdkValue("Permission", grant) + id, _ := ve.ObtainSdkValue("Grantee.ID", grant) + t, _ := ve.ObtainSdkValue("Grantee.Type", grant) + if id != nil && t.(string) == "CanonicalUser" && p == permission.(string) { + result = append(result, "Id="+id.(string)) + } + } + return result +} + +func (s *VolcengineTosBucketService) WithResourceResponseHandlers(m map[string]interface{}) []ve.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]ve.ResponseConvert, error) { + return m, map[string]ve.ResponseConvert{ + "EnableVersion": { + Convert: func(i interface{}) interface{} { + status, _ := ve.ObtainSdkValue("Status", i) + if status.(string) != "Enabled" { + return false + } + return true + }, + }, + "AccountAcl": { + Convert: ve.ConvertTosAccountAcl(), + }, + "PublicAcl": { + Convert: ve.ConvertTosPublicAcl(), + }, + }, nil + } + return []ve.ResourceResponseHandler{handler} +} + +func (s *VolcengineTosBucketService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + //create bucket + callback := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "CreateBucket", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + }, + "public_acl": { + ConvertType: ve.ConvertDefault, + TargetField: "x-tos-acl", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + }, + "storage_class": { + ConvertType: ve.ConvertDefault, + TargetField: "x-tos-storage-class", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //创建Bucket + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.PUT, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + Header: (*call.SdkParam)[ve.TosHeader].(map[string]string), + }, nil) + }, + AfterCall: func(d *schema.ResourceData, client *ve.SdkClient, resp *map[string]interface{}, call ve.SdkCall) error { + d.SetId((*call.SdkParam)[ve.TosDomain].(string)) + return nil + }, + }, + } + //version + callbackVersion := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutBucketVersioning", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + }, + "enable_version": { + ConvertType: ve.ConvertDefault, + TargetField: "Status", + Convert: func(data *schema.ResourceData, i interface{}) interface{} { + if i.(bool) { + return "Enabled" + } else { + return "" + } + }, + ForceGet: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + //if disable version,skip this call + if (*call.SdkParam)[ve.TosParam].(map[string]interface{})["Status"] == "" { + return false, nil + } + return true, nil + }, + ExecuteCall: s.executePutBucketVersioning(), + }, + } + //acl + callbackAcl := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutBucketAcl", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + }, + "account_acl": { + ConvertType: ve.ConvertListN, + TargetField: "Grants", + NextLevelConvert: map[string]ve.RequestConvert{ + "account_id": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.ID", + }, + "acl_type": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.Type", + }, + "permission": { + ConvertType: ve.ConvertDefault, + TargetField: "Permission", + }, + }, + }, + }, + BeforeCall: s.beforePutBucketAcl(), + ExecuteCall: s.executePutBucketAcl(), + //Refresh: &ve.StateRefresh{ + // Target: []string{"Success"}, + // Timeout: resourceData.Timeout(schema.TimeoutCreate), + //}, + }, + } + return []ve.Callback{callback, callbackVersion, callbackAcl} +} + +func (s *VolcengineTosBucketService) ModifyResource(data *schema.ResourceData, resource *schema.Resource) []ve.Callback { + var callbacks []ve.Callback + if data.HasChange("enable_version") { + //version + callbackVersion := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutBucketVersioning", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + ForceGet: true, + }, + "enable_version": { + ConvertType: ve.ConvertDefault, + TargetField: "Status", + Convert: func(data *schema.ResourceData, i interface{}) interface{} { + if i.(bool) { + return "Enabled" + } else { + return "Suspended" + } + }, + ForceGet: true, + }, + }, + ExecuteCall: s.executePutBucketVersioning(), + }, + } + callbacks = append(callbacks, callbackVersion) + } + var grant = []string{ + "public_acl", + "account_acl", + } + for _, v := range grant { + if data.HasChange(v) { + callbackAcl := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutBucketAcl", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + ForceGet: true, + }, + "account_acl": { + ConvertType: ve.ConvertListN, + TargetField: "Grants", + NextLevelConvert: map[string]ve.RequestConvert{ + "account_id": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.ID", + ForceGet: true, + }, + "acl_type": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.Type", + ForceGet: true, + }, + "permission": { + ConvertType: ve.ConvertDefault, + TargetField: "Permission", + ForceGet: true, + }, + }, + ForceGet: true, + }, + }, + BeforeCall: s.beforePutBucketAcl(), + ExecuteCall: s.executePutBucketAcl(), + Refresh: &ve.StateRefresh{ + Target: []string{"Success"}, + Timeout: data.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, callbackAcl) + break + } + } + + return callbacks +} + +func (s *VolcengineTosBucketService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "DeleteBucket", + ConvertMode: ve.RequestConvertIgnore, + SdkParam: &map[string]interface{}{ + "BucketName": s.ReadResourceId(resourceData.Id()), + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //删除Bucket + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.DELETE, + Domain: (*call.SdkParam)["BucketName"].(string), + }, nil) + }, + CallError: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall, baseErr error) error { + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if ve.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading tos on delete %q, %w", s.ReadResourceId(d.Id()), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineTosBucketService) DatasourceResources(data *schema.ResourceData, resource *schema.Resource) ve.DataSourceInfo { + + name, ok := data.GetOk("bucket_name") + return ve.DataSourceInfo{ + ServiceCategory: ve.ServiceTos, + RequestConverts: map[string]ve.RequestConvert{ + "bucket_name": { + Ignore: true, + }, + }, + NameField: "Name", + IdField: "BucketId", + CollectField: "buckets", + ResponseConverts: map[string]ve.ResponseConvert{}, + ExtraData: func(sourceData []interface{}) (extraData []interface{}, err error) { + for _, v := range sourceData { + if v.(map[string]interface{})["Location"].(string) != s.Client.Region { + continue + } + if ok { + if name.(string) == v.(map[string]interface{})["Name"].(string) { + v.(map[string]interface{})["BucketId"] = v.(map[string]interface{})["Name"].(string) + extraData = append(extraData, v) + break + } else { + continue + } + } else { + v.(map[string]interface{})["BucketId"] = v.(map[string]interface{})["Name"].(string) + extraData = append(extraData, v) + } + + } + return extraData, err + }, + } +} + +func (s *VolcengineTosBucketService) ReadResourceId(id string) string { + return id +} + +func (s *VolcengineTosBucketService) beforePutBucketAcl() ve.BeforeCallFunc { + + return func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + data, err := s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + UrlParam: map[string]string{ + "acl": "", + }, + }, nil) + return ve.BeforeTosPutAcl(d, call, data, err) + } +} + +func (s *VolcengineTosBucketService) executePutBucketAcl() ve.ExecuteCallFunc { + return func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //PutAcl + param := (*call.SdkParam)[ve.TosParam].(map[string]interface{}) + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.PUT, + ContentType: ve.ApplicationJSON, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + Header: (*call.SdkParam)[ve.TosHeader].(map[string]string), + UrlParam: map[string]string{ + "acl": "", + }, + }, ¶m) + } +} + +func (s *VolcengineTosBucketService) executePutBucketVersioning() ve.ExecuteCallFunc { + return func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //PutVersion + condition := (*call.SdkParam)[ve.TosParam].(map[string]interface{}) + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + ContentType: ve.ApplicationJSON, + HttpMethod: ve.PUT, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + UrlParam: map[string]string{ + "versioning": "", + }, + }, &condition) + } +} diff --git a/volcengine/tos/object/data_source_volcengine_tos_objects.go b/volcengine/tos/object/data_source_volcengine_tos_objects.go new file mode 100644 index 00000000..a3793127 --- /dev/null +++ b/volcengine/tos/object/data_source_volcengine_tos_objects.go @@ -0,0 +1,72 @@ +package object + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +func DataSourceVolcengineTosObjects() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVolcengineTosObjectRead, + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Required: true, + Description: "The name the TOS bucket.", + }, + "object_name": { + Type: schema.TypeString, + Optional: true, + Description: "The name the TOS Object.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of TOS Object.", + }, + + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of TOS Object query.", + }, + "objects": { + Description: "The collection of TOS Object query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name the TOS Object.", + }, + "size": { + Type: schema.TypeInt, + Computed: true, + Description: "The name the TOS Object size.", + }, + "storage_class": { + Type: schema.TypeString, + Computed: true, + Description: "The name the TOS Object storage class.", + }, + }, + }, + }, + }, + } +} + +func dataSourceVolcengineTosObjectRead(d *schema.ResourceData, meta interface{}) error { + tosBucketService := NewTosObjectService(meta.(*ve.SdkClient)) + return tosBucketService.Dispatcher.Data(tosBucketService, d, DataSourceVolcengineTosObjects()) +} diff --git a/volcengine/tos/object/resource_volcengine_tos_object.go b/volcengine/tos/object/resource_volcengine_tos_object.go new file mode 100644 index 00000000..f2f24979 --- /dev/null +++ b/volcengine/tos/object/resource_volcengine_tos_object.go @@ -0,0 +1,199 @@ +package object + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +/* + +Import +TOS Object can be imported using the id, e.g. +``` +$ terraform import volcengine_tos_object.default bucketName:objectName +``` + +*/ + +func ResourceVolcengineTosObject() *schema.Resource { + resource := &schema.Resource{ + Create: resourceVolcengineTosObjectCreate, + Read: resourceVolcengineTosObjectRead, + Update: resourceVolcengineTosObjectUpdate, + Delete: resourceVolcengineTosObjectDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(2 * time.Minute), + Update: schema.DefaultTimeout(2 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), + }, + Importer: &schema.ResourceImporter{ + State: func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 2 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must be of the form bucketName:objectName") + } + _ = data.Set("bucket_name", items[0]) + _ = data.Set("object_name", items[1]) + return []*schema.ResourceData{data}, nil + }, + }, + CustomizeDiff: func(diff *schema.ResourceDiff, i interface{}) (err error) { + if diff.Id() != "" && diff.HasChange("file_path") && !diff.Get("enable_version").(bool) { + return diff.ForceNew("file_path") + } + return err + }, + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the bucket.", + }, + "object_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the object.", + }, + "file_path": { + Type: schema.TypeString, + Required: true, + //ForceNew: true, + Description: "The file path for upload.", + }, + "encryption": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "AES256", + }, false), + Description: "The encryption of the object.Valid value is AES256.", + }, + "content_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + Description: "The content type of the object.", + }, + "public_acl": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "private", + "public-read", + "public-read-write", + "authenticated-read", + "bucket-owner-read", + }, false), + Default: "private", + Description: "The public acl control of object.Valid value is private|public-read|public-read-write|authenticated-read|bucket-owner-read.", + }, + "storage_class": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "STANDARD", + "IA", + }, false), + Default: "STANDARD", + Description: "The storage type of the object.Valid value is STANDARD|IA.", + }, + "version_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + Description: "The version ids of the object if exist.", + }, + "account_acl": { + Type: schema.TypeSet, + Optional: true, + Description: "The user set of grant full control.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + Description: "The accountId to control.", + }, + "acl_type": { + Type: schema.TypeString, + Optional: true, + Default: "CanonicalUser", + ValidateFunc: validation.StringInSlice([]string{ + "CanonicalUser", + }, false), + Description: "The acl type to control.Valid value is CanonicalUser.", + }, + "permission": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "FULL_CONTROL", + "READ", + "READ_ACP", + "WRITE", + "WRITE_ACP", + }, false), + Description: "The permission to control.Valid value is FULL_CONTROL|READ|READ_ACP|WRITE|WRITE_ACP.", + }, + }, + }, + Set: ve.TosAccountAclHash, + }, + "enable_version": { + Type: schema.TypeBool, + Computed: true, + Description: "The flag of enable tos version.", + }, + }, + } + return resource +} + +func resourceVolcengineTosObjectCreate(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosObjectService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Create(tosBucketService, d, ResourceVolcengineTosObject()) + if err != nil { + return fmt.Errorf("error on creating tos bucket %q, %s", d.Id(), err) + } + return resourceVolcengineTosObjectRead(d, meta) +} + +func resourceVolcengineTosObjectRead(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosObjectService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Read(tosBucketService, d, ResourceVolcengineTosObject()) + if err != nil { + return fmt.Errorf("error on reading tos bucket %q, %s", d.Id(), err) + } + return err +} + +func resourceVolcengineTosObjectUpdate(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosObjectService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Update(tosBucketService, d, ResourceVolcengineTosObject()) + if err != nil { + return fmt.Errorf("error on updating tos bucket %q, %s", d.Id(), err) + } + return resourceVolcengineTosObjectRead(d, meta) +} + +func resourceVolcengineTosObjectDelete(d *schema.ResourceData, meta interface{}) (err error) { + tosBucketService := NewTosObjectService(meta.(*ve.SdkClient)) + err = tosBucketService.Dispatcher.Delete(tosBucketService, d, ResourceVolcengineTosObject()) + if err != nil { + return fmt.Errorf("error on deleting tos bucket %q, %s", d.Id(), err) + } + return err +} diff --git a/volcengine/tos/object/service_volcengine_tos_object.go b/volcengine/tos/object/service_volcengine_tos_object.go new file mode 100644 index 00000000..8e194e56 --- /dev/null +++ b/volcengine/tos/object/service_volcengine_tos_object.go @@ -0,0 +1,542 @@ +package object + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +type VolcengineTosObjectService struct { + Client *ve.SdkClient + Dispatcher *ve.Dispatcher +} + +func NewTosObjectService(c *ve.SdkClient) *VolcengineTosObjectService { + return &VolcengineTosObjectService{ + Client: c, + Dispatcher: &ve.Dispatcher{}, + } +} + +func (s *VolcengineTosObjectService) GetClient() *ve.SdkClient { + return s.Client +} + +func (s *VolcengineTosObjectService) ReadResources(condition map[string]interface{}) (data []interface{}, err error) { + tos := s.Client.TosClient + var ( + action string + resp *map[string]interface{} + results interface{} + ) + action = "ListObjects" + logger.Debug(logger.ReqFormat, action, nil) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: condition[ve.TosDomain].(string), + }, nil) + if err != nil { + return data, err + } + results, err = ve.ObtainSdkValue(ve.TosResponse+".Contents", *resp) + if err != nil { + return data, err + } + data = results.([]interface{}) + return data, err +} + +func (s *VolcengineTosObjectService) ReadResource(resourceData *schema.ResourceData, instanceId string) (data map[string]interface{}, err error) { + tos := s.Client.TosClient + bucketName := resourceData.Get("bucket_name").(string) + var ( + action string + resp *map[string]interface{} + ok bool + header http.Header + acl map[string]interface{} + bucketVersion map[string]interface{} + ) + + if instanceId == "" { + instanceId = s.ReadResourceId(resourceData.Id()) + } else { + instanceId = s.ReadResourceId(instanceId) + } + + action = "HeadObject" + logger.Debug(logger.ReqFormat, action, bucketName+":"+instanceId) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.HEAD, + Domain: bucketName, + Path: []string{instanceId}, + }, nil) + if err != nil { + return data, err + } + data = make(map[string]interface{}) + + if header, ok = (*resp)[ve.TosHeader].(http.Header); ok { + if header.Get("X-Tos-Storage-Class") != "" { + data["StorageClass"] = header.Get("x-tos-storage-class") + } + if header.Get("Content-Type") != "" { + data["ContentType"] = header.Get("Content-Type") + } + if header.Get("X-Tos-Server-Side-Encryption") != "" { + data["Encryption"] = header.Get("X-Tos-Server-Side-Encryption") + } + + if header.Get("X-Tos-Version-Id") != "" { + action = "ListObjects" + logger.Debug(logger.ReqFormat, action, bucketName+":"+instanceId) + + var ( + nextVersionIdMarker string + versionIds []string + ) + + for { + urlParam := map[string]string{ + "prefix": instanceId, + "max-keys": "100", + "versions": "", + } + if nextVersionIdMarker != "" { + urlParam["key-marker"] = instanceId + urlParam["version-id-marker"] = nextVersionIdMarker + } + + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: bucketName, + UrlParam: urlParam, + }, nil) + + if err != nil { + return data, err + } + versions, _ := ve.ObtainSdkValue(ve.TosResponse+".Versions", *resp) + next, _ := ve.ObtainSdkValue(ve.TosResponse+".NextVersionIdMarker", *resp) + + if versions == nil || len(versions.([]interface{})) == 0 { + break + } + + if next == nil || next.(string) == "" { + nextVersionIdMarker = "" + } else { + nextVersionIdMarker = next.(string) + } + + for _, version := range versions.([]interface{}) { + versionId, _ := ve.ObtainSdkValue("VersionId", version) + versionIds = append(versionIds, versionId.(string)) + } + + if nextVersionIdMarker == "" { + break + } + } + logger.Debug(logger.ReqFormat, action, versionIds) + data["VersionIds"] = versionIds + } + } + + action = "GetObjectAcl" + req := map[string]interface{}{ + "acl": "", + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: bucketName, + Path: []string{instanceId}, + }, &req) + if err != nil { + return data, err + } + if acl, ok = (*resp)[ve.TosResponse].(map[string]interface{}); ok { + data["PublicAcl"] = acl + data["AccountAcl"] = acl + } + + action = "GetBucketVersioning" + req = map[string]interface{}{ + "versioning": "", + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = tos.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: bucketName, + }, &req) + if err != nil { + return data, err + } + if bucketVersion, ok = (*resp)[ve.TosResponse].(map[string]interface{}); ok { + data["EnableVersion"] = bucketVersion + } + + if len(data) == 0 { + return data, fmt.Errorf("object %s not exist ", instanceId) + } + return data, nil +} + +func (s *VolcengineTosObjectService) RefreshResourceState(data *schema.ResourceData, target []string, timeout time.Duration, instanceId string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 60 * time.Second, + MinTimeout: 60 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + return data, "Success", err + }, + } +} + +func (VolcengineTosObjectService) WithResourceResponseHandlers(m map[string]interface{}) []ve.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]ve.ResponseConvert, error) { + return m, map[string]ve.ResponseConvert{ + "EnableVersion": { + Convert: func(i interface{}) interface{} { + status, _ := ve.ObtainSdkValue("Status", i) + return status.(string) == "Enabled" + }, + }, + "AccountAcl": { + Convert: ve.ConvertTosAccountAcl(), + }, + "PublicAcl": { + Convert: ve.ConvertTosPublicAcl(), + }, + }, nil + } + return []ve.ResourceResponseHandler{handler} +} + +func (s *VolcengineTosObjectService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + //create object + callback := s.createOrReplaceObject(resourceData, resource, false) + //acl + callbackAcl := s.createOrUpdateObjectAcl(resourceData, resource, false) + return []ve.Callback{callback, callbackAcl} +} + +func (s *VolcengineTosObjectService) ModifyResource(data *schema.ResourceData, resource *schema.Resource) []ve.Callback { + var callbacks []ve.Callback + + if data.HasChange("file_path") { + callbacks = append(callbacks, s.createOrReplaceObject(data, resource, true)) + callbacks = append(callbacks, s.createOrUpdateObjectAcl(data, resource, true)) + } else { + var grant = []string{ + "public_acl", + "account_acl", + } + for _, v := range grant { + if data.HasChange(v) { + callbackAcl := s.createOrUpdateObjectAcl(data, resource, true) + callbacks = append(callbacks, callbackAcl) + break + } + } + } + + return callbacks +} + +func (s *VolcengineTosObjectService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "DeleteObject", + ConvertMode: ve.RequestConvertIgnore, + SdkParam: &map[string]interface{}{ + "BucketName": resourceData.Get("bucket_name"), + "ObjectName": s.ReadResourceId(resourceData.Id()), + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + + if d.Get("version_ids") != nil && len(d.Get("version_ids").(*schema.Set).List()) > 0 { + for _, vv := range d.Get("version_ids").(*schema.Set).List() { + condition := make(map[string]interface{}) + condition["versionId"] = vv + //remove Object-with-version + logger.Debug(logger.RespFormat, call.Action, condition) + _, err := s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.DELETE, + Domain: (*call.SdkParam)["BucketName"].(string), + Path: []string{(*call.SdkParam)["ObjectName"].(string)}, + }, &condition) + if err != nil { + return nil, err + } + } + } else { + //remove Object-no-version + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.DELETE, + Domain: (*call.SdkParam)["BucketName"].(string), + Path: []string{(*call.SdkParam)["ObjectName"].(string)}, + }, nil) + } + + return nil, nil + }, + CallError: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall, baseErr error) error { + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if ve.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading tos object on delete %q, %w", s.ReadResourceId(d.Id()), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineTosObjectService) DatasourceResources(data *schema.ResourceData, resource *schema.Resource) ve.DataSourceInfo { + name, ok := data.GetOk("object_name") + bucketName, _ := data.GetOk("bucket_name") + return ve.DataSourceInfo{ + ServiceCategory: ve.ServiceTos, + RequestConverts: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + }, + "object_name": { + Ignore: true, + }, + }, + NameField: "Key", + IdField: "ObjectId", + CollectField: "objects", + ResponseConverts: map[string]ve.ResponseConvert{ + "Key": { + TargetField: "name", + }, + }, + ExtraData: func(sourceData []interface{}) (extraData []interface{}, err error) { + for _, v := range sourceData { + if ok { + if name.(string) == v.(map[string]interface{})["Key"].(string) { + v.(map[string]interface{})["ObjectId"] = bucketName.(string) + ":" + v.(map[string]interface{})["Key"].(string) + extraData = append(extraData, v) + break + } else { + continue + } + } else { + v.(map[string]interface{})["ObjectId"] = bucketName.(string) + ":" + v.(map[string]interface{})["Key"].(string) + extraData = append(extraData, v) + } + + } + return extraData, err + }, + } +} + +func (s *VolcengineTosObjectService) ReadResourceId(id string) string { + return id[strings.Index(id, ":")+1:] +} + +func (s *VolcengineTosObjectService) beforePutObjectAcl() ve.BeforeCallFunc { + return func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + data, err := s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.GET, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + Path: (*call.SdkParam)[ve.TosPath].([]string), + UrlParam: map[string]string{ + "acl": "", + }, + }, nil) + return ve.BeforeTosPutAcl(d, call, data, err) + } +} + +func (s *VolcengineTosObjectService) executePutObjectAcl() ve.ExecuteCallFunc { + return func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //PutAcl + param := (*call.SdkParam)[ve.TosParam].(map[string]interface{}) + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.PUT, + ContentType: ve.ApplicationJSON, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + Path: (*call.SdkParam)[ve.TosPath].([]string), + UrlParam: map[string]string{ + "acl": "", + }, + }, ¶m) + } +} + +func (s *VolcengineTosObjectService) createOrUpdateObjectAcl(resourceData *schema.ResourceData, resource *schema.Resource, isUpdate bool) ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutObjectAcl", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + ForceGet: isUpdate, + }, + "object_name": { + ConvertType: ve.ConvertDefault, + TargetField: "ObjectName", + SpecialParam: &ve.SpecialParam{ + Type: ve.PathParam, + Index: 0, + }, + ForceGet: isUpdate, + }, + "account_acl": { + ConvertType: ve.ConvertListN, + TargetField: "Grants", + ForceGet: isUpdate, + NextLevelConvert: map[string]ve.RequestConvert{ + "account_id": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.ID", + ForceGet: isUpdate, + }, + "acl_type": { + ConvertType: ve.ConvertDefault, + TargetField: "Grantee.Type", + ForceGet: isUpdate, + }, + "permission": { + ConvertType: ve.ConvertDefault, + TargetField: "Permission", + ForceGet: isUpdate, + }, + }, + }, + }, + BeforeCall: s.beforePutObjectAcl(), + ExecuteCall: s.executePutObjectAcl(), + //Refresh: &ve.StateRefresh{ + // Target: []string{"Success"}, + // Timeout: resourceData.Timeout(schema.TimeoutCreate), + //}, + }, + } + if isUpdate { + callback.Call.Refresh = &ve.StateRefresh{ + Target: []string{"Success"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + } + } + return callback +} + +func (s *VolcengineTosObjectService) createOrReplaceObject(resourceData *schema.ResourceData, resource *schema.Resource, isUpdate bool) ve.Callback { + return ve.Callback{ + Call: ve.SdkCall{ + ServiceCategory: ve.ServiceTos, + Action: "PutObject", + ConvertMode: ve.RequestConvertInConvert, + Convert: map[string]ve.RequestConvert{ + "bucket_name": { + ConvertType: ve.ConvertDefault, + TargetField: "BucketName", + SpecialParam: &ve.SpecialParam{ + Type: ve.DomainParam, + }, + ForceGet: isUpdate, + }, + "object_name": { + ConvertType: ve.ConvertDefault, + TargetField: "ObjectName", + SpecialParam: &ve.SpecialParam{ + Type: ve.PathParam, + Index: 0, + }, + ForceGet: isUpdate, + }, + "public_acl": { + ConvertType: ve.ConvertDefault, + TargetField: "x-tos-acl", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + ForceGet: isUpdate, + }, + "storage_class": { + ConvertType: ve.ConvertDefault, + TargetField: "x-tos-storage-class", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + ForceGet: isUpdate, + }, + "content_type": { + ConvertType: ve.ConvertDefault, + TargetField: "Content-Type", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + ForceGet: isUpdate, + }, + "file_path": { + ConvertType: ve.ConvertDefault, + TargetField: "file-path", + SpecialParam: &ve.SpecialParam{ + Type: ve.FilePathParam, + }, + }, + "encryption": { + ConvertType: ve.ConvertDefault, + TargetField: "x-tos-server-side-encryption", + SpecialParam: &ve.SpecialParam{ + Type: ve.HeaderParam, + }, + ForceGet: isUpdate, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + //创建Object + return s.Client.TosClient.DoTosCall(ve.TosInfo{ + HttpMethod: ve.PUT, + Domain: (*call.SdkParam)[ve.TosDomain].(string), + Header: (*call.SdkParam)[ve.TosHeader].(map[string]string), + Path: (*call.SdkParam)[ve.TosPath].([]string), + ContentPath: (*call.SdkParam)[ve.TosFilePath].(string), + }, nil) + }, + AfterCall: func(d *schema.ResourceData, client *ve.SdkClient, resp *map[string]interface{}, call ve.SdkCall) error { + d.SetId((*call.SdkParam)[ve.TosDomain].(string) + ":" + (*call.SdkParam)[ve.TosPath].([]string)[0]) + return nil + }, + }, + } +} diff --git a/volcengine/vke/node_pool/data_source_volcengine_vke_node_pools.go b/volcengine/vke/node_pool/data_source_volcengine_vke_node_pools.go index fae32e1d..a5c9d3fb 100644 --- a/volcengine/vke/node_pool/data_source_volcengine_vke_node_pools.go +++ b/volcengine/vke/node_pool/data_source_volcengine_vke_node_pools.go @@ -332,6 +332,11 @@ func DataSourceVolcengineNodePools() *schema.Resource { Computed: true, Description: "The Size of DataVolume.", }, + "mount_point": { + Type: schema.TypeString, + Computed: true, + Description: "The target mount directory of the disk.", + }, }, }, Description: "The DataVolume of NodeConfig.", diff --git a/volcengine/vke/node_pool/resource_volcengine_vke_node_pool.go b/volcengine/vke/node_pool/resource_volcengine_vke_node_pool.go index 2edc6cb0..0b3dbca0 100644 --- a/volcengine/vke/node_pool/resource_volcengine_vke_node_pool.go +++ b/volcengine/vke/node_pool/resource_volcengine_vke_node_pool.go @@ -170,8 +170,8 @@ func ResourceVolcengineNodePool() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"PTSSD", "ESSD_PL0"}, false), - Description: "The Type of SystemVolume, the value can be `PTSSD` or `ESSD_PL0`.", + ValidateFunc: validation.StringInSlice([]string{"PTSSD", "ESSD_PL0", "ESSD_FlexPL"}, false), + Description: "The Type of SystemVolume, the value can be `PTSSD` or `ESSD_PL0` or `ESSD_FlexPL`.", }, "size": { Type: schema.TypeInt, @@ -194,8 +194,8 @@ func ResourceVolcengineNodePool() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"PTSSD", "ESSD_PL0"}, false), - Description: "The Type of DataVolumes, the value can be `PTSSD` or `ESSD_PL0`.", + ValidateFunc: validation.StringInSlice([]string{"PTSSD", "ESSD_PL0", "ESSD_FlexPL"}, false), + Description: "The Type of DataVolumes, the value can be `PTSSD` or `ESSD_PL0` or `ESSD_FlexPL`.", }, "size": { Type: schema.TypeInt, @@ -204,6 +204,12 @@ func ResourceVolcengineNodePool() *schema.Resource { ValidateFunc: validation.IntBetween(20, 32768), Description: "The Size of DataVolumes, the value range in 20~32768.", }, + "mount_point": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The target mount directory of the disk. Must start with `/`.", + }, }, }, Description: "The DataVolumes of NodeConfig.", @@ -216,6 +222,7 @@ func ResourceVolcengineNodePool() *schema.Resource { "additional_container_storage_enabled": { Type: schema.TypeBool, Optional: true, + ForceNew: true, Description: "The AdditionalContainerStorageEnabled of NodeConfig.", }, "image_id": { diff --git a/volcengine/vke/node_pool/service_volcengine_vke_node_pool.go b/volcengine/vke/node_pool/service_volcengine_vke_node_pool.go index 972a9d57..0349ffea 100644 --- a/volcengine/vke/node_pool/service_volcengine_vke_node_pool.go +++ b/volcengine/vke/node_pool/service_volcengine_vke_node_pool.go @@ -306,15 +306,7 @@ func (s *VolcengineNodePoolService) CreateResource(resourceData *schema.Resource }, }, "data_volumes": { - ConvertType: ve.ConvertJsonArray, - NextLevelConvert: map[string]ve.RequestConvert{ - "type": { - ConvertType: ve.ConvertJsonObject, - }, - "size": { - ConvertType: ve.ConvertJsonObject, - }, - }, + ConvertType: ve.ConvertJsonObjectArray, }, "initialize_script": { ConvertType: ve.ConvertJsonObject, @@ -344,29 +336,10 @@ func (s *VolcengineNodePoolService) CreateResource(resourceData *schema.Resource ConvertType: ve.ConvertJsonObject, NextLevelConvert: map[string]ve.RequestConvert{ "labels": { - ConvertType: ve.ConvertJsonArray, - NextLevelConvert: map[string]ve.RequestConvert{ - "key": { - ConvertType: ve.ConvertJsonObject, - }, - "value": { - ConvertType: ve.ConvertJsonObject, - }, - }, + ConvertType: ve.ConvertJsonObjectArray, }, "taints": { - ConvertType: ve.ConvertJsonArray, - NextLevelConvert: map[string]ve.RequestConvert{ - "key": { - ConvertType: ve.ConvertJsonObject, - }, - "value": { - ConvertType: ve.ConvertJsonObject, - }, - "effect": { - ConvertType: ve.ConvertJsonObject, - }, - }, + ConvertType: ve.ConvertJsonObjectArray, }, "cordon": { ConvertType: ve.ConvertJsonObject, @@ -469,29 +442,10 @@ func (s *VolcengineNodePoolService) ModifyResource(resourceData *schema.Resource ConvertType: ve.ConvertJsonObject, NextLevelConvert: map[string]ve.RequestConvert{ "labels": { - ConvertType: ve.ConvertJsonArray, - NextLevelConvert: map[string]ve.RequestConvert{ - "key": { - ConvertType: ve.ConvertJsonObject, - }, - "value": { - ConvertType: ve.ConvertJsonObject, - }, - }, + ConvertType: ve.ConvertJsonObjectArray, }, "taints": { - ConvertType: ve.ConvertJsonArray, - NextLevelConvert: map[string]ve.RequestConvert{ - "key": { - ConvertType: ve.ConvertJsonObject, - }, - "value": { - ConvertType: ve.ConvertJsonObject, - }, - "effect": { - ConvertType: ve.ConvertJsonObject, - }, - }, + ConvertType: ve.ConvertJsonObjectArray, }, "cordon": { ConvertType: ve.ConvertJsonObject, @@ -541,7 +495,64 @@ func (s *VolcengineNodePoolService) ModifyResource(resourceData *schema.Resource } } } - logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + + if d.HasChange("kubernetes_config") { + if _, ok := (*call.SdkParam)["KubernetesConfig"]; !ok { // 列表被删除 + (*call.SdkParam)["KubernetesConfig"] = map[string]interface{}{ + "Labels": []interface{}{}, + "Taints": []interface{}{}, + } + } + } + + labelsChange := false + taintsChange := false + if d.HasChange("kubernetes_config.0.taints") { + taintsChange = true + } + if d.HasChange("kubernetes_config.0.labels") { + labelsChange = true + } + if labelsChange || taintsChange { + if _, ok := (*call.SdkParam)["KubernetesConfig"]; !ok { + (*call.SdkParam)["KubernetesConfig"] = map[string]interface{}{} + } + } + if labelsChange { + if _, ok := (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Labels"]; !ok { // 被清空了 + (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Labels"] = []interface{}{} + } + } + if taintsChange { + if _, ok := (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Taints"]; !ok { + (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Taints"] = []interface{}{} + } + } + + if labelsChange && (!taintsChange) { + var taints []interface{} + for _, taint := range d.Get("kubernetes_config.0.taints").([]interface{}) { + t := taint.(map[string]interface{}) + taints = append(taints, map[string]interface{}{ + "Key": t["key"], + "Value": t["value"], + "Effect": t["effect"], + }) + } + (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Taints"] = taints + } else if taintsChange && (!labelsChange) { + var labels []interface{} + for _, label := range d.Get("kubernetes_config.0.labels").(*schema.Set).List() { // for set + t := label.(map[string]interface{}) + labels = append(labels, map[string]interface{}{ + "Key": t["key"], + "Value": t["value"], + }) + } + (*call.SdkParam)["KubernetesConfig"].(map[string]interface{})["Labels"] = labels + } + + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) logger.Debug(logger.RespFormat, call.Action, resp, err) return resp, err @@ -732,6 +743,9 @@ func (s *VolcengineNodePoolService) DatasourceResources(*schema.ResourceData, *s volume := make(map[string]interface{}, 0) volume["size"] = strconv.FormatFloat(_data.(map[string]interface{})["Size"].(float64), 'g', 5, 32) volume["type"] = _data.(map[string]interface{})["Type"].(string) + if p, ok := _data.(map[string]interface{})["MountPoint"]; ok { // 可能不存在 + volume["mount_point"] = p.(string) + } results = append(results, volume) } } diff --git a/website/docs/d/tos_buckets.html.markdown b/website/docs/d/tos_buckets.html.markdown new file mode 100644 index 00000000..cd52f76d --- /dev/null +++ b/website/docs/d/tos_buckets.html.markdown @@ -0,0 +1,33 @@ +--- +subcategory: "TOS(BETA)" +layout: "volcengine" +page_title: "Volcengine: volcengine_tos_buckets" +sidebar_current: "docs-volcengine-datasource-tos_buckets" +description: |- + Use this data source to query detailed information of tos buckets +--- +# volcengine_tos_buckets +Use this data source to query detailed information of tos buckets +## Example Usage +```hcl +data "volcengine_tos_buckets" "default" { + name_regex = "test" +} +``` +## Argument Reference +The following arguments are supported: +* `bucket_name` - (Optional) The name the TOS bucket. +* `name_regex` - (Optional) A Name Regex of TOS bucket. +* `output_file` - (Optional) File name where to save data source results. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `buckets` - The collection of TOS bucket query. + * `is_truncated` - The truncated the TOS bucket. + * `marker` - The marker the TOS bucket. + * `max_keys` - The max keys the TOS bucket. + * `name` - The name the TOS bucket. + * `prefix` - The prefix the TOS bucket. +* `total_count` - The total count of TOS bucket query. + + diff --git a/website/docs/d/tos_objects.html.markdown b/website/docs/d/tos_objects.html.markdown new file mode 100644 index 00000000..83617772 --- /dev/null +++ b/website/docs/d/tos_objects.html.markdown @@ -0,0 +1,32 @@ +--- +subcategory: "TOS(BETA)" +layout: "volcengine" +page_title: "Volcengine: volcengine_tos_objects" +sidebar_current: "docs-volcengine-datasource-tos_objects" +description: |- + Use this data source to query detailed information of tos objects +--- +# volcengine_tos_objects +Use this data source to query detailed information of tos objects +## Example Usage +```hcl +data "volcengine_tos_objects" "default" { + bucket_name = "test" +} +``` +## Argument Reference +The following arguments are supported: +* `bucket_name` - (Required) The name the TOS bucket. +* `name_regex` - (Optional) A Name Regex of TOS Object. +* `object_name` - (Optional) The name the TOS Object. +* `output_file` - (Optional) File name where to save data source results. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `objects` - The collection of TOS Object query. + * `name` - The name the TOS Object. + * `size` - The name the TOS Object size. + * `storage_class` - The name the TOS Object storage class. +* `total_count` - The total count of TOS Object query. + + diff --git a/website/docs/d/vke_node_pools.html.markdown b/website/docs/d/vke_node_pools.html.markdown index 323acc0c..c0e27969 100644 --- a/website/docs/d/vke_node_pools.html.markdown +++ b/website/docs/d/vke_node_pools.html.markdown @@ -45,6 +45,7 @@ In addition to all arguments above, the following attributes are exported: * `create_client_token` - The ClientToken when successfully created. * `create_time` - The CreateTime of NodePool. * `data_volumes` - The DataVolume of NodeConfig. + * `mount_point` - The target mount directory of the disk. * `size` - The Size of DataVolume. * `type` - The Type of DataVolume. * `desired_replicas` - The DesiredReplicas of AutoScaling. diff --git a/website/docs/r/tos_bucket.html.markdown b/website/docs/r/tos_bucket.html.markdown new file mode 100644 index 00000000..63770334 --- /dev/null +++ b/website/docs/r/tos_bucket.html.markdown @@ -0,0 +1,53 @@ +--- +subcategory: "TOS(BETA)" +layout: "volcengine" +page_title: "Volcengine: volcengine_tos_bucket" +sidebar_current: "docs-volcengine-resource-tos_bucket" +description: |- + Provides a resource to manage tos bucket +--- +# volcengine_tos_bucket +Provides a resource to manage tos bucket +## Example Usage +```hcl +resource "volcengine_tos_bucket" "default" { + bucket_name = "test-xym-1" + # storage_class ="IA" + public_acl = "private" + enable_version = true + account_acl { + account_id = "1" + permission = "READ" + } + account_acl { + account_id = "2001" + permission = "WRITE_ACP" + } +} +``` +## Argument Reference +The following arguments are supported: +* `bucket_name` - (Required, ForceNew) The name of the bucket. +* `account_acl` - (Optional) The user set of grant full control. +* `enable_version` - (Optional) The flag of enable tos version. +* `public_acl` - (Optional) The public acl control of object.Valid value is private|public-read|public-read-write|authenticated-read|bucket-owner-read. +* `storage_class` - (Optional, ForceNew) The storage type of the object.Valid value is STANDARD|IA. + +The `account_acl` object supports the following: + +* `account_id` - (Required) The accountId to control. +* `permission` - (Required) The permission to control.Valid value is FULL_CONTROL|READ|READ_ACP|WRITE|WRITE_ACP. +* `acl_type` - (Optional) The acl type to control.Valid value is CanonicalUser. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `id` - ID of the resource. + + + +## Import +Tos Bucket can be imported using the id, e.g. +``` +$ terraform import volcengine_tos_bucket.default region:bucketName +``` + diff --git a/website/docs/r/tos_object.html.markdown b/website/docs/r/tos_object.html.markdown new file mode 100644 index 00000000..8b6a51ae --- /dev/null +++ b/website/docs/r/tos_object.html.markdown @@ -0,0 +1,63 @@ +--- +subcategory: "TOS(BETA)" +layout: "volcengine" +page_title: "Volcengine: volcengine_tos_object" +sidebar_current: "docs-volcengine-resource-tos_object" +description: |- + Provides a resource to manage tos object +--- +# volcengine_tos_object +Provides a resource to manage tos object +## Example Usage +```hcl +resource "volcengine_tos_object" "default" { + bucket_name = "test-xym-1" + object_name = "demo_xym" + file_path = "/Users/bytedance/Work/Go/build/test.txt" + # storage_class ="IA" + public_acl = "private" + encryption = "AES256" + #content_type = "text/plain" + account_acl { + account_id = "1" + permission = "READ" + } + account_acl { + account_id = "2001" + permission = "WRITE_ACP" + } + # lifecycle { + # ignore_changes = ["file_path"] + # } +} +``` +## Argument Reference +The following arguments are supported: +* `bucket_name` - (Required, ForceNew) The name of the bucket. +* `file_path` - (Required) The file path for upload. +* `object_name` - (Required, ForceNew) The name of the object. +* `account_acl` - (Optional) The user set of grant full control. +* `content_type` - (Optional, ForceNew) The content type of the object. +* `encryption` - (Optional, ForceNew) The encryption of the object.Valid value is AES256. +* `public_acl` - (Optional) The public acl control of object.Valid value is private|public-read|public-read-write|authenticated-read|bucket-owner-read. +* `storage_class` - (Optional, ForceNew) The storage type of the object.Valid value is STANDARD|IA. + +The `account_acl` object supports the following: + +* `account_id` - (Required) The accountId to control. +* `permission` - (Required) The permission to control.Valid value is FULL_CONTROL|READ|READ_ACP|WRITE|WRITE_ACP. +* `acl_type` - (Optional) The acl type to control.Valid value is CanonicalUser. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `id` - ID of the resource. +* `enable_version` - The flag of enable tos version. +* `version_ids` - The version ids of the object if exist. + + +## Import +TOS Object can be imported using the id, e.g. +``` +$ terraform import volcengine_tos_object.default bucketName:objectName +``` + diff --git a/website/docs/r/vke_node_pool.html.markdown b/website/docs/r/vke_node_pool.html.markdown index b7c540ec..7262a314 100644 --- a/website/docs/r/vke_node_pool.html.markdown +++ b/website/docs/r/vke_node_pool.html.markdown @@ -61,8 +61,9 @@ The `auto_scaling` object supports the following: The `data_volumes` object supports the following: +* `mount_point` - (Optional, ForceNew) The target mount directory of the disk. Must start with `/`. * `size` - (Optional, ForceNew) The Size of DataVolumes, the value range in 20~32768. -* `type` - (Optional, ForceNew) The Type of DataVolumes, the value can be `PTSSD` or `ESSD_PL0`. +* `type` - (Optional, ForceNew) The Type of DataVolumes, the value can be `PTSSD` or `ESSD_PL0` or `ESSD_FlexPL`. The `kubernetes_config` object supports the following: @@ -85,7 +86,7 @@ The `node_config` object supports the following: * `instance_type_ids` - (Required, ForceNew) The InstanceTypeIds of NodeConfig. * `security` - (Required) The Security of NodeConfig. * `subnet_ids` - (Required, ForceNew) The SubnetIds of NodeConfig. -* `additional_container_storage_enabled` - (Optional) The AdditionalContainerStorageEnabled of NodeConfig. +* `additional_container_storage_enabled` - (Optional, ForceNew) The AdditionalContainerStorageEnabled of NodeConfig. * `auto_renew_period` - (Optional, ForceNew) The AutoRenewPeriod of PrePaid instance of NodeConfig. Valid values: 1, 2, 3, 6, 12. Unit: month. when InstanceChargeType is PrePaid and AutoRenew enable, default value is 1. * `auto_renew` - (Optional, ForceNew) Is AutoRenew of PrePaid instance of NodeConfig. Valid values: true, false. when InstanceChargeType is PrePaid, default value is true. * `data_volumes` - (Optional, ForceNew) The DataVolumes of NodeConfig. @@ -104,7 +105,7 @@ The `security` object supports the following: The `system_volume` object supports the following: * `size` - (Optional, ForceNew) The Size of SystemVolume, the value range in 20~2048. -* `type` - (Optional, ForceNew) The Type of SystemVolume, the value can be `PTSSD` or `ESSD_PL0`. +* `type` - (Optional, ForceNew) The Type of SystemVolume, the value can be `PTSSD` or `ESSD_PL0` or `ESSD_FlexPL`. The `taints` object supports the following: diff --git a/website/volcengine.erb b/website/volcengine.erb index 92c21ebe..1fe429ff 100644 --- a/website/volcengine.erb +++ b/website/volcengine.erb @@ -316,6 +316,33 @@ +
  • + TOS(BETA) + +
  • VKE