diff --git a/go.mod b/go.mod index beb3d6176f..f033bcebba 100644 --- a/go.mod +++ b/go.mod @@ -89,7 +89,7 @@ require ( k8s.io/cluster-bootstrap v0.19.3 k8s.io/cri-api v0.22.17 moul.io/http2curl/v2 v2.3.0 - yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240521070416-dbc80ff43103 + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240522105017-236088f9697b yunion.io/x/executor v0.0.0-20230705125604-c5ac3141db32 yunion.io/x/jsonutils v1.0.1-0.20240203102553-4096f103b401 yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91 diff --git a/go.sum b/go.sum index b89719b337..d94b163e8c 100644 --- a/go.sum +++ b/go.sum @@ -1213,8 +1213,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240521070416-dbc80ff43103 h1:f5vNbU6wUTO9DfoES8QqPuPtfo1AjUU6d+W0BdNqWik= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240521070416-dbc80ff43103/go.mod h1:quoJjGTJ2PjAY0+3YeN5JuN136whECKmfkJQwIsXKjM= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240522105017-236088f9697b h1:d3c79OrzQWrmmZfzgw4hoKMIxrrRVlco2vXEpYhOakE= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240522105017-236088f9697b/go.mod h1:quoJjGTJ2PjAY0+3YeN5JuN136whECKmfkJQwIsXKjM= yunion.io/x/executor v0.0.0-20230705125604-c5ac3141db32 h1:v7POYkQwo1XzOxBoIoRVr/k0V9Y5JyjpshlIFa9raug= yunion.io/x/executor v0.0.0-20230705125604-c5ac3141db32/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= diff --git a/vendor/modules.txt b/vendor/modules.txt index 2ae56fa2c1..3d80bd98fe 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1483,7 +1483,7 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.2.0 ## explicit; go 1.12 sigs.k8s.io/yaml -# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240521070416-dbc80ff43103 +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20240522105017-236088f9697b ## explicit; go 1.18 yunion.io/x/cloudmux/pkg/apis yunion.io/x/cloudmux/pkg/apis/billing diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/bucket.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/bucket.go index 925b9db878..bfabadfff2 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/bucket.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/bucket.go @@ -60,7 +60,7 @@ func (b *SBucket) GetProjectId() string { } func (b *SBucket) GetGlobalId() string { - if b.getAppId() == b.region.client.appId { + if b.getAppId() == b.region.client.GetAppId() { return b.Name } else { return b.getFullName() @@ -152,9 +152,9 @@ func (b *SBucket) getAppId() string { return b.appId } if b.zone != nil { - return b.zone.region.client.appId + return b.zone.region.client.GetAppId() } - return b.region.client.appId + return b.region.client.GetAppId() } func (b *SBucket) getFullName() string { diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/organization.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/organization.go new file mode 100644 index 0000000000..e396c92408 --- /dev/null +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/organization.go @@ -0,0 +1,92 @@ +// Copyright 2019 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 qcloud + +import ( + "fmt" + "time" +) + +type SOrganizationMember struct { + AppId string + AuthName string + AuthStatus string + BindStatus string + CreateTime time.Time + IsAllowQuit string + MemberType string + MemberUin int + Name string + NodeId int + NodeName string + OrgIdentity []struct { + IdentityAliasName string + IdentityId int + } + OrgPermission []struct { + Id string + Name string + } + OrgPolicyName string + OrgPolicyType string + PayName string + PayUin string + PermissionStatus string + Remark string +} + +func (self *SQcloudClient) DescribeOrganizationMembers() ([]SOrganizationMember, error) { + params := map[string]string{ + "Limit": "50", + "Offset": "0", + } + ret := []SOrganizationMember{} + for { + resp, err := self.orgRequest("DescribeOrganizationMembers", params) + if err != nil { + return nil, err + } + part := struct { + Items []SOrganizationMember + Total int + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, err + } + ret = append(ret, part.Items...) + if len(ret) >= part.Total || len(part.Items) == 0 { + break + } + params["Offset"] = fmt.Sprintf("%d", len(ret)) + } + return ret, nil +} + +func (self *SQcloudClient) DescribeOrganizationMember(uin string) (*SOrganizationMember, error) { + params := map[string]string{ + "MemberUin": uin, + } + resp, err := self.orgRequest("DescribeOrganizationMember", params) + if err != nil { + return nil, err + } + ret := &SOrganizationMember{} + err = resp.Unmarshal(ret) + if err != nil { + return nil, err + } + return ret, nil +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/provider/provider.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/provider/provider.go index f6277ae57c..0fc89c9248 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/provider/provider.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/provider/provider.go @@ -164,16 +164,13 @@ func (self *SQcloudProviderFactory) ValidateChangeBandwidth(instanceId string, b func (self *SQcloudProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { output := cloudprovider.SCloudaccount{} - if len(input.AppId) == 0 { - return output, errors.Wrap(cloudprovider.ErrMissingParameter, "app_id") - } if len(input.SecretId) == 0 { return output, errors.Wrap(cloudprovider.ErrMissingParameter, "secret_id") } if len(input.SecretKey) == 0 { return output, errors.Wrap(cloudprovider.ErrMissingParameter, "secret_key") } - output.Account = fmt.Sprintf("%s/%s", input.SecretId, input.AppId) + output.Account = input.SecretId output.Secret = input.SecretKey return output, nil } @@ -182,10 +179,9 @@ func (self *SQcloudProviderFactory) ValidateUpdateCloudaccountCredential(ctx con output := cloudprovider.SCloudaccount{} if len(input.AppId) == 0 { accountInfo := strings.Split(cloudaccount, "/") - if len(accountInfo) < 2 { - return output, errors.Wrap(cloudprovider.ErrMissingParameter, "app_id") + if len(accountInfo) == 2 { + input.AppId = accountInfo[1] } - input.AppId = accountInfo[1] } if len(input.SecretId) == 0 { return output, errors.Wrap(cloudprovider.ErrMissingParameter, "secret_id") @@ -193,8 +189,12 @@ func (self *SQcloudProviderFactory) ValidateUpdateCloudaccountCredential(ctx con if len(input.SecretKey) == 0 { return output, errors.Wrap(cloudprovider.ErrMissingParameter, "secret_key") } + account := input.SecretId + if len(input.AppId) > 0 { + account = fmt.Sprintf("%s/%s", input.SecretId, input.AppId) + } output = cloudprovider.SCloudaccount{ - Account: fmt.Sprintf("%s/%s", input.SecretId, input.AppId), + Account: account, Secret: input.SecretKey, } return output, nil @@ -202,15 +202,15 @@ func (self *SQcloudProviderFactory) ValidateUpdateCloudaccountCredential(ctx con func (self *SQcloudProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig) (cloudprovider.ICloudProvider, error) { secretId := cfg.Account - appId := "" + accountId := "" if tmp := strings.Split(cfg.Account, "/"); len(tmp) == 2 { secretId = tmp[0] - appId = tmp[1] + accountId = tmp[1] } client, err := qcloud.NewQcloudClient( qcloud.NewQcloudClientConfig( secretId, cfg.Secret, - ).AppId(appId).CloudproviderConfig(cfg), + ).AccountId(accountId).CloudproviderConfig(cfg), ) if err != nil { return nil, err @@ -223,17 +223,20 @@ func (self *SQcloudProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig func (self *SQcloudProviderFactory) GetClientRC(info cloudprovider.SProviderInfo) (map[string]string, error) { secretId := info.Account - appId := "" + accountId := "" if tmp := strings.Split(info.Account, "/"); len(tmp) == 2 { secretId = tmp[0] - appId = tmp[1] + accountId = tmp[1] } - return map[string]string{ - "QCLOUD_APPID": appId, + ret := map[string]string{ "QCLOUD_SECRET_ID": secretId, "QCLOUD_SECRET_KEY": info.Secret, "QCLOUD_REGION": qcloud.QCLOUD_DEFAULT_REGION, - }, nil + } + if len(accountId) == 12 { + ret["QCLOUD_ACCOUNT_ID"] = accountId + } + return ret, nil } func init() { diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/qcloud.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/qcloud.go index aa1221fb12..b4adacd04b 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/qcloud.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/qcloud.go @@ -72,6 +72,7 @@ const ( QCLOUD_STS_API_VERSION = "2018-08-13" QCLOUD_TAG_API_VERSION = "2018-08-13" QCLOUD_WAF_API_VERSION = "2018-01-25" + QCLOUD_ORG_API_VERSION = "2021-03-31" ) type QcloudClientConfig struct { @@ -79,6 +80,7 @@ type QcloudClientConfig struct { secretId string secretKey string + accountId string appId string debug bool @@ -97,8 +99,12 @@ func (cfg *QcloudClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderC return cfg } -func (cfg *QcloudClientConfig) AppId(appId string) *QcloudClientConfig { - cfg.appId = appId +func (cfg *QcloudClientConfig) AccountId(accountId string) *QcloudClientConfig { + if len(accountId) == 12 { + cfg.accountId = accountId + } else { + cfg.appId = accountId + } return cfg } @@ -158,6 +164,11 @@ func vpcRequest(client *common.Client, apiName string, params map[string]string, return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, true) } +func orgRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) { + domain := "organization.tencentcloudapi.com" + return _jsonRequest(client, domain, QCLOUD_ORG_API_VERSION, apiName, params, updateFunc, debug, true) +} + func auditRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) { domain := apiDomain("cloudaudit", params) return _jsonRequest(client, domain, QCLOUD_AUDIT_API_VERSION, apiName, params, updateFunc, debug, true) @@ -358,6 +369,7 @@ func _baseJsonRequest(client *common.Client, req tchttp.Request, resp qcloudResp "InvalidParameter.RoleNotExist", "ResourceNotFound", "FailedOperation.CertificateNotFound", + "ResourceNotFound.OrganizationNotExist", }) { return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error()) } @@ -440,6 +452,14 @@ func (client *SQcloudClient) getSdkClient(regionId string) (*common.Client, erro if err != nil { return nil, err } + if len(client.accountId) > 0 { + arn := fmt.Sprintf("qcs::cam::uin/%s:roleName/%s", client.accountId, "OrganizationAccessControlRole") + sts := common.DefaultRoleArnProvider(client.secretId, client.secretKey, arn) + cli, err = cli.WithProvider(sts) + if err != nil { + return nil, errors.Wrapf(err, "WithProvider") + } + } httpClient := client.cpcfg.AdaptiveTimeoutHttpClient() ts, _ := httpClient.Transport.(*http.Transport) cli.WithHttpTransport(cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) { @@ -489,6 +509,14 @@ func (client *SQcloudClient) vpcRequest(apiName string, params map[string]string return vpcRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug) } +func (client *SQcloudClient) orgRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient(params) + if err != nil { + return nil, err + } + return orgRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug) +} + func (client *SQcloudClient) auditRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient(params) if err != nil { @@ -750,6 +778,9 @@ func (self *SQcloudClient) invalidateIBuckets() { } func (self *SQcloudClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) { + if len(self.accountId) > 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "organization") + } if self.ibuckets == nil { err := self.fetchBuckets() if err != nil { @@ -818,8 +849,8 @@ func (client *SQcloudClient) fetchBuckets() error { createAt, _ := timeutils.ParseTimeStr(bInfo.CreationDate) slashPos := strings.LastIndexByte(bInfo.Name, '-') appId := bInfo.Name[slashPos+1:] - if appId != client.appId { - log.Errorf("[%s %s] Inconsistent appId: %s expect %s", bInfo.Name, bInfo.Region, appId, client.appId) + if appId != client.GetAppId() { + log.Errorf("[%s %s] Inconsistent appId: %s expect %s", bInfo.Name, bInfo.Region, appId, client.GetAppId()) } name := bInfo.Name[:slashPos] region, err := client.getIRegionByRegionId(bInfo.Region) @@ -865,20 +896,33 @@ func (client *SQcloudClient) fetchBuckets() error { } func (client *SQcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { - err := client.fetchRegions() - if err != nil { + nodes, err := client.DescribeOrganizationMembers() + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { return nil, err } subAccount := cloudprovider.SSubAccount{} subAccount.Id = client.GetAccountId() subAccount.Name = client.cpcfg.Name subAccount.Account = client.secretId - subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL - subAccount.DefaultProjectId = "0" - if len(client.appId) > 0 { + if len(client.appId) > 0 { // 兼容旧版本账号,避免订阅删除 subAccount.Account = fmt.Sprintf("%s/%s", client.secretId, client.appId) } - return []cloudprovider.SSubAccount{subAccount}, nil + subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL + subAccount.DefaultProjectId = "0" + ret := []cloudprovider.SSubAccount{subAccount} + for _, node := range nodes { + uin := fmt.Sprintf("%d", node.MemberUin) + if len(subAccount.Id) > 0 && uin != subAccount.Id { + account := cloudprovider.SSubAccount{} + account.Id = uin + account.Name = node.Name + account.Account = fmt.Sprintf("%s/%s", client.secretId, uin) + account.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL + account.DefaultProjectId = "0" + ret = append(ret, account) + } + } + return ret, nil } func (self *SQcloudClient) GetAccountId() string { @@ -1017,6 +1061,18 @@ func (client *SQcloudClient) GetIProjects() ([]cloudprovider.ICloudProject, erro return iprojects, nil } +func (client *SQcloudClient) GetAppId() string { + if len(client.appId) > 0 { + return client.appId + } + resp, err := client.camRequest("GetUserAppId", map[string]string{}) + if err != nil { + return "" + } + client.appId, _ = resp.GetString("AppId") + return client.appId +} + func (self *SQcloudClient) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) { rs, err := self.GetCertificates("", "", "") if err != nil { @@ -1039,7 +1095,6 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP, cloudprovider.CLOUD_CAPABILITY_EIP, cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, - cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, cloudprovider.CLOUD_CAPABILITY_RDS, cloudprovider.CLOUD_CAPABILITY_CACHE, cloudprovider.CLOUD_CAPABILITY_EVENT, @@ -1057,5 +1112,9 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_SNAPSHOT_POLICY, cloudprovider.CLOUD_CAPABILITY_WAF + cloudprovider.READ_ONLY_SUFFIX, } + // 官方cos sdk 未支持sts + if len(self.accountId) == 0 { + caps = append(caps, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE) + } return caps } diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/storagecache.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/storagecache.go index 90e9bcf853..7858ad6e47 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/storagecache.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/storagecache.go @@ -105,7 +105,7 @@ func (self *SStoragecache) UploadImage(ctx context.Context, image *cloudprovider func (self *SRegion) getCosUrl(bucket, object string) string { //signature := cosauth.NewSignature(self.client.AppID, bucket, self.client.secretId, time.Now().Add(time.Minute*30).String(), time.Now().String(), "yunion", object).SignOnce(self.client.secretKey) - return fmt.Sprintf("http://%s-%s.cos.%s.myqcloud.com/%s", bucket, self.client.appId, self.Region, object) + return fmt.Sprintf("http://%s-%s.cos.%s.myqcloud.com/%s", bucket, self.client.GetAppId(), self.Region, object) } func (self *SStoragecache) uploadImage(ctx context.Context, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {