mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-24 23:41:52 +08:00
fix(region): vendor update (#18591)
This commit is contained in:
@@ -83,7 +83,7 @@ require (
|
||||
k8s.io/client-go v0.19.3
|
||||
k8s.io/cluster-bootstrap v0.19.3
|
||||
moul.io/http2curl/v2 v2.3.0
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231103051434-611bf46d2441
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231106041411-c516a6835d01
|
||||
yunion.io/x/executor v0.0.0-20230705125604-c5ac3141db32
|
||||
yunion.io/x/jsonutils v1.0.1-0.20230613121553-0f3b41e2ef19
|
||||
yunion.io/x/log v1.0.1-0.20230411060016-feb3f46ab361
|
||||
|
||||
@@ -1179,8 +1179,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.20231103051434-611bf46d2441 h1:xLB2zpFHYTkQ/9hHbV2T/7PMUTxDp240vA70tCeb0i4=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231103051434-611bf46d2441/go.mod h1:McRjoG2gaOUisB+Qa41kLCNZhr0lsCu4apEjTiphXVY=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231106041411-c516a6835d01 h1:wpwKBOnIbQnjbIk/JdAHk/Nq1pdhxBEBxKAqLsIbUKw=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231106041411-c516a6835d01/go.mod h1:McRjoG2gaOUisB+Qa41kLCNZhr0lsCu4apEjTiphXVY=
|
||||
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=
|
||||
|
||||
Vendored
+1
-1
@@ -1438,7 +1438,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.20231103051434-611bf46d2441
|
||||
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20231106041411-c516a6835d01
|
||||
## explicit; go 1.18
|
||||
yunion.io/x/cloudmux/pkg/apis
|
||||
yunion.io/x/cloudmux/pkg/apis/billing
|
||||
|
||||
+4
@@ -385,6 +385,10 @@ func IsSupportElasticSearch(prod ICloudProvider) bool {
|
||||
return IsSupportCapability(prod, CLOUD_CAPABILITY_ES)
|
||||
}
|
||||
|
||||
func IsSupportSSLCertificate(prod ICloudProvider) bool {
|
||||
return IsSupportCapability(prod, CLOUD_CAPABILITY_CERT)
|
||||
}
|
||||
|
||||
func IsSupportKafka(prod ICloudProvider) bool {
|
||||
return IsSupportCapability(prod, CLOUD_CAPABILITY_KAFKA)
|
||||
}
|
||||
|
||||
+1
@@ -57,6 +57,7 @@ const (
|
||||
CLOUD_CAPABILITY_MODELARTES = "modelarts"
|
||||
CLOUD_CAPABILITY_VPC_PEER = "vpcpeer" // 对等连接
|
||||
CLOUD_CAPABILITY_MISC = "misc"
|
||||
CLOUD_CAPABILITY_CERT = "sslcertificates" // 证书
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+21
@@ -203,6 +203,8 @@ type ICloudRegion interface {
|
||||
GetIModelartsPoolSku() ([]ICloudModelartsPoolSku, error)
|
||||
|
||||
GetIMiscResources() ([]ICloudMiscResource, error)
|
||||
|
||||
GetISSLCertificates() ([]ICloudSSLCertificate, error)
|
||||
}
|
||||
|
||||
type ICloudZone interface {
|
||||
@@ -1681,3 +1683,22 @@ type ICloudMiscResource interface {
|
||||
|
||||
GetConfig() jsonutils.JSONObject
|
||||
}
|
||||
|
||||
type ICloudSSLCertificate interface {
|
||||
IVirtualResource
|
||||
|
||||
GetSans() string
|
||||
GetStartDate() time.Time
|
||||
GetProvince() string
|
||||
GetCommon() string
|
||||
GetCountry() string
|
||||
GetIssuer() string
|
||||
GetExpired() bool
|
||||
GetEndDate() time.Time
|
||||
GetFingerprint() string
|
||||
GetCity() string
|
||||
GetOrgName() string
|
||||
GetIsUpload() bool
|
||||
GetCert() string
|
||||
GetKey() string
|
||||
}
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ import (
|
||||
)
|
||||
|
||||
type SSubAccount struct {
|
||||
Id string // 账号ID
|
||||
// 若Account不为空,可不传
|
||||
Name string
|
||||
// 描述信息
|
||||
|
||||
+6
@@ -79,6 +79,7 @@ const (
|
||||
ALIYUN_K8S_API_VERSION = "2015-12-15"
|
||||
ALIYUN_OTS_API_VERSION = "2016-06-20"
|
||||
ALIYUN_RD_API_VERSION = "2022-04-19"
|
||||
ALIYUN_CAS_API_VERSION = "2018-07-13"
|
||||
|
||||
ALIYUN_SERVICE_ECS = "ecs"
|
||||
ALIYUN_SERVICE_VPC = "vpc"
|
||||
@@ -762,6 +763,7 @@ func (self *SAliyunClient) GetRegions() []SRegion {
|
||||
|
||||
func (self *SAliyunClient) getSubAccount() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKey
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
@@ -796,6 +798,7 @@ func (self *SAliyunClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
account := cloudprovider.SSubAccount{}
|
||||
account.Name = fmt.Sprintf("%s/%s", accounts[i].DisplayName, self.cpcfg.Name)
|
||||
account.Account = self.accessKey
|
||||
account.Id = accountId
|
||||
account.HealthStatus = api.CLOUD_PROVIDER_HEALTH_SUSPENDED
|
||||
if strings.HasSuffix(accounts[i].Status, "Success") {
|
||||
account.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
@@ -803,6 +806,7 @@ func (self *SAliyunClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
if accounts[i].AccountId != accountId {
|
||||
account.Name = fmt.Sprintf("%s/%s", accounts[i].DisplayName, accounts[i].AccountId)
|
||||
account.Account = fmt.Sprintf("%s/%s", self.accessKey, accounts[i].AccountId)
|
||||
account.Id = accounts[i].AccountId
|
||||
}
|
||||
ret = append(ret, account)
|
||||
}
|
||||
@@ -815,6 +819,7 @@ func (self *SAliyunClient) GetAccountId() string {
|
||||
}
|
||||
caller, err := self.GetCallerIdentity()
|
||||
if err != nil {
|
||||
log.Errorf("GetCallerIdentity fail %s", err)
|
||||
return ""
|
||||
}
|
||||
self.ownerId = caller.AccountId
|
||||
@@ -982,6 +987,7 @@ func (region *SAliyunClient) GetCapabilities() []string {
|
||||
cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_TABLESTORE + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_CERT,
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
+9
@@ -246,6 +246,15 @@ func (self *SRegion) kvsRequest(action string, params map[string]string) (jsonut
|
||||
return jsonRequest(client, "r-kvstore.aliyuncs.com", ALIYUN_API_VERSION_KVS, action, params, self.client.debug)
|
||||
}
|
||||
|
||||
func (self *SRegion) scRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
|
||||
client, err := self.getSdkClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain := "cas.aliyuncs.com"
|
||||
return jsonRequest(client, domain, ALIYUN_CAS_API_VERSION, apiName, params, self.client.debug)
|
||||
}
|
||||
|
||||
type LBRegion struct {
|
||||
RegionEndpoint string
|
||||
RegionId string
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
"yunion.io/x/cloudmux/pkg/multicloud"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type SSSLCertificate struct {
|
||||
multicloud.SVirtualResourceBase
|
||||
AliyunTags
|
||||
region *SRegion
|
||||
|
||||
Sans string // 证书的SAN(Subject Alternative Name)扩展属性,表示证书关联的其他域名、IP地址等
|
||||
Id int // 证书ID
|
||||
StartDate time.Time // 证书签发日期
|
||||
Province string // 购买证书的用户所属的公司或组织所在的省
|
||||
Common string // 证书绑定的主域名
|
||||
Country string // 购买证书的用户所属的公司或组织所在的国家或地区
|
||||
Issuer string // 证书颁发机构
|
||||
BuyInAliyun bool // 是否在阿里云购买了证书
|
||||
Expired bool // 证书是否过期
|
||||
EndDate time.Time // 证书到期日期
|
||||
Name string // 证书名称
|
||||
Fingerprint string // 证书名称
|
||||
City string // 购买证书的用户所属的公司或组织所在的城市
|
||||
OrgName string // 购买证书的用户所属的公司或组织的名称
|
||||
|
||||
// certificate details
|
||||
detailsInitd bool
|
||||
Cert string `json:"Cert"` // 证书内容
|
||||
Key string `json:"Key"` // 证书私钥
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetSans() string {
|
||||
return s.Sans
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetStartDate() time.Time {
|
||||
return s.StartDate
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetProvince() string {
|
||||
return s.Province
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCommon() string {
|
||||
return s.Common
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCountry() string {
|
||||
return s.Country
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetIssuer() string {
|
||||
return s.Issuer
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetExpired() bool {
|
||||
return s.Expired
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetEndDate() time.Time {
|
||||
return s.EndDate
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetFingerprint() string {
|
||||
return s.Fingerprint
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCity() string {
|
||||
return s.City
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetOrgName() string {
|
||||
return s.OrgName
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetId() string {
|
||||
return strconv.Itoa(s.Id)
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetGlobalId() string {
|
||||
return strconv.Itoa(s.Id)
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetStatus() string {
|
||||
if s.Expired {
|
||||
return "expired"
|
||||
} else {
|
||||
return "normal"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetIsUpload() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCert() string {
|
||||
s.GetDetails()
|
||||
return s.Cert
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetKey() string {
|
||||
s.GetDetails()
|
||||
return s.Key
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetDetails() (*SSSLCertificate, error) {
|
||||
if !s.detailsInitd {
|
||||
cert, err := s.region.GetCertificate(s.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.detailsInitd = true
|
||||
s.Cert = cert.Cert
|
||||
s.Key = cert.Key
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetISSLCertificates 获取证书资源列表
|
||||
func (r *SRegion) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) {
|
||||
ret := make([]SSSLCertificate, 0)
|
||||
|
||||
for {
|
||||
part, total, err := r.GetSSLCertificates(100, len(ret)/100+1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetSSLCertificates")
|
||||
}
|
||||
|
||||
ret = append(ret, part...)
|
||||
if len(ret) >= total {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]cloudprovider.ICloudSSLCertificate, 0)
|
||||
for i := range ret {
|
||||
if !ret[i].BuyInAliyun {
|
||||
continue
|
||||
}
|
||||
ret[i].region = r
|
||||
result = append(result, &ret[i])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *SRegion) GetSSLCertificates(size, page int) ([]SSSLCertificate, int, error) {
|
||||
if size < 1 || size > 100 {
|
||||
size = 100
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"ShowSize": fmt.Sprintf("%d", size),
|
||||
"CurrentPage": fmt.Sprintf("%d", page),
|
||||
}
|
||||
resp, err := r.scRequest("DescribeUserCertificateList", params)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "DescribeUserCertificateList")
|
||||
}
|
||||
|
||||
ret := make([]SSSLCertificate, 0)
|
||||
err = resp.Unmarshal(&ret, "CertificateList")
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "resp.Unmarshal")
|
||||
}
|
||||
|
||||
totalCount, _ := resp.Int("TotalCount")
|
||||
return ret, int(totalCount), nil
|
||||
}
|
||||
|
||||
func (r *SRegion) GetCertificate(certId string) (*SSSLCertificate, error) {
|
||||
params := map[string]string{
|
||||
"CertId": fmt.Sprintf("%s", certId),
|
||||
}
|
||||
resp, err := r.scRequest("DescribeUserCertificateDetail", params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "DescribeUserCertificateDetail")
|
||||
}
|
||||
|
||||
cert := &SSSLCertificate{}
|
||||
err = resp.Unmarshal(cert)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal")
|
||||
}
|
||||
cert.region = r
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
+1
@@ -444,6 +444,7 @@ func (self *SApsaraClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
return nil, err
|
||||
}
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKey
|
||||
if self.organizationId != "1" {
|
||||
|
||||
+11
-8
@@ -213,8 +213,8 @@ func (r *SRegion) ListAccounts() ([]SAccount, error) {
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
defRegion, err := self.getDefaultRegion()
|
||||
func (awscli *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
defRegion, err := awscli.getDefaultRegion()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "getDefaultRegion")
|
||||
}
|
||||
@@ -224,8 +224,9 @@ func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
if strings.Contains(err.Error(), "AWSOrganizationsNotInUseException") || strings.Contains(err.Error(), "AccessDeniedException") {
|
||||
// permission denied, fall back to single account mode
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKey
|
||||
subAccount.Name = awscli.cpcfg.Name
|
||||
subAccount.Account = awscli.accessKey
|
||||
subAccount.Id = awscli.accountId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
return []cloudprovider.SSubAccount{subAccount}, nil
|
||||
} else {
|
||||
@@ -233,7 +234,7 @@ func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
}
|
||||
} else {
|
||||
// check if caller is a root caller
|
||||
caller, _ := self.GetCallerIdentity()
|
||||
caller, _ := awscli.GetCallerIdentity()
|
||||
isRootAccount := false
|
||||
// arn:aws:iam::285906155448:root
|
||||
if caller != nil && strings.HasSuffix(caller.Arn, ":root") {
|
||||
@@ -249,8 +250,9 @@ func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_SUSPENDED
|
||||
}
|
||||
if account.IsMaster {
|
||||
subAccount.Name = fmt.Sprintf("%s/%s", account.Name, self.cpcfg.Name)
|
||||
subAccount.Account = self.accessKey
|
||||
subAccount.Name = fmt.Sprintf("%s/%s", account.Name, awscli.cpcfg.Name)
|
||||
subAccount.Account = awscli.accessKey
|
||||
subAccount.Id = account.ID
|
||||
} else {
|
||||
if isRootAccount {
|
||||
log.Warningf("Cannot access non-master account with root account!!")
|
||||
@@ -260,7 +262,8 @@ func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
if len(account.Name) > 0 {
|
||||
subAccount.Name = fmt.Sprintf("%s/%s", account.Name, account.ID)
|
||||
}
|
||||
subAccount.Account = fmt.Sprintf("%s/%s", self.accessKey, account.ID)
|
||||
subAccount.Account = fmt.Sprintf("%s/%s", awscli.accessKey, account.ID)
|
||||
subAccount.Id = account.ID
|
||||
}
|
||||
subAccounts = append(subAccounts, subAccount)
|
||||
}
|
||||
|
||||
+1
@@ -896,6 +896,7 @@ func (self *SAzureClient) GetSubAccounts() (subAccounts []cloudprovider.SSubAcco
|
||||
subAccounts = make([]cloudprovider.SSubAccount, len(self.subscriptions))
|
||||
for i, subscription := range self.subscriptions {
|
||||
subAccounts[i].Account = fmt.Sprintf("%s/%s", self.tenantId, subscription.SubscriptionId)
|
||||
subAccounts[i].Id = subscription.SubscriptionId
|
||||
subAccounts[i].Name = subscription.DisplayName
|
||||
subAccounts[i].HealthStatus = subscription.GetHealthStatus()
|
||||
}
|
||||
|
||||
+1
@@ -213,6 +213,7 @@ func (self *SBaiduClient) request(method httputils.THttpMethod, service, regionI
|
||||
|
||||
func (self *SBaiduClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKeyId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
@@ -332,6 +332,7 @@ func (self *SCtyunClient) GetIRegions() []cloudprovider.ICloudRegion {
|
||||
func (self *SCtyunClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccounts := make([]cloudprovider.SSubAccount, 0)
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKey
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
@@ -296,6 +296,7 @@ func (self *SChinaUnionClient) request(method httputils.THttpMethod, resource st
|
||||
|
||||
func (self *SChinaUnionClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKeyId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
@@ -153,6 +153,7 @@ func (ec *SEcloudClient) GetCapabilities() []string {
|
||||
|
||||
func (ec *SEcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = ec.GetAccountId()
|
||||
subAccount.Name = ec.cpcfg.Name
|
||||
subAccount.Account = ec.signer.GetAccessKeyId()
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
@@ -938,6 +938,7 @@ func (client *SGoogleClient) GetSubAccounts() ([]cloudprovider.SSubAccount, erro
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Name = project.Name
|
||||
subAccount.Account = fmt.Sprintf("%s/%s", project.ProjectId, client.clientEmail)
|
||||
subAccount.Id = project.ProjectId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
if project.LifecycleState != "ACTIVE" {
|
||||
continue
|
||||
|
||||
+12
@@ -382,6 +382,16 @@ func (self *SHuaweiClient) vpcUpdate(regionId, resource string, params map[strin
|
||||
return self.request(httputils.PUT, uri, url.Values{}, params)
|
||||
}
|
||||
|
||||
func (self *SHuaweiClient) sslcertList(regionId, resource string, query url.Values) (jsonutils.JSONObject, error) {
|
||||
uri := fmt.Sprintf("https://scm.%s.myhuaweicloud.com/v3/%s", regionId, resource)
|
||||
return self.request(httputils.GET, uri, query, nil)
|
||||
}
|
||||
|
||||
func (self *SHuaweiClient) sslcertExport(regionId, resource string, scID string) (jsonutils.JSONObject, error) {
|
||||
uri := fmt.Sprintf("https://scm.%s.myhuaweicloud.com/v3/%s/%s/export", regionId, resource, scID)
|
||||
return self.request(httputils.POST, uri, url.Values{}, nil)
|
||||
}
|
||||
|
||||
type akClient struct {
|
||||
client *http.Client
|
||||
aksk aksk.SignOptions
|
||||
@@ -631,6 +641,7 @@ func (self *SHuaweiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
// continue
|
||||
// }
|
||||
s := cloudprovider.SSubAccount{
|
||||
Id: project.ID,
|
||||
Name: fmt.Sprintf("%s-%s", self.cpcfg.Name, project.Name),
|
||||
Account: fmt.Sprintf("%s/%s", self.accessKey, project.ID),
|
||||
HealthStatus: project.GetHealthStatus(),
|
||||
@@ -819,6 +830,7 @@ func (self *SHuaweiClient) GetCapabilities() []string {
|
||||
cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_MODELARTES,
|
||||
cloudprovider.CLOUD_CAPABILITY_VPC_PEER,
|
||||
cloudprovider.CLOUD_CAPABILITY_CERT,
|
||||
}
|
||||
// huawei objectstore is shared across projects(subscriptions)
|
||||
// to avoid multiple project access the same bucket
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package huawei
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
"yunion.io/x/cloudmux/pkg/multicloud"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type SSSLCertificate struct {
|
||||
multicloud.SVirtualResourceBase
|
||||
HuaweiTags
|
||||
region *SRegion
|
||||
|
||||
Id string // 证书ID
|
||||
Name string // 证书名称
|
||||
Domain string // 证书绑定的域名
|
||||
Sans string // 证书的SAN(Subject Alternative Name)扩展属性,表示证书关联的其他域名、IP地址等
|
||||
SignatureAlgorithm string // 证书签名算法
|
||||
DeploySupport bool // 是否支持部署
|
||||
Type string // 证书类型 取值如下: DV_SSL_CERT、DV_SSL_CERT_BASIC、EV_SSL_CERT、EV_SSL_CERT_PRO、OV_SSL_CERT、OV_SSL_CERT_PRO
|
||||
Brand string // 证书品牌 取值如下:GLOBALSIGN、SYMANTEC、GEOTRUST、CFCA
|
||||
//ExpireTime time.Time // 证书过期时间
|
||||
ExpireTime string // 证书过期时间
|
||||
DomainType string // 域名类型。取值如下: SINGLE_DOMAIN:单域名 WILDCARD:通配符 MULTI_DOMAIN:多域名
|
||||
ValidityPeriod int // 证书有效期,单位为月
|
||||
Status string // 证书状态。取值如下: PAID:证书已支付;待申请证书 ISSUED:证书已签发 CHECKING:证书申请审核中 CANCELCHECKING:取消证书申请审核中 UNPASSED:证书申请未通过 EXPIRED:证书已过期 REVOKING:证书吊销申请审核中 CANCLEREVOKING:证书取消吊销申请审核中 REVOKED:证书已吊销 UPLOAD:证书托管中 SUPPLEMENTCHECKING:多域名证书新增附加域名审核中 CANCELSUPPLEMENTING:取消新增附加域名审核中
|
||||
DomainCount int // 证书绑定的域名数量
|
||||
WildcardCount int // 证书绑定的通配符域名数量
|
||||
Description string // 证书描述
|
||||
EnterpriseProjectId string // 企业项目ID 默认为“0”
|
||||
|
||||
// certificate details
|
||||
detailsInitd bool
|
||||
Certificate string `json:"certificate"` // 证书内容
|
||||
PrivateKey string `json:"private_key"` // 证书私钥
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetSans() string {
|
||||
return s.Sans
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetStartDate() time.Time {
|
||||
return s.GetEndDate().AddDate(0, -s.ValidityPeriod, 0)
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetProvince() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCommon() string {
|
||||
return s.Domain
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCountry() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetIssuer() string {
|
||||
return s.Brand
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetExpired() bool {
|
||||
return time.Now().After(s.GetEndDate())
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetEndDate() time.Time {
|
||||
t, _ := time.Parse("2006-01-02 15:04:05", s.ExpireTime)
|
||||
return t
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetFingerprint() string {
|
||||
var buf bytes.Buffer
|
||||
s.GetDetails()
|
||||
certBlock, _ := pem.Decode([]byte(s.Certificate))
|
||||
if certBlock == nil {
|
||||
return ""
|
||||
}
|
||||
cert, _ := x509.ParseCertificate(certBlock.Bytes)
|
||||
fingerprint := sha1.Sum(cert.Raw)
|
||||
for _, f := range fingerprint {
|
||||
fmt.Fprintf(&buf, "%02X", f)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCity() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetOrgName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetId() string {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetGlobalId() string {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetStatus() string {
|
||||
if s.GetExpired() {
|
||||
return "expired"
|
||||
} else {
|
||||
return "normal"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetIsUpload() bool {
|
||||
if s.Status == "UPLOAD" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetCert() string {
|
||||
s.GetDetails()
|
||||
return s.Certificate
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetKey() string {
|
||||
s.GetDetails()
|
||||
return s.PrivateKey
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetDetails() (*SSSLCertificate, error) {
|
||||
if !s.detailsInitd {
|
||||
cert, err := s.region.GetCertificate(s.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.detailsInitd = true
|
||||
s.Certificate = cert.Certificate
|
||||
s.PrivateKey = cert.PrivateKey
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetISSLCertificates 获取证书资源列表
|
||||
func (r *SRegion) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) {
|
||||
ret := make([]SSSLCertificate, 0)
|
||||
offset := 0
|
||||
|
||||
for {
|
||||
part, total, err := r.GetSSLCertificates(50, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetSSLCertificates")
|
||||
}
|
||||
|
||||
ret = append(ret, part...)
|
||||
if len(ret) >= total {
|
||||
break
|
||||
}
|
||||
|
||||
offset += 50
|
||||
}
|
||||
|
||||
result := make([]cloudprovider.ICloudSSLCertificate, 0)
|
||||
for i := range ret {
|
||||
ret[i].region = r
|
||||
result = append(result, &ret[i])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *SRegion) GetSSLCertificates(size, offset int) ([]SSSLCertificate, int, error) {
|
||||
if size < 1 || size > 50 {
|
||||
size = 50
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
params := url.Values{
|
||||
"limit": []string{fmt.Sprintf("%d", size)},
|
||||
"offset": []string{fmt.Sprintf("%d", offset)},
|
||||
"sort_key": []string{"certExpiredTime"},
|
||||
"sort_dir": []string{"DESC"},
|
||||
}
|
||||
resp, err := r.client.sslcertList(r.ID, "scm/certificates", params)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "CertificateList")
|
||||
}
|
||||
|
||||
ret := make([]SSSLCertificate, 0)
|
||||
err = resp.Unmarshal(&ret, "certificates")
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "resp.Unmarshal")
|
||||
}
|
||||
|
||||
totalCount, _ := resp.Int("total_count")
|
||||
return ret, int(totalCount), nil
|
||||
}
|
||||
|
||||
func (r *SRegion) GetCertificate(certId string) (*SSSLCertificate, error) {
|
||||
resp, err := r.client.sslcertExport(r.ID, "scm/certificates", certId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "DescribeCertificateDetail")
|
||||
}
|
||||
|
||||
cert := &SSSLCertificate{}
|
||||
err = resp.Unmarshal(cert)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal")
|
||||
}
|
||||
cert.region = r
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
+1
@@ -82,6 +82,7 @@ func (self *SJDCloudClient) GetIRegions() []cloudprovider.ICloudRegion {
|
||||
|
||||
func (self *SJDCloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKey
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
@@ -261,6 +261,7 @@ func (self *SKsyunClient) request(service, regionId, apiName, apiVersion string,
|
||||
|
||||
func (self *SKsyunClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKeyId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ func (self *SRegion) GetClusterVmMaxId() int {
|
||||
}
|
||||
|
||||
if len(idxs) < 1 {
|
||||
return 0
|
||||
return 99
|
||||
}
|
||||
|
||||
sort.Ints(idxs)
|
||||
|
||||
+99
-3
@@ -28,6 +28,18 @@ import (
|
||||
"yunion.io/x/cloudmux/pkg/multicloud"
|
||||
)
|
||||
|
||||
var CERT_STATUS_MAP = map[int]string{
|
||||
0: "pending",
|
||||
1: "normal",
|
||||
2: "deleted",
|
||||
3: "expired",
|
||||
4: "normal",
|
||||
5: "pending",
|
||||
6: "deleted",
|
||||
7: "deleted",
|
||||
8: "pending",
|
||||
}
|
||||
|
||||
type projectInfo struct {
|
||||
ProjectID string `json:"projectId"`
|
||||
OwnerUin int64 `json:"ownerUin"`
|
||||
@@ -71,6 +83,7 @@ type SCertificate struct {
|
||||
SubjectAltName []string `json:"subjectAltName"`
|
||||
CertificatePrivateKey string `json:"CertificatePrivateKey"`
|
||||
CertificatePublicKey string `json:"CertificatePublicKey"`
|
||||
CertFingerprint string `json:"CertFingerprint"`
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetDetails() (*SCertificate, error) {
|
||||
@@ -83,6 +96,7 @@ func (self *SCertificate) GetDetails() (*SCertificate, error) {
|
||||
self.SubjectAltName = cert.SubjectAltName
|
||||
self.CertificatePrivateKey = cert.CertificatePrivateKey
|
||||
self.CertificatePublicKey = cert.CertificatePublicKey
|
||||
self.CertFingerprint = cert.CertFingerprint
|
||||
}
|
||||
return self, nil
|
||||
}
|
||||
@@ -97,6 +111,16 @@ func (self *SCertificate) GetPrivateKey() string {
|
||||
return self.CertificatePrivateKey
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetCert() string {
|
||||
self.GetDetails()
|
||||
return self.CertificatePublicKey
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetKey() string {
|
||||
self.GetDetails()
|
||||
return self.CertificatePrivateKey
|
||||
}
|
||||
|
||||
// 证书不能修改
|
||||
func (self *SCertificate) Sync(name, privateKey, publickKey string) error {
|
||||
return cloudprovider.ErrNotSupported
|
||||
@@ -111,7 +135,11 @@ func (self *SCertificate) GetId() string {
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetName() string {
|
||||
return self.Alias
|
||||
if len(self.Alias) > 0 {
|
||||
return self.Alias
|
||||
} else {
|
||||
return self.Domain
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetGlobalId() string {
|
||||
@@ -120,7 +148,10 @@ func (self *SCertificate) GetGlobalId() string {
|
||||
|
||||
// todo: 貌似目前onecloud没有记录状态
|
||||
func (self *SCertificate) GetStatus() string {
|
||||
return strconv.Itoa(self.Status)
|
||||
if _, ok := CERT_STATUS_MAP[self.Status]; !ok {
|
||||
return "unknown"
|
||||
}
|
||||
return CERT_STATUS_MAP[self.Status]
|
||||
}
|
||||
|
||||
func (self *SCertificate) Refresh() error {
|
||||
@@ -142,7 +173,7 @@ func (self *SCertificate) GetCommonName() string {
|
||||
|
||||
func (self *SCertificate) GetSubjectAlternativeNames() string {
|
||||
self.GetDetails()
|
||||
return strings.Join(self.SubjectAltName, ",")
|
||||
return self.CertFingerprint
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetFingerprint() string {
|
||||
@@ -160,6 +191,57 @@ func (self *SCertificate) GetProjectId() string {
|
||||
return self.ProjectID
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetSans() string {
|
||||
return strings.Join(self.SubjectAltName, ",")
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetStartDate() time.Time {
|
||||
return self.CERTBeginTime
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetProvince() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetCommon() string {
|
||||
return self.Domain
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetCountry() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetIssuer() string {
|
||||
return self.ProductZhName
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetExpired() bool {
|
||||
if self.Status == 3 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetEndDate() time.Time {
|
||||
return self.CERTEndTime
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetCity() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetOrgName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetIsUpload() bool {
|
||||
if self.From == "upload" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ssl.tencentcloudapi.com
|
||||
/*
|
||||
状态值 0:审核中,1:已通过,2:审核失败,3:已过期,4:已添加 DNS 解析记录,5:OV/EV 证书,待提交资料,6:订单取消中,7:已取消,8:已提交资料, 待上传确认函。
|
||||
@@ -318,3 +400,17 @@ func (self *SRegion) CreateILoadBalancerCertificate(input *cloudprovider.SLoadba
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// GetISSLCertificates 获取证书资源列表
|
||||
func (self *SRegion) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) {
|
||||
rs, err := self.GetCertificates("", "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]cloudprovider.ICloudSSLCertificate, 0)
|
||||
for i := range rs {
|
||||
result = append(result, &rs[i])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
+2
@@ -835,6 +835,7 @@ func (client *SQcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, erro
|
||||
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
|
||||
@@ -1003,6 +1004,7 @@ func (self *SQcloudClient) GetCapabilities() []string {
|
||||
cloudprovider.CLOUD_CAPABILITY_KAFKA + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
|
||||
cloudprovider.CLOUD_CAPABILITY_CERT,
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
+1
@@ -242,6 +242,7 @@ func (self *SQingCloudClient) request(service, action, regionId string, params m
|
||||
|
||||
func (self *SQingCloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = self.GetAccountId()
|
||||
subAccount.Name = self.cpcfg.Name
|
||||
subAccount.Account = self.accessKeyId
|
||||
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
||||
|
||||
+4
@@ -408,3 +408,7 @@ func (self *SRegion) GetIModelartsPoolSku() ([]cloudprovider.ICloudModelartsPool
|
||||
func (self *SRegion) GetIMiscResources() ([]cloudprovider.ICloudMiscResource, error) {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetIMiscResources")
|
||||
}
|
||||
|
||||
func (self *SRegion) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetISSLCertificate")
|
||||
}
|
||||
+1
@@ -298,6 +298,7 @@ func (self *SUcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
subAccounts := make([]cloudprovider.SSubAccount, 0)
|
||||
for _, project := range projects {
|
||||
subAccount := cloudprovider.SSubAccount{}
|
||||
subAccount.Id = project.ProjectID
|
||||
subAccount.Name = fmt.Sprintf("%s-%s", self.cpcfg.Name, project.ProjectName)
|
||||
// ucloud账号ID中可能包含/。因此使用::作为分割符号
|
||||
subAccount.Account = fmt.Sprintf("%s::%s", self.accessKeyId, project.ProjectID)
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/cloudmux/pkg/apis/compute"
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
|
||||
+1
@@ -189,6 +189,7 @@ func (client *SVolcEngineClient) GetSubAccounts() ([]cloudprovider.SSubAccount,
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user