| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- // Copyright 2023 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package volcengine
- import (
- "context"
- "crypto/tls"
- "fmt"
- "io"
- "net/http"
- "net/http/httputil"
- "net/url"
- "strings"
- "sync"
- "time"
- "github.com/fatih/color"
- tos "github.com/volcengine/ve-tos-golang-sdk/v2/tos"
- sdk "github.com/volcengine/volc-sdk-golang/base"
- "moul.io/http2curl/v2"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/httputils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- )
- const (
- CLOUD_PROVIDER_VOLCENGINE = api.CLOUD_PROVIDER_VOLCENGINE
- CLOUD_PROVIDER_VOLCENGINE_CN = "火山云"
- CLOUD_PROVIDER_VOLCENGINE_EN = "VolcEngine"
- VOLCENGINE_API_VERSION = "2020-04-01"
- VOLCENGINE_IAM_API_VERSION = "2018-01-01"
- VOLCENGINE_MONITOR_API_VERSION = "2018-01-01"
- VOLCENGINE_BILLING_API_VERSION = "2022-01-01"
- VOLCENGINE_API = "open.volcengineapi.com"
- VOLCENGINE_IAM_API = "iam.volcengineapi.com"
- VOLCENGINE_TOS_API = "tos-cn-beijing.volces.com"
- VOLCENGINE_BILLING_API = "billing.volcengineapi.com"
- VOLCENGINE_SERVICE_ECS = "ecs"
- VOLCENGINE_SERVICE_VPC = "vpc"
- VOLCENGINE_SERVICE_NAT = "natgateway"
- VOLCENGINE_SERVICE_STORAGE = "storage_ebs"
- VOLCENGINE_SERVICE_IAM = "iam"
- VOLCENGINE_SERVICE_TOS = "tos"
- VOLCENGINE_SERVICE_MONITOR = "cloudmonitor"
- VOLCENGINE_SERVICE_BILLING = "billing"
- VOLCENGINE_DEFAULT_REGION = "cn-beijing"
- )
- type VolcEngineClientConfig struct {
- cpcfg cloudprovider.ProviderConfig
- cloudEnv string
- accessKey string
- secretKey string
- accountId string
- debug bool
- client *http.Client
- lock sync.Mutex
- }
- func NewVolcEngineClientConfig(accessKey, secretKey string) *VolcEngineClientConfig {
- cfg := &VolcEngineClientConfig{
- accessKey: accessKey,
- secretKey: secretKey,
- }
- return cfg
- }
- func (cfg *VolcEngineClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *VolcEngineClientConfig {
- cfg.cpcfg = cpcfg
- return cfg
- }
- func (cfg *VolcEngineClientConfig) AccountId(id string) *VolcEngineClientConfig {
- cfg.accountId = id
- return cfg
- }
- func (cfg *VolcEngineClientConfig) Debug(debug bool) *VolcEngineClientConfig {
- cfg.debug = debug
- return cfg
- }
- type SVolcEngineClient struct {
- *VolcEngineClientConfig
- ownerId string
- projects []SProject
- iregions []cloudprovider.ICloudRegion
- iBuckets []cloudprovider.ICloudBucket
- }
- func NewVolcEngineClient(cfg *VolcEngineClientConfig) (*SVolcEngineClient, error) {
- client := SVolcEngineClient{
- VolcEngineClientConfig: cfg,
- }
- err := client.fetchRegions()
- if err != nil {
- return nil, errors.Wrap(err, "fetchReginos")
- }
- return &client, nil
- }
- // Regions
- func (client *SVolcEngineClient) fetchRegions() error {
- body, err := client.ecsRequest("", "DescribeRegions", nil)
- if err != nil {
- return errors.Wrapf(err, "DescribeRegions")
- }
- regions := make([]SRegion, 0)
- err = body.Unmarshal(®ions, "Regions")
- if err != nil {
- return errors.Wrapf(err, "resp.Unmarshal")
- }
- client.iregions = make([]cloudprovider.ICloudRegion, len(regions))
- for i := 0; i < len(regions); i += 1 {
- regions[i].client = client
- client.iregions[i] = ®ions[i]
- }
- return nil
- }
- func (client *SVolcEngineClient) GetRegions() []SRegion {
- regions := make([]SRegion, len(client.iregions))
- for i := 0; i < len(regions); i += 1 {
- region := client.iregions[i].(*SRegion)
- regions[i] = *region
- }
- return regions
- }
- func (client *SVolcEngineClient) GetRegion(regionId string) *SRegion {
- if len(regionId) == 0 {
- regionId = VOLCENGINE_DEFAULT_REGION
- }
- for i := 0; i < len(client.iregions); i += 1 {
- if client.iregions[i].GetId() == regionId {
- return client.iregions[i].(*SRegion)
- }
- }
- return nil
- }
- func (client *SVolcEngineClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
- return client.iregions, nil
- }
- func (client *SVolcEngineClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
- for i := 0; i < len(client.iregions); i += 1 {
- if (client.iregions[i].GetId() == id) || (client.iregions[i].GetGlobalId() == id) {
- return client.iregions[i], nil
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (client *SVolcEngineClient) GetAccountId() string {
- if len(client.ownerId) > 0 {
- return client.ownerId
- }
- balance, err := client.QueryBalance()
- if err != nil {
- return ""
- }
- client.ownerId = balance.AccountId
- return client.ownerId
- }
- func (client *SVolcEngineClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
- err := client.fetchRegions()
- if err != nil {
- return nil, err
- }
- subAccount := cloudprovider.SSubAccount{}
- subAccount.Id = client.GetAccountId()
- subAccount.Name = client.cpcfg.Name
- subAccount.Account = client.accessKey
- subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
- subAccount.DefaultProjectId = "default"
- return []cloudprovider.SSubAccount{subAccount}, nil
- }
- func (client *SVolcEngineClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
- projects, err := client.GetProjects()
- if err != nil {
- return nil, err
- }
- ret := []cloudprovider.ICloudProject{}
- for i := range projects {
- ret = append(ret, &projects[i])
- }
- return ret, nil
- }
- func (client *SVolcEngineClient) GetProjects() ([]SProject, error) {
- if len(client.projects) > 0 {
- return client.projects, nil
- }
- limit, offset := 50, 0
- client.projects = []SProject{}
- for {
- parts, total, err := client.ListProjects(limit, offset)
- if err != nil {
- return nil, errors.Wrap(err, "GetProjects")
- }
- client.projects = append(client.projects, parts...)
- if len(client.projects) >= total {
- break
- }
- offset = len(client.projects)
- }
- return client.projects, nil
- }
- func (cli *SVolcEngineClient) getDefaultClient() *http.Client {
- cli.lock.Lock()
- defer cli.lock.Unlock()
- if !gotypes.IsNil(cli.client) {
- return cli.client
- }
- cli.client = httputils.GetAdaptiveTimeoutClient()
- httputils.SetClientProxyFunc(cli.client, cli.cpcfg.ProxyFunc)
- ts, _ := cli.client.Transport.(*http.Transport)
- ts.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
- cli.client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
- if cli.cpcfg.ReadOnly {
- action := req.URL.Query().Get("Action")
- if len(action) > 0 {
- for _, prefix := range []string{"Get", "Describe", "List"} {
- if strings.HasPrefix(action, prefix) {
- return nil, nil
- }
- }
- }
- return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
- }
- return nil, nil
- })
- return cli.client
- }
- type sCred struct {
- client *SVolcEngineClient
- cred sdk.Credentials
- }
- func (self *sCred) Do(req *http.Request) (*http.Response, error) {
- cli := self.client.getDefaultClient()
- req = self.cred.Sign(req)
- return cli.Do(req)
- }
- func (client *SVolcEngineClient) monitorRequest(regionId, apiName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
- cred := client.getSdkCredential(regionId, VOLCENGINE_SERVICE_MONITOR, "")
- return client.jsonRequest(cred, VOLCENGINE_API, VOLCENGINE_MONITOR_API_VERSION, apiName, params)
- }
- func (client *SVolcEngineClient) jsonRequest(cred sdk.Credentials, domain string, apiVersion string, apiName string, params interface{}) (jsonutils.JSONObject, error) {
- query := url.Values{
- "Action": []string{apiName},
- "Version": []string{apiVersion},
- }
- method := httputils.GET
- for prefix, _method := range map[string]httputils.THttpMethod{
- "Get": httputils.GET,
- "Describe": httputils.GET,
- "List": httputils.GET,
- "Delete": httputils.GET,
- "Put": httputils.PUT,
- "Create": httputils.POST,
- } {
- if strings.HasPrefix(apiName, prefix) {
- method = _method
- break
- }
- }
- if strings.HasPrefix(domain, "bucketname") {
- if strings.HasPrefix(apiName, "Delete") {
- method = httputils.DELETE
- }
- }
- if cred.Service == VOLCENGINE_SERVICE_MONITOR {
- method = httputils.POST
- }
- // 私网接口仅支持GET请求
- if cred.Service == VOLCENGINE_SERVICE_VPC {
- method = httputils.GET
- }
- var reqBody io.Reader = nil
- switch method {
- case httputils.POST:
- reqBody = strings.NewReader(jsonutils.Marshal(params).String())
- default:
- _params, _ := params.(map[string]string)
- for k, v := range _params {
- query.Set(k, v)
- }
- }
- u, err := url.Parse(fmt.Sprintf("http://%s?%s", domain, query.Encode()))
- if err != nil {
- return nil, errors.Wrapf(err, "url.Parse")
- }
- req, err := http.NewRequest(string(method), u.String(), reqBody)
- if err != nil {
- return nil, errors.Wrapf(err, "NewRequest")
- }
- cli := &sCred{
- client: client,
- cred: cred,
- }
- resp, err := cli.Do(req)
- if err != nil {
- return nil, errors.Wrapf(err, "Do request")
- }
- defer resp.Body.Close()
- red := color.New(color.FgRed, color.Bold).PrintlnFunc()
- green := color.New(color.FgGreen, color.Bold).PrintlnFunc()
- yellow := color.New(color.FgYellow, color.Bold).PrintlnFunc()
- cyan := color.New(color.FgHiCyan, color.Bold).PrintlnFunc()
- if client.debug {
- dump, _ := httputil.DumpRequestOut(req, true)
- yellow(string(dump))
- if req.Header.Get("Content-Type") != "application/octet-stream" {
- curlCmd, _ := http2curl.GetCurlCommand(req)
- cyan("CURL:", curlCmd, "\n")
- }
- dump, _ = httputil.DumpResponse(resp, true)
- if resp.StatusCode < 300 {
- green(string(dump))
- } else if resp.StatusCode < 400 {
- yellow(string(dump))
- } else {
- red(string(dump))
- }
- }
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, errors.Wrapf(err, "Read body")
- }
- obj, err := jsonutils.Parse(body)
- if err != nil {
- return nil, errors.Wrapf(err, "Parse body %s", string(body))
- }
- // {"ResponseMetadata":{"RequestId":"202404021200176E2994C9222303CC731B","Action":"CreateUser","Version":"2018-01-01","Service":"iam","Region":"cn-beijing","Error":{"Code":"ParameterNotFound","Message":"The parameter 'UserName' is required."}}}
- if obj.Contains("ResponseMetadata", "Error") {
- ve := &sVolcError{StatusCode: resp.StatusCode}
- obj.Unmarshal(ve, "ResponseMetadata")
- return nil, ve
- }
- if obj.Contains("Result") {
- return obj.Get("Result")
- }
- return obj, nil
- }
- func (client *SVolcEngineClient) getSdkCredential(region string, service string, token string) sdk.Credentials {
- cred := sdk.Credentials{
- AccessKeyID: client.accessKey,
- SecretAccessKey: client.secretKey,
- Region: region,
- Service: service,
- SessionToken: token,
- }
- return cred
- }
- func (client *SVolcEngineClient) getDefaultCredential(region string, service string) sdk.Credentials {
- if region == "" {
- region = VOLCENGINE_DEFAULT_REGION
- }
- cred := sdk.Credentials{
- AccessKeyID: client.accessKey,
- SecretAccessKey: client.secretKey,
- Region: region,
- Service: service,
- SessionToken: "",
- }
- return cred
- }
- type sVolcError struct {
- StatusCode int
- RequestId string
- Action string
- Version string
- Service string
- Region string
- ErrorMessage struct {
- Code string
- Message string
- } `json:"Error"`
- }
- func (self *sVolcError) Error() string {
- return jsonutils.Marshal(self).String()
- }
- func (client *SVolcEngineClient) ecsRequest(region string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
- cred := client.getDefaultCredential(region, VOLCENGINE_SERVICE_ECS)
- return client.jsonRequest(cred, VOLCENGINE_API, VOLCENGINE_API_VERSION, apiName, params)
- }
- func (client *SVolcEngineClient) iam20210801Request(region string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
- cred := client.getDefaultCredential(region, VOLCENGINE_SERVICE_IAM)
- return client.jsonRequest(cred, VOLCENGINE_IAM_API, "2021-08-01", apiName, params)
- }
- func (client *SVolcEngineClient) iamRequest(region string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
- cred := client.getDefaultCredential(region, VOLCENGINE_SERVICE_IAM)
- return client.jsonRequest(cred, VOLCENGINE_IAM_API, VOLCENGINE_IAM_API_VERSION, apiName, params)
- }
- func (client *SVolcEngineClient) getTosClient(regionId string) (*tos.ClientV2, error) {
- tosClient, err := tos.NewClientV2(VOLCENGINE_TOS_API, tos.WithRegion(regionId), tos.WithCredentials(tos.NewStaticCredentials(client.accessKey, client.secretKey)))
- return tosClient, err
- }
- func (client *SVolcEngineClient) billRequest(region string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
- cred := client.getDefaultCredential(region, VOLCENGINE_SERVICE_BILLING)
- domain := VOLCENGINE_API
- if apiName == "QueryBalanceAcct" {
- domain = VOLCENGINE_BILLING_API + "/open-apis/trade_balance"
- }
- return client.jsonRequest(cred, domain, VOLCENGINE_BILLING_API_VERSION, apiName, params)
- }
- // Buckets
- func (client *SVolcEngineClient) invalidateIBuckets() {
- client.iBuckets = nil
- }
- func (client *SVolcEngineClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
- if client.iBuckets == nil {
- err := client.fetchBuckets()
- if err != nil {
- return nil, errors.Wrap(err, "fetchBuckets")
- }
- }
- return client.iBuckets, nil
- }
- func (client *SVolcEngineClient) fetchBuckets() error {
- toscli, err := client.getTosClient(VOLCENGINE_DEFAULT_REGION)
- if err != nil {
- return errors.Wrap(err, "client.getOssClient")
- }
- out, err := toscli.ListBuckets(context.Background(), &tos.ListBucketsInput{})
- if err != nil {
- return errors.Wrap(err, "tos.ListBuckets")
- }
- ret := make([]cloudprovider.ICloudBucket, 0)
- for _, bucket := range out.Buckets {
- regionId := bucket.Location
- region, err := client.GetIRegionById(regionId)
- if err != nil {
- log.Errorf("cannot find bucket's region %s", regionId)
- continue
- }
- t, err := time.Parse(time.RFC3339, bucket.CreationDate)
- if err != nil {
- return errors.Wrapf(err, "Prase CreationDate error")
- }
- b := SBucket{
- region: region.(*SRegion),
- Name: bucket.Name,
- Location: bucket.Location,
- CreationDate: t,
- }
- ret = append(ret, &b)
- }
- client.iBuckets = ret
- return nil
- }
- func getTOSExternalDomain(regionId string) string {
- return "tos-cn-beijing.volces.com"
- }
- func getTOSInternalDomain(regionId string) string {
- return "tos-cn-beijing.ivolces.com"
- }
- func (region *SVolcEngineClient) GetCapabilities() []string {
- caps := []string{
- cloudprovider.CLOUD_CAPABILITY_PROJECT,
- cloudprovider.CLOUD_CAPABILITY_COMPUTE,
- cloudprovider.CLOUD_CAPABILITY_NETWORK,
- cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
- cloudprovider.CLOUD_CAPABILITY_EIP,
- cloudprovider.CLOUD_CAPABILITY_CLOUDID,
- cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
- cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
- }
- return caps
- }
- func (client *SVolcEngineClient) GetAccessEnv() string {
- return api.CLOUD_ACCESS_ENV_VOLCENGINE_CHINA
- }
- type SBalance struct {
- AccountId string
- ArrearsBalance float64
- AvailableBalance float64
- CashBalance float64
- CreditLimit float64
- FreezeAmount float64
- }
- func (client *SVolcEngineClient) QueryBalance() (*SBalance, error) {
- resp, err := client.billRequest("", "QueryBalanceAcct", nil)
- if err != nil {
- return nil, err
- }
- ret := &SBalance{}
- err = resp.Unmarshal(ret)
- if err != nil {
- return nil, errors.Wrapf(err, "Unmarshal")
- }
- return ret, nil
- }
|