mirror of https://github.com/usual2970/certimate
				
				
				
			
						commit
						f9568f1a4a
					
				| 
						 | 
				
			
			@ -74,8 +74,8 @@ make local.run
 | 
			
		|||
|   服务商   | 支持申请证书 | 支持部署证书 | 备注                                                         |
 | 
			
		||||
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
 | 
			
		||||
|   阿里云   |      √       |      √       | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN            |
 | 
			
		||||
|   腾讯云   |      √       |      √       | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN、COS、CLB       |
 | 
			
		||||
|   华为云   |      √       |      √       | 可签发在华为云注册的域名;可部署到华为云 CDN                 |
 | 
			
		||||
|   腾讯云   |      √       |      √       | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、CLB       |
 | 
			
		||||
|   华为云   |      √       |      √       | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB            |
 | 
			
		||||
|   七牛云   |              |      √       | 可部署到七牛云 CDN                                           |
 | 
			
		||||
|    AWS     |      √       |              | 可签发在 AWS Route53 托管的域名                              |
 | 
			
		||||
| CloudFlare |      √       |              | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,10 +71,10 @@ password:1234567890
 | 
			
		|||
## List of Supported Providers
 | 
			
		||||
 | 
			
		||||
|   Provider    | Registration | Deployment | Remarks                                                                                          |
 | 
			
		||||
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
 | 
			
		||||
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------------ |
 | 
			
		||||
| Alibaba Cloud |      √       |     √      | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN      |
 | 
			
		||||
| Tencent Cloud |      √       |     √      | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN, COS, CLB |
 | 
			
		||||
| Huawei Cloud  |      √       |     √      | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN        |
 | 
			
		||||
| Tencent Cloud |      √       |     √      | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, CLB |
 | 
			
		||||
| Huawei Cloud  |      √       |     √      | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB        |
 | 
			
		||||
|  Qiniu Cloud  |              |     √      | Supports deployment to Qiniu Cloud CDN                                                           |
 | 
			
		||||
|      AWS      |      √       |            | Supports domains managed on AWS Route53                                                          |
 | 
			
		||||
|  CloudFlare   |      √       |            | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates        |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ const (
 | 
			
		|||
	targetTencentCLB     = "tencent-clb"
 | 
			
		||||
	targetTencentCOS     = "tencent-cos"
 | 
			
		||||
	targetHuaweiCloudCDN = "huaweicloud-cdn"
 | 
			
		||||
	targetHuaweiCloudELB = "huaweicloud-elb"
 | 
			
		||||
	targetQiniuCdn       = "qiniu-cdn"
 | 
			
		||||
	targetLocal          = "local"
 | 
			
		||||
	targetSSH            = "ssh"
 | 
			
		||||
| 
						 | 
				
			
			@ -113,6 +114,8 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
 | 
			
		|||
		return NewTencentCOSDeployer(option)
 | 
			
		||||
	case targetHuaweiCloudCDN:
 | 
			
		||||
		return NewHuaweiCloudCDNDeployer(option)
 | 
			
		||||
	case targetHuaweiCloudELB:
 | 
			
		||||
		return NewHuaweiCloudELBDeployer(option)
 | 
			
		||||
	case targetQiniuCdn:
 | 
			
		||||
		return NewQiniuCDNDeployer(option)
 | 
			
		||||
	case targetLocal:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,24 +7,53 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
 | 
			
		||||
	cdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
 | 
			
		||||
	cdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
 | 
			
		||||
	cdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
 | 
			
		||||
	hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
 | 
			
		||||
	hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
 | 
			
		||||
	hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domain"
 | 
			
		||||
	uploader "github.com/usual2970/certimate/internal/pkg/core/uploader"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/uploader"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/cast"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudCDNDeployer struct {
 | 
			
		||||
	option *DeployerOption
 | 
			
		||||
	infos  []string
 | 
			
		||||
 | 
			
		||||
	sdkClient   *hcCdn.CdnClient
 | 
			
		||||
	sslUploader uploader.Uploader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
 | 
			
		||||
	access := &domain.HuaweiCloudAccess{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(option.Access), access); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient(
 | 
			
		||||
		access.AccessKeyId,
 | 
			
		||||
		access.SecretAccessKey,
 | 
			
		||||
		option.DeployConfig.GetConfigAsString("region"),
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
 | 
			
		||||
	uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{
 | 
			
		||||
		Region:          "",
 | 
			
		||||
		AccessKeyId:     access.AccessKeyId,
 | 
			
		||||
		SecretAccessKey: access.SecretAccessKey,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &HuaweiCloudCDNDeployer{
 | 
			
		||||
		option:      option,
 | 
			
		||||
		infos:       make([]string, 0),
 | 
			
		||||
		sdkClient:   client,
 | 
			
		||||
		sslUploader: uploader,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,25 +66,12 @@ func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
 | 
			
		||||
	access := &domain.HuaweiCloudAccess{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: CDN 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
 | 
			
		||||
	client, err := d.createClient("", access.AccessKeyId, access.SecretAccessKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("SDK 客户端创建成功", nil))
 | 
			
		||||
 | 
			
		||||
	// 查询加速域名配置
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
 | 
			
		||||
	showDomainFullConfigReq := &cdnModel.ShowDomainFullConfigRequest{
 | 
			
		||||
	showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
 | 
			
		||||
		DomainName: d.option.DeployConfig.GetConfigAsString("domain"),
 | 
			
		||||
	}
 | 
			
		||||
	showDomainFullConfigResp, err := client.ShowDomainFullConfig(showDomainFullConfigReq)
 | 
			
		||||
	showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -68,19 +84,10 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
 | 
			
		|||
	updateDomainMultiCertificatesReqBodyContent := &huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent{}
 | 
			
		||||
	updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
 | 
			
		||||
	updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
 | 
			
		||||
	var updateDomainMultiCertificatesResp *cdnModel.UpdateDomainMultiCertificatesResponse
 | 
			
		||||
	var updateDomainMultiCertificatesResp *hcCdnModel.UpdateDomainMultiCertificatesResponse
 | 
			
		||||
	if d.option.DeployConfig.GetConfigAsBool("useSCM") {
 | 
			
		||||
		uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{
 | 
			
		||||
			Region:          "", // TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
 | 
			
		||||
			AccessKeyId:     access.AccessKeyId,
 | 
			
		||||
			SecretAccessKey: access.SecretAccessKey,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 上传证书到 SCM
 | 
			
		||||
		uploadResult, err := uploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
 | 
			
		||||
		uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +109,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
 | 
			
		|||
			Https: updateDomainMultiCertificatesReqBodyContent,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(client, updateDomainMultiCertificatesReq)
 | 
			
		||||
	updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(d.sdkClient, updateDomainMultiCertificatesReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +119,11 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudCDNDeployer) createClient(region, accessKeyId, secretAccessKey string) (*cdn.CdnClient, error) {
 | 
			
		||||
func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdn.CdnClient, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-1" // CDN 服务默认区域:华北一北京
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auth, err := global.NewCredentialsBuilder().
 | 
			
		||||
		WithAk(accessKeyId).
 | 
			
		||||
		WithSk(secretAccessKey).
 | 
			
		||||
| 
						 | 
				
			
			@ -121,16 +132,12 @@ func (d *HuaweiCloudCDNDeployer) createClient(region, accessKeyId, secretAccessK
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-1" // CDN 服务默认区域:华北一北京
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcRegion, err := cdnRegion.SafeValueOf(region)
 | 
			
		||||
	hcRegion, err := hcCdnRegion.SafeValueOf(region)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcClient, err := cdn.CdnClientBuilder().
 | 
			
		||||
	hcClient, err := hcCdn.CdnClientBuilder().
 | 
			
		||||
		WithRegion(hcRegion).
 | 
			
		||||
		WithCredential(auth).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
| 
						 | 
				
			
			@ -138,12 +145,12 @@ func (d *HuaweiCloudCDNDeployer) createClient(region, accessKeyId, secretAccessK
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := cdn.NewCdnClient(hcClient)
 | 
			
		||||
	client := hcCdn.NewCdnClient(hcClient)
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent struct {
 | 
			
		||||
	cdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
 | 
			
		||||
	hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
 | 
			
		||||
 | 
			
		||||
	SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -156,20 +163,20 @@ type huaweicloudCDNUpdateDomainMultiCertificatesRequest struct {
 | 
			
		|||
	Body *huaweicloudCDNUpdateDomainMultiCertificatesRequestBody `json:"body,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *cdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*cdnModel.UpdateDomainMultiCertificatesResponse, error) {
 | 
			
		||||
func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *hcCdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*hcCdnModel.UpdateDomainMultiCertificatesResponse, error) {
 | 
			
		||||
	// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求
 | 
			
		||||
	// 可能需要等之后 SDK 更新
 | 
			
		||||
 | 
			
		||||
	requestDef := cdn.GenReqDefForUpdateDomainMultiCertificates()
 | 
			
		||||
	requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates()
 | 
			
		||||
 | 
			
		||||
	if resp, err := client.HcClient.Sync(request, requestDef); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else {
 | 
			
		||||
		return resp.(*cdnModel.UpdateDomainMultiCertificatesResponse), nil
 | 
			
		||||
		return resp.(*hcCdnModel.UpdateDomainMultiCertificatesResponse), nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent {
 | 
			
		||||
func mergeHuaweiCloudCDNConfig(src *hcCdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return dest
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +193,7 @@ func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *huaweicloudCD
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if src.ForceRedirect != nil {
 | 
			
		||||
		dest.ForceRedirectConfig = &cdnModel.ForceRedirect{}
 | 
			
		||||
		dest.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
 | 
			
		||||
 | 
			
		||||
		if src.ForceRedirect.Status == "on" {
 | 
			
		||||
			dest.ForceRedirectConfig.Switch = 1
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,365 @@
 | 
			
		|||
package deployer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
 | 
			
		||||
	hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
 | 
			
		||||
	hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
 | 
			
		||||
	hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
 | 
			
		||||
	hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
 | 
			
		||||
	hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
 | 
			
		||||
	hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domain"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/uploader"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/cast"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudELBDeployer struct {
 | 
			
		||||
	option *DeployerOption
 | 
			
		||||
	infos  []string
 | 
			
		||||
 | 
			
		||||
	sdkClient   *hcElb.ElbClient
 | 
			
		||||
	sslUploader uploader.Uploader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
 | 
			
		||||
	access := &domain.HuaweiCloudAccess{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(option.Access), access); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := (&HuaweiCloudELBDeployer{}).createSdkClient(
 | 
			
		||||
		access.AccessKeyId,
 | 
			
		||||
		access.SecretAccessKey,
 | 
			
		||||
		option.DeployConfig.GetConfigAsString("region"),
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uploader, err := uploader.NewHuaweiCloudELBUploader(&uploader.HuaweiCloudELBUploaderConfig{
 | 
			
		||||
		Region:          option.DeployConfig.GetConfigAsString("region"),
 | 
			
		||||
		AccessKeyId:     access.AccessKeyId,
 | 
			
		||||
		SecretAccessKey: access.SecretAccessKey,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &HuaweiCloudELBDeployer{
 | 
			
		||||
		option:      option,
 | 
			
		||||
		infos:       make([]string, 0),
 | 
			
		||||
		sdkClient:   client,
 | 
			
		||||
		sslUploader: uploader,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) GetID() string {
 | 
			
		||||
	return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) GetInfo() []string {
 | 
			
		||||
	return d.infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context) error {
 | 
			
		||||
	switch d.option.DeployConfig.GetConfigAsString("resourceType") {
 | 
			
		||||
	case "certificate":
 | 
			
		||||
		if err := d.deployToCertificate(ctx); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "loadbalancer":
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "listener":
 | 
			
		||||
		if err := d.deployToListener(ctx); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.New("unsupported resource type")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-4" // ELB 服务默认区域:华北四北京
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projectId, err := (&HuaweiCloudELBDeployer{}).getSdkProjectId(
 | 
			
		||||
		accessKeyId,
 | 
			
		||||
		secretAccessKey,
 | 
			
		||||
		region,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auth, err := basic.NewCredentialsBuilder().
 | 
			
		||||
		WithAk(accessKeyId).
 | 
			
		||||
		WithSk(secretAccessKey).
 | 
			
		||||
		WithProjectId(projectId).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcRegion, err := hcElbRegion.SafeValueOf(region)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcClient, err := hcElb.ElbClientBuilder().
 | 
			
		||||
		WithRegion(hcRegion).
 | 
			
		||||
		WithCredential(auth).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := hcElb.NewElbClient(hcClient)
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *HuaweiCloudELBDeployer) getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-4" // IAM 服务默认区域:华北四北京
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auth, err := global.NewCredentialsBuilder().
 | 
			
		||||
		WithAk(accessKeyId).
 | 
			
		||||
		WithSk(secretAccessKey).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcRegion, err := hcIamRegion.SafeValueOf(region)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcClient, err := hcIam.IamClientBuilder().
 | 
			
		||||
		WithRegion(hcRegion).
 | 
			
		||||
		WithCredential(auth).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := hcIam.NewIamClient(hcClient)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request := &hcIamModel.KeystoneListProjectsRequest{
 | 
			
		||||
		Name: ®ion,
 | 
			
		||||
	}
 | 
			
		||||
	response, err := client.KeystoneListProjects(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	} else if response.Projects == nil || len(*response.Projects) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("no project found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (*response.Projects)[0].Id, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context) error {
 | 
			
		||||
	// 更新证书
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-elb/UpdateCertificate.html
 | 
			
		||||
	updateCertificateReq := &hcElbModel.UpdateCertificateRequest{
 | 
			
		||||
		CertificateId: d.option.DeployConfig.GetConfigAsString("certificateId"),
 | 
			
		||||
		Body: &hcElbModel.UpdateCertificateRequestBody{
 | 
			
		||||
			Certificate: &hcElbModel.UpdateCertificateOption{
 | 
			
		||||
				Certificate: cast.StringPtr(d.option.Certificate.Certificate),
 | 
			
		||||
				PrivateKey:  cast.StringPtr(d.option.Certificate.PrivateKey),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to execute sdk request 'elb.UpdateCertificate': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已更新 ELB 证书", updateCertificateResp))
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error {
 | 
			
		||||
	// 查询负载均衡器详情
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-elb/ShowLoadBalancer.html
 | 
			
		||||
	showLoadBalancerReq := &hcElbModel.ShowLoadBalancerRequest{
 | 
			
		||||
		LoadbalancerId: d.option.DeployConfig.GetConfigAsString("loadbalancerId"),
 | 
			
		||||
	}
 | 
			
		||||
	showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to execute sdk request 'elb.ShowLoadBalancer': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已查询到到 ELB 负载均衡器", showLoadBalancerResp))
 | 
			
		||||
 | 
			
		||||
	// 查询监听器列表
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-elb/ListListeners.html
 | 
			
		||||
	listenerIds := make([]string, 0)
 | 
			
		||||
	listListenersLimit := int32(2000)
 | 
			
		||||
	var listListenersMarker *string = nil
 | 
			
		||||
	for {
 | 
			
		||||
		listListenersReq := &hcElbModel.ListListenersRequest{
 | 
			
		||||
			Limit:          cast.Int32Ptr(listListenersLimit),
 | 
			
		||||
			Marker:         listListenersMarker,
 | 
			
		||||
			Protocol:       &[]string{"HTTPS", "TERMINATED_HTTPS"},
 | 
			
		||||
			LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id},
 | 
			
		||||
		}
 | 
			
		||||
		listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to execute sdk request 'elb.ListListeners': %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if listListenersResp.Listeners != nil {
 | 
			
		||||
			for _, listener := range *listListenersResp.Listeners {
 | 
			
		||||
				listenerIds = append(listenerIds, listener.Id)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		} else {
 | 
			
		||||
			listListenersMarker = listListenersResp.PageInfo.NextMarker
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已查询到到 ELB 负载均衡器下的监听器", listenerIds))
 | 
			
		||||
 | 
			
		||||
	// 上传证书到 SCM
 | 
			
		||||
	uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已上传证书", uploadResult))
 | 
			
		||||
 | 
			
		||||
	// 批量更新监听器证书
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for _, listenerId := range listenerIds {
 | 
			
		||||
		if err := d.updateListenerCertificate(ctx, listenerId, uploadResult.CertId); err != nil {
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) > 0 {
 | 
			
		||||
		return errors.Join(errs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context) error {
 | 
			
		||||
	// 上传证书到 SCM
 | 
			
		||||
	uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已上传证书", uploadResult))
 | 
			
		||||
 | 
			
		||||
	// 更新监听器证书
 | 
			
		||||
	if err := d.updateListenerCertificate(ctx, d.option.DeployConfig.GetConfigAsString("listenerId"), uploadResult.CertId); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context, hcListenerId string, hcCertId string) error {
 | 
			
		||||
	// 查询监听器详情
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
 | 
			
		||||
	showListenerReq := &hcElbModel.ShowListenerRequest{
 | 
			
		||||
		ListenerId: hcListenerId,
 | 
			
		||||
	}
 | 
			
		||||
	showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to execute sdk request 'elb.ShowListener': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已查询到到 ELB 监听器", showListenerResp))
 | 
			
		||||
 | 
			
		||||
	// 更新监听器
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-elb/UpdateListener.html
 | 
			
		||||
	updateListenerReq := &hcElbModel.UpdateListenerRequest{
 | 
			
		||||
		ListenerId: hcListenerId,
 | 
			
		||||
		Body: &hcElbModel.UpdateListenerRequestBody{
 | 
			
		||||
			Listener: &hcElbModel.UpdateListenerOption{
 | 
			
		||||
				DefaultTlsContainerRef: cast.StringPtr(hcCertId),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	if showListenerResp.Listener.SniContainerRefs != nil {
 | 
			
		||||
		if len(showListenerResp.Listener.SniContainerRefs) > 0 {
 | 
			
		||||
			// 如果开启 SNI,需替换同 SAN 的证书
 | 
			
		||||
			sniCertIds := make([]string, 0)
 | 
			
		||||
			sniCertIds = append(sniCertIds, hcCertId)
 | 
			
		||||
 | 
			
		||||
			listOldCertificateReq := &hcElbModel.ListCertificatesRequest{
 | 
			
		||||
				Id: &showListenerResp.Listener.SniContainerRefs,
 | 
			
		||||
			}
 | 
			
		||||
			listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			showNewCertificateReq := &hcElbModel.ShowCertificateRequest{
 | 
			
		||||
				CertificateId: hcCertId,
 | 
			
		||||
			}
 | 
			
		||||
			showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("failed to execute sdk request 'elb.ShowCertificate': %w", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, certificate := range *listOldCertificateResp.Certificates {
 | 
			
		||||
				oldCertificate := certificate
 | 
			
		||||
				newCertificate := showNewCertificateResp.Certificate
 | 
			
		||||
 | 
			
		||||
				if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil {
 | 
			
		||||
					oldCertificateSans := oldCertificate.SubjectAlternativeNames
 | 
			
		||||
					newCertificateSans := newCertificate.SubjectAlternativeNames
 | 
			
		||||
					sort.Strings(*oldCertificateSans)
 | 
			
		||||
					sort.Strings(*newCertificateSans)
 | 
			
		||||
					if strings.Join(*oldCertificateSans, ";") == strings.Join(*newCertificateSans, ";") {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					if oldCertificate.Domain == newCertificate.Domain {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				sniCertIds = append(sniCertIds, certificate.Id)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			updateListenerReq.Body.Listener.SniContainerRefs = &sniCertIds
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if showListenerResp.Listener.SniMatchAlgo != "" {
 | 
			
		||||
			updateListenerReq.Body.Listener.SniMatchAlgo = cast.StringPtr(showListenerResp.Listener.SniMatchAlgo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to execute sdk request 'elb.UpdateListener': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.infos = append(d.infos, toStr("已更新监听器", updateListenerResp))
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,13 +9,13 @@ type Uploader interface {
 | 
			
		|||
	// 上传证书。
 | 
			
		||||
	//
 | 
			
		||||
	// 入参:
 | 
			
		||||
	//   - ctx:
 | 
			
		||||
	//   - certPem:证书 PEM 内容
 | 
			
		||||
	//   - privkeyPem:私钥 PEM 内容
 | 
			
		||||
	//   - ctx:上下文。
 | 
			
		||||
	//   - certPem:证书 PEM 内容。
 | 
			
		||||
	//   - privkeyPem:私钥 PEM 内容。
 | 
			
		||||
	//
 | 
			
		||||
	// 出参:
 | 
			
		||||
	//   - res:
 | 
			
		||||
	//   - err:
 | 
			
		||||
	//   - res:上传结果。
 | 
			
		||||
	//   - err: 错误。
 | 
			
		||||
	Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,12 @@ type AliyunCASUploader struct {
 | 
			
		|||
	sdkRuntime *util.RuntimeOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAliyunCASUploader(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
 | 
			
		||||
	client, err := (&AliyunCASUploader{config: config}).createSdkClient()
 | 
			
		||||
func NewAliyunCASUploader(config *AliyunCASUploaderConfig) (Uploader, error) {
 | 
			
		||||
	client, err := (&AliyunCASUploader{}).createSdkClient(
 | 
			
		||||
		config.Region,
 | 
			
		||||
		config.AccessKeyId,
 | 
			
		||||
		config.AccessKeySecret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -98,13 +102,13 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
 | 
			
		||||
		if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			listUserCertificateOrderPage += 1
 | 
			
		||||
			if listUserCertificateOrderPage > 99 { // 避免死循环
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 生成新证书名(需符合阿里云命名规则)
 | 
			
		||||
	var certId, certName string
 | 
			
		||||
| 
						 | 
				
			
			@ -129,10 +133,7 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *AliyunCASUploader) createSdkClient() (*cas20200407.Client, error) {
 | 
			
		||||
	region := u.config.Region
 | 
			
		||||
	accessKeyId := u.config.AccessKeyId
 | 
			
		||||
	accessKeySecret := u.config.AccessKeySecret
 | 
			
		||||
func (u *AliyunCASUploader) createSdkClient(region, accessKeyId, accessKeySecret string) (*cas20200407.Client, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,19 +6,22 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
 | 
			
		||||
	hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
 | 
			
		||||
	hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
 | 
			
		||||
	hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
 | 
			
		||||
	hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
 | 
			
		||||
	hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
 | 
			
		||||
	hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/cast"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/x509"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudELBUploaderConfig struct {
 | 
			
		||||
	Region          string `json:"region"`
 | 
			
		||||
	ProjectId       string `json:"projectId"`
 | 
			
		||||
	AccessKeyId     string `json:"accessKeyId"`
 | 
			
		||||
	SecretAccessKey string `json:"secretAccessKey"`
 | 
			
		||||
	Region          string `json:"region"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudELBUploader struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -26,8 +29,12 @@ type HuaweiCloudELBUploader struct {
 | 
			
		|||
	sdkClient *hcElb.ElbClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
 | 
			
		||||
	client, err := (&HuaweiCloudELBUploader{config: config}).createSdkClient()
 | 
			
		||||
func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (Uploader, error) {
 | 
			
		||||
	client, err := (&HuaweiCloudELBUploader{}).createSdkClient(
 | 
			
		||||
		config.AccessKeyId,
 | 
			
		||||
		config.SecretAccessKey,
 | 
			
		||||
		config.Region,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -87,14 +94,21 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
 | 
			
		|||
 | 
			
		||||
		if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker
 | 
			
		||||
			listCertificatesPage++
 | 
			
		||||
			if listCertificatesPage >= 9 { // 避免死循环
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 获取项目 ID
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-iam/iam_06_0001.html
 | 
			
		||||
	projectId, err := u.getSdkProjectId(u.config.Region, u.config.AccessKeyId, u.config.SecretAccessKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to get SDK project id: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 生成新证书名(需符合华为云命名规则)
 | 
			
		||||
	var certId, certName string
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +119,7 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
 | 
			
		|||
	createCertificateReq := &hcElbModel.CreateCertificateRequest{
 | 
			
		||||
		Body: &hcElbModel.CreateCertificateRequestBody{
 | 
			
		||||
			Certificate: &hcElbModel.CreateCertificateOption{
 | 
			
		||||
				ProjectId:   cast.StringPtr(u.config.ProjectId),
 | 
			
		||||
				ProjectId:   cast.StringPtr(projectId),
 | 
			
		||||
				Name:        cast.StringPtr(certName),
 | 
			
		||||
				Certificate: cast.StringPtr(certPem),
 | 
			
		||||
				PrivateKey:  cast.StringPtr(privkeyPem),
 | 
			
		||||
| 
						 | 
				
			
			@ -125,10 +139,7 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
 | 
			
		|||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *HuaweiCloudELBUploader) createSdkClient() (*hcElb.ElbClient, error) {
 | 
			
		||||
	region := u.config.Region
 | 
			
		||||
	accessKeyId := u.config.AccessKeyId
 | 
			
		||||
	secretAccessKey := u.config.SecretAccessKey
 | 
			
		||||
func (u *HuaweiCloudELBUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-4" // ELB 服务默认区域:华北四北京
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -157,3 +168,47 @@ func (u *HuaweiCloudELBUploader) createSdkClient() (*hcElb.ElbClient, error) {
 | 
			
		|||
	client := hcElb.NewElbClient(hcClient)
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *HuaweiCloudELBUploader) getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-4" // IAM 服务默认区域:华北四北京
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auth, err := global.NewCredentialsBuilder().
 | 
			
		||||
		WithAk(accessKeyId).
 | 
			
		||||
		WithSk(secretAccessKey).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcRegion, err := hcIamRegion.SafeValueOf(region)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcClient, err := hcIam.IamClientBuilder().
 | 
			
		||||
		WithRegion(hcRegion).
 | 
			
		||||
		WithCredential(auth).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := hcIam.NewIamClient(hcClient)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request := &hcIamModel.KeystoneListProjectsRequest{
 | 
			
		||||
		Name: ®ion,
 | 
			
		||||
	}
 | 
			
		||||
	response, err := client.KeystoneListProjects(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	} else if response.Projects == nil || len(*response.Projects) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("no project found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (*response.Projects)[0].Id, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,9 +15,9 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudSCMUploaderConfig struct {
 | 
			
		||||
	Region          string `json:"region"`
 | 
			
		||||
	AccessKeyId     string `json:"accessKeyId"`
 | 
			
		||||
	SecretAccessKey string `json:"secretAccessKey"`
 | 
			
		||||
	Region          string `json:"region"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudSCMUploader struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +25,12 @@ type HuaweiCloudSCMUploader struct {
 | 
			
		|||
	sdkClient *hcScm.ScmClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
 | 
			
		||||
	client, err := (&HuaweiCloudSCMUploader{config: config}).createSdkClient()
 | 
			
		||||
func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (Uploader, error) {
 | 
			
		||||
	client, err := (&HuaweiCloudSCMUploader{}).createSdkClient(
 | 
			
		||||
		config.AccessKeyId,
 | 
			
		||||
		config.SecretAccessKey,
 | 
			
		||||
		config.Region,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -99,14 +103,14 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
 | 
			
		|||
 | 
			
		||||
		if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			listCertificatesOffset += listCertificatesLimit
 | 
			
		||||
			listCertificatesPage += 1
 | 
			
		||||
			if listCertificatesPage > 99 { // 避免死循环
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 生成新证书名(需符合华为云命名规则)
 | 
			
		||||
	var certId, certName string
 | 
			
		||||
| 
						 | 
				
			
			@ -133,10 +137,7 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
 | 
			
		|||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *HuaweiCloudSCMUploader) createSdkClient() (*hcScm.ScmClient, error) {
 | 
			
		||||
	region := u.config.Region
 | 
			
		||||
	accessKeyId := u.config.AccessKeyId
 | 
			
		||||
	secretAccessKey := u.config.SecretAccessKey
 | 
			
		||||
func (u *HuaweiCloudSCMUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcScm.ScmClient, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "cn-north-4" // SCM 服务默认区域:华北四北京
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,8 +23,12 @@ type TencentCloudSSLUploader struct {
 | 
			
		|||
	sdkClient *tcSsl.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewTencentCloudSSLUploader(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
 | 
			
		||||
	client, err := (&TencentCloudSSLUploader{config: config}).createSdkClient()
 | 
			
		||||
func NewTencentCloudSSLUploader(config *TencentCloudSSLUploaderConfig) (Uploader, error) {
 | 
			
		||||
	client, err := (&TencentCloudSSLUploader{}).createSdkClient(
 | 
			
		||||
		config.Region,
 | 
			
		||||
		config.SecretId,
 | 
			
		||||
		config.SecretKey,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -73,10 +77,7 @@ func (u *TencentCloudSSLUploader) Upload(ctx context.Context, certPem string, pr
 | 
			
		|||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *TencentCloudSSLUploader) createSdkClient() (*tcSsl.Client, error) {
 | 
			
		||||
	region := u.config.Region
 | 
			
		||||
	secretId := u.config.SecretId
 | 
			
		||||
	secretKey := u.config.SecretKey
 | 
			
		||||
func (u *TencentCloudSSLUploader) createSdkClient(region, secretId, secretKey string) (*tcSsl.Client, error) {
 | 
			
		||||
	if region == "" {
 | 
			
		||||
		region = "ap-guangzhou" // SSL 服务默认区域:广州
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,8 +13,8 @@ import (
 | 
			
		|||
//   - certPem: 证书 PEM 内容。
 | 
			
		||||
//
 | 
			
		||||
// 出参:
 | 
			
		||||
//   - cert:
 | 
			
		||||
//   - err:
 | 
			
		||||
//   - cert: x509.Certificate 对象。
 | 
			
		||||
//   - err: 错误。
 | 
			
		||||
func ParseCertificateFromPEM(certPem string) (cert *x509.Certificate, err error) {
 | 
			
		||||
	pemData := []byte(certPem)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ type DeployEditContext = {
 | 
			
		|||
  deploy: DeployConfig;
 | 
			
		||||
  error: Record<string, string>;
 | 
			
		||||
  setDeploy: (deploy: DeployConfig) => void;
 | 
			
		||||
  setError: (error: Record<string, string>) => void;
 | 
			
		||||
  setError: (error: Record<string, string | undefined>) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const Context = createContext<DeployEditContext>({} as DeployEditContext);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ import DeployToTencentCDN from "./DeployToTencentCDN";
 | 
			
		|||
import DeployToTencentCLB from "./DeployToTencentCLB";
 | 
			
		||||
import DeployToTencentCOS from "./DeployToTencentCOS";
 | 
			
		||||
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
 | 
			
		||||
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
 | 
			
		||||
import DeployToQiniuCDN from "./DeployToQiniuCDN";
 | 
			
		||||
import DeployToSSH from "./DeployToSSH";
 | 
			
		||||
import DeployToWebhook from "./DeployToWebhook";
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +84,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
 | 
			
		|||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return item.configType === locDeployConfig.type.split("-")[0];
 | 
			
		||||
    return item.configType === deployTargetsMap.get(locDeployConfig.type)?.provider;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const handleSaveClick = () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +130,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
 | 
			
		|||
    case "huaweicloud-cdn":
 | 
			
		||||
      childComponent = <DeployToHuaweiCloudCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "huaweicloud-elb":
 | 
			
		||||
      childComponent = <DeployToHuaweiCloudELB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "qiniu-cdn":
 | 
			
		||||
      childComponent = <DeployToQiniuCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,17 @@ const DeployToAliyunCDN = () => {
 | 
			
		|||
 | 
			
		||||
  const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          domain: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,9 +8,22 @@ import { Label } from "@/components/ui/label";
 | 
			
		|||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunOSS = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          endpoint: "oss-cn-hangzhou.aliyuncs.com",
 | 
			
		||||
          bucket: "",
 | 
			
		||||
          domain: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
| 
						 | 
				
			
			@ -32,11 +45,11 @@ const DeployToAliyunOSS = () => {
 | 
			
		|||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const bucketResp = bucketSchema.safeParse(data.config?.domain);
 | 
			
		||||
    if (!bucketResp.success) {
 | 
			
		||||
    const resp = bucketSchema.safeParse(data.config?.bucket);
 | 
			
		||||
    if (!resp.success) {
 | 
			
		||||
      setError({
 | 
			
		||||
        ...error,
 | 
			
		||||
        bucket: JSON.parse(bucketResp.error.message)[0].message,
 | 
			
		||||
        bucket: JSON.parse(resp.error.message)[0].message,
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      setError({
 | 
			
		||||
| 
						 | 
				
			
			@ -44,35 +57,22 @@ const DeployToAliyunOSS = () => {
 | 
			
		|||
        bucket: "",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          endpoint: "oss-cn-hangzhou.aliyuncs.com",
 | 
			
		||||
          bucket: "",
 | 
			
		||||
          domain: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
    message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const bucketSchema = z.string().min(1, {
 | 
			
		||||
    message: t("domain.deployment.form.oss_bucket.placeholder"),
 | 
			
		||||
    message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.oss_endpoint.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_oss_endpoint.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.oss_endpoint.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_oss_endpoint.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.endpoint}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -91,9 +91,9 @@ const DeployToAliyunOSS = () => {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.oss_bucket.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_oss_bucket.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.oss_bucket.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_oss_bucket.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.bucket}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,18 @@ const DeployToHuaweiCloudCDN = () => {
 | 
			
		|||
 | 
			
		||||
  const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-north-1",
 | 
			
		||||
          domain: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +49,23 @@ const DeployToHuaweiCloudCDN = () => {
 | 
			
		|||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_elb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const newData = produce(data, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setDeploy(newData);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{error?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
| 
						 | 
				
			
			@ -44,26 +73,9 @@ const DeployToHuaweiCloudCDN = () => {
 | 
			
		|||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const temp = e.target.value;
 | 
			
		||||
 | 
			
		||||
            const resp = domainSchema.safeParse(temp);
 | 
			
		||||
            if (!resp.success) {
 | 
			
		||||
              setError({
 | 
			
		||||
                ...error,
 | 
			
		||||
                domain: JSON.parse(resp.error.message)[0].message,
 | 
			
		||||
              });
 | 
			
		||||
            } else {
 | 
			
		||||
              setError({
 | 
			
		||||
                ...error,
 | 
			
		||||
                domain: "",
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const newData = produce(data, (draft) => {
 | 
			
		||||
              if (!draft.config) {
 | 
			
		||||
                draft.config = {};
 | 
			
		||||
              }
 | 
			
		||||
              draft.config.domain = temp;
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setDeploy(newData);
 | 
			
		||||
          }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,190 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
const DeployToHuaweiCloudCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-north-1",
 | 
			
		||||
          resourceType: "",
 | 
			
		||||
          certificateId: "",
 | 
			
		||||
          loadbalancerId: "",
 | 
			
		||||
          listenerId: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")),
 | 
			
		||||
      resourceType: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder")),
 | 
			
		||||
      certificateId: z.string().optional(),
 | 
			
		||||
      loadbalancerId: z.string().optional(),
 | 
			
		||||
      listenerId: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "certificate" ? !!data.certificateId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder"),
 | 
			
		||||
      path: ["certificateId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "loadbalancer" ? !!data.certificateId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder"),
 | 
			
		||||
      path: ["loadbalancerId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder"),
 | 
			
		||||
      path: ["listenerId"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(data.config);
 | 
			
		||||
    if (!res.success) {
 | 
			
		||||
      setError({
 | 
			
		||||
        ...error,
 | 
			
		||||
        region: res.error.errors.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
        resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
        certificateId: res.error.errors.find((e) => e.path[0] === "certificateId")?.message,
 | 
			
		||||
        loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
        listenerId: res.error.errors.find((e) => e.path[0] === "listenerId")?.message,
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      setError({
 | 
			
		||||
        ...error,
 | 
			
		||||
        region: undefined,
 | 
			
		||||
        resourceType: undefined,
 | 
			
		||||
        certificateId: undefined,
 | 
			
		||||
        loadbalancerId: undefined,
 | 
			
		||||
        listenerId: undefined,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_elb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const newData = produce(data, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setDeploy(newData);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{error?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_elb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={data?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const newData = produce(data, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setDeploy(newData);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="certificate">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{error?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {data?.config?.resourceType === "certificate" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_certificate_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={data?.config?.certificateId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const newData = produce(data, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.certificateId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setDeploy(newData);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{error?.certificateId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {data?.config?.resourceType === "loadbalancer" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={data?.config?.loadbalancerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const newData = produce(data, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setDeploy(newData);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{error?.loadbalancerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {data?.config?.resourceType === "listener" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_listener_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={data?.config?.listenerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const newData = produce(data, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setDeploy(newData);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{error?.listenerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToHuaweiCloudCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -8,13 +8,8 @@ import { useDeployEditContext } from "./DeployEdit";
 | 
			
		|||
 | 
			
		||||
const DeployToKubernetesSecret = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const { deploy: data, setDeploy } = useDeployEditContext();
 | 
			
		||||
  const { deploy: data, setDeploy, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +25,10 @@ const DeployToKubernetesSecret = () => {
 | 
			
		|||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex flex-col space-y-8">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,17 @@ const DeployToQiniuCDN = () => {
 | 
			
		|||
 | 
			
		||||
  const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
        ...data,
 | 
			
		||||
        config: {
 | 
			
		||||
          domain: "",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setError({});
 | 
			
		||||
  }, []);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,7 +76,6 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      setDeploy({
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +91,7 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const regionSchema = z.string().regex(/^ap-[a-z]+$/, {
 | 
			
		||||
    message: t("domain.deployment.form.clb_region.placeholder"),
 | 
			
		||||
    message: t("domain.deployment.form.tencent_clb_region.placeholder"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const domainSchema = z.string().regex(/^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
| 
						 | 
				
			
			@ -100,19 +99,19 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
  });
 | 
			
		||||
 | 
			
		||||
  const clbIdSchema = z.string().regex(/^lb-[a-zA-Z0-9]{8}$/, {
 | 
			
		||||
    message: t("domain.deployment.form.clb_id.placeholder"),
 | 
			
		||||
    message: t("domain.deployment.form.tencent_clb_id.placeholder"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const lsnIdSchema = z.string().regex(/^lbl-.{8}$/, {
 | 
			
		||||
    message: t("domain.deployment.form.clb_listener.placeholder"),
 | 
			
		||||
    message: t("domain.deployment.form.tencent_clb_listener.placeholder"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.clb_region.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.clb_region.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -144,9 +143,9 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.clb_id.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_id.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.clb_id.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_id.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.clbId}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -178,9 +177,9 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.clb_listener.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_listener.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.clb_listener.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_listener.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.lsnId}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -212,9 +211,9 @@ const DeployToTencentCLB = () => {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.clb_domain.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.clb_domain.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,9 +89,9 @@ const DeployToTencentCOS = () => {
 | 
			
		|||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.cos_region.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_cos_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.cos_region.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_cos_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -123,9 +123,9 @@ const DeployToTencentCOS = () => {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.cos_bucket.label")}</Label>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_cos_bucket.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.cos_bucket.placeholder")}
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_cos_bucket.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={data?.config?.bucket}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,6 +65,7 @@ export type Statistic = {
 | 
			
		|||
 | 
			
		||||
type DeployTarget = {
 | 
			
		||||
  type: string;
 | 
			
		||||
  provider: string;
 | 
			
		||||
  name: string;
 | 
			
		||||
  icon: string;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -78,10 +79,11 @@ export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map
 | 
			
		|||
    ["tencent-clb", "common.provider.tencent.clb", "/imgs/providers/tencent.svg"],
 | 
			
		||||
    ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"],
 | 
			
		||||
    ["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
 | 
			
		||||
    ["huaweicloud-elb", "common.provider.huaweicloud.elb", "/imgs/providers/huaweicloud.svg"],
 | 
			
		||||
    ["qiniu-cdn", "common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"],
 | 
			
		||||
    ["local", "common.provider.local", "/imgs/providers/local.svg"],
 | 
			
		||||
    ["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
 | 
			
		||||
    ["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
 | 
			
		||||
    ["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
 | 
			
		||||
  ].map(([type, name, icon]) => [type, { type, name, icon }])
 | 
			
		||||
  ].map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,14 +56,15 @@
 | 
			
		|||
  "common.provider.aliyun.oss": "Alibaba Cloud - OSS",
 | 
			
		||||
  "common.provider.aliyun.cdn": "Alibaba Cloud - CDN",
 | 
			
		||||
  "common.provider.aliyun.dcdn": "Alibaba Cloud - DCDN",
 | 
			
		||||
  "common.provider.tencent": "Tencent",
 | 
			
		||||
  "common.provider.tencent.cdn": "Tencent - CDN",
 | 
			
		||||
  "common.provider.tencent.clb": "Tencent - CLB",
 | 
			
		||||
  "common.provider.tencent.cos": "Tencent - COS",
 | 
			
		||||
  "common.provider.tencent": "Tencent Cloud",
 | 
			
		||||
  "common.provider.tencent.cdn": "Tencent Cloud - CDN",
 | 
			
		||||
  "common.provider.tencent.clb": "Tencent Cloud - CLB",
 | 
			
		||||
  "common.provider.tencent.cos": "Tencent Cloud - COS",
 | 
			
		||||
  "common.provider.huaweicloud": "Huawei Cloud",
 | 
			
		||||
  "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
 | 
			
		||||
  "common.provider.qiniu": "Qiniu",
 | 
			
		||||
  "common.provider.qiniu.cdn": "Qiniu - CDN",
 | 
			
		||||
  "common.provider.huaweicloud.elb": "Huawei Cloud - ELB",
 | 
			
		||||
  "common.provider.qiniu": "Qiniu Cloud",
 | 
			
		||||
  "common.provider.qiniu.cdn": "Qiniu Cloud - CDN",
 | 
			
		||||
  "common.provider.aws": "AWS",
 | 
			
		||||
  "common.provider.cloudflare": "Cloudflare",
 | 
			
		||||
  "common.provider.namesilo": "Namesilo",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,21 +54,38 @@
 | 
			
		|||
  "domain.deployment.form.access.label": "Access Configuration",
 | 
			
		||||
  "domain.deployment.form.access.placeholder": "Please select provider authorization configuration",
 | 
			
		||||
  "domain.deployment.form.access.list": "Provider Authorization Configurations",
 | 
			
		||||
  "domain.deployment.form.cos_region.label": "Region",
 | 
			
		||||
  "domain.deployment.form.cos_region.placeholder": "Please enter region, e.g. ap-guangzhou",
 | 
			
		||||
  "domain.deployment.form.cos_bucket.label": "Bucket",
 | 
			
		||||
  "domain.deployment.form.cos_bucket.placeholder": "Please enter bucket, e.g. example-1250000000",
 | 
			
		||||
  "domain.deployment.form.clb_region.label": "region(please distinguish between region and availability zone)",
 | 
			
		||||
  "domain.deployment.form.clb_region.placeholder": "Please enter region, e.g. ap-guangzhou",
 | 
			
		||||
  "domain.deployment.form.clb_id.label": "CLB id",
 | 
			
		||||
  "domain.deployment.form.clb_id.placeholder": "Please enter CLB id, e.g. lb-xxxxxxxx",
 | 
			
		||||
  "domain.deployment.form.clb_listener.label": "Listener id",
 | 
			
		||||
  "domain.deployment.form.clb_listener.placeholder": "Please enter listener id, e.g. lbl-xxxxxxxx. The specific listener should have set the corresponding domain HTTPS forwarding, and the original certificate domain should be consistent with the certificate to be deployed.",
 | 
			
		||||
  "domain.deployment.form.clb_domain.label": "Deploy to domain (Wildcard domain is also supported)",
 | 
			
		||||
  "domain.deployment.form.clb_domain.placeholder": "Please enter domain to be deployed. If SNI is not enabled, you can leave it blank.",
 | 
			
		||||
  "domain.deployment.form.domain.label": "Deploy to domain (Single domain only, not wildcard domain)",
 | 
			
		||||
  "domain.deployment.form.domain.label.wildsupported": "Deploy to domain (Wildcard domain is also supported)",
 | 
			
		||||
  "domain.deployment.form.domain.placeholder": "Please enter domain to be deployed",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_endpoint.label": "Endpoint",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_endpoint.placeholder": "Please enter endpoint",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_bucket.label": "Bucket",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_bucket.placeholder": "Please enter bucket",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_region.label": "Region",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_bucket.label": "Bucket",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_bucket.placeholder": "Please enter bucket",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_region.label": "Region",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_id.label": "CLB ID",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_id.placeholder": "Please enter CLB ID (e.g. lb-xxxxxxxx)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_listener.label": "Listener ID",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_listener.placeholder": "Please enter listener ID (e.g. lbl-xxxxxxxx). The specific listener should have set the corresponding domain HTTPS forwarding, and the original certificate domain should be consistent with the certificate to be deployed.",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_domain.label": "Deploy to domain (Wildcard domain is also supported)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_domain.placeholder": "Please enter domain to be deployed. If SNI is not enabled, you can leave it blank.",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_region.label": "Region",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_region.placeholder": "Please enter region (e.g. cn-north-1)",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.label": "Resource Type",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.placeholder": "Please select ELB resource type",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label": "ELB Certificate",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "ELB LoadBalancer",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label": "ELB Listener",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_certificate_id.label": "Certificate ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_certificate_id.placeholder": "Please enter ELB certificate ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_loadbalancer_id.label": "LoadBalancer ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder": "Please enter ELB loadbalancer ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_listener_id.label": "Listener ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_listener_id.placeholder": "Please enter ELB listener ID",
 | 
			
		||||
  "domain.deployment.form.ssh_key_path.label": "Private Key Save Path",
 | 
			
		||||
  "domain.deployment.form.ssh_key_path.placeholder": "Please enter private key save path",
 | 
			
		||||
  "domain.deployment.form.ssh_cert_path.label": "Certificate Save Path",
 | 
			
		||||
| 
						 | 
				
			
			@ -77,10 +94,6 @@
 | 
			
		|||
  "domain.deployment.form.ssh_pre_command.placeholder": "Command to be executed before deploying the certificate",
 | 
			
		||||
  "domain.deployment.form.ssh_command.label": "Command",
 | 
			
		||||
  "domain.deployment.form.ssh_command.placeholder": "Please enter command",
 | 
			
		||||
  "domain.deployment.form.oss_endpoint.label": "Endpoint",
 | 
			
		||||
  "domain.deployment.form.oss_endpoint.placeholder": "Please enter endpoint",
 | 
			
		||||
  "domain.deployment.form.oss_bucket.label": "Bucket",
 | 
			
		||||
  "domain.deployment.form.oss_bucket.placeholder": "Please enter bucket",
 | 
			
		||||
  "domain.deployment.form.k8s_namespace.label": "Namespace",
 | 
			
		||||
  "domain.deployment.form.k8s_namespace.placeholder": "Please enter namespace",
 | 
			
		||||
  "domain.deployment.form.k8s_secret_name.label": "Secret Name",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,17 +51,17 @@
 | 
			
		|||
  "common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
 | 
			
		||||
  "common.errmsg.ip_invalid": "请输入正确的 IP 地址",
 | 
			
		||||
  "common.errmsg.url_invalid": "请输入正确的 URL",
 | 
			
		||||
 | 
			
		||||
  "common.provider.tencent": "腾讯云",
 | 
			
		||||
  "common.provider.tencent.cdn": "腾讯云 - CDN",
 | 
			
		||||
  "common.provider.tencent.clb": "腾讯云 - CLB",
 | 
			
		||||
  "common.provider.tencent.cos": "腾讯云 - COS",
 | 
			
		||||
  "common.provider.aliyun": "阿里云",
 | 
			
		||||
  "common.provider.aliyun.oss": "阿里云 - OSS",
 | 
			
		||||
  "common.provider.aliyun.cdn": "阿里云 - CDN",
 | 
			
		||||
  "common.provider.aliyun.dcdn": "阿里云 - DCDN",
 | 
			
		||||
  "common.provider.tencent": "腾讯云",
 | 
			
		||||
  "common.provider.tencent.cos": "腾讯云 - COS",
 | 
			
		||||
  "common.provider.tencent.cdn": "腾讯云 - CDN",
 | 
			
		||||
  "common.provider.tencent.clb": "腾讯云 - CLB",
 | 
			
		||||
  "common.provider.huaweicloud": "华为云",
 | 
			
		||||
  "common.provider.huaweicloud.cdn": "华为云 - CDN",
 | 
			
		||||
  "common.provider.huaweicloud.elb": "华为云 - ELB",
 | 
			
		||||
  "common.provider.qiniu": "七牛云",
 | 
			
		||||
  "common.provider.qiniu.cdn": "七牛云 - CDN",
 | 
			
		||||
  "common.provider.aws": "AWS",
 | 
			
		||||
| 
						 | 
				
			
			@ -79,4 +79,3 @@
 | 
			
		|||
  "common.provider.telegram": "Telegram",
 | 
			
		||||
  "common.provider.lark": "飞书"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,21 +54,38 @@
 | 
			
		|||
  "domain.deployment.form.access.label": "授权配置",
 | 
			
		||||
  "domain.deployment.form.access.placeholder": "请选择授权配置",
 | 
			
		||||
  "domain.deployment.form.access.list": "服务商授权配置列表",
 | 
			
		||||
  "domain.deployment.form.cos_region.label": "region",
 | 
			
		||||
  "domain.deployment.form.cos_region.placeholder": "请输入 region, 如 ap-guangzhou",
 | 
			
		||||
  "domain.deployment.form.cos_bucket.label": "存储桶",
 | 
			
		||||
  "domain.deployment.form.cos_bucket.placeholder": "请输入存储桶名, 如 example-1250000000",
 | 
			
		||||
  "domain.deployment.form.clb_region.label": "region(地域, 请准确区分地域和可用区)",
 | 
			
		||||
  "domain.deployment.form.clb_region.placeholder": "请输入 region, 如 ap-guangzhou",
 | 
			
		||||
  "domain.deployment.form.clb_id.label": "CLB id",
 | 
			
		||||
  "domain.deployment.form.clb_id.placeholder": "请输入CLB实例id, 如 lb-xxxxxxxx",
 | 
			
		||||
  "domain.deployment.form.clb_listener.label": "监听器 id(对应监听器应已设置对应域名HTTPS转发, 且原证书对应域名应与待部署证书的一致)",
 | 
			
		||||
  "domain.deployment.form.clb_listener.placeholder": "请输入监听器id, 如 lbl-xxxxxxxx",
 | 
			
		||||
  "domain.deployment.form.clb_domain.label": "部署到域名(支持泛域名)",
 | 
			
		||||
  "domain.deployment.form.clb_domain.placeholder": "请输入部署到的域名, 如未开启SNI, 可置空忽略此项",
 | 
			
		||||
  "domain.deployment.form.domain.label": "部署到域名(仅支持单个域名;不支持泛域名)",
 | 
			
		||||
  "domain.deployment.form.domain.label.wildsupported": "部署到域名(支持泛域名)",
 | 
			
		||||
  "domain.deployment.form.domain.placeholder": "请输入部署到的域名",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_endpoint.label": "Endpoint",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_endpoint.placeholder": "请输入 Endpoint",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_bucket.label": "存储桶",
 | 
			
		||||
  "domain.deployment.form.aliyun_oss_bucket.placeholder": "请输入存储桶名",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_region.label": "地域",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_region.placeholder": "请输入地域(如 ap-guangzhou)",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_bucket.label": "存储桶",
 | 
			
		||||
  "domain.deployment.form.tencent_cos_bucket.placeholder": "请输入存储桶名",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_region.label": "地域",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_region.placeholder": "请输入地域(如 ap-guangzhou)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_id.label": "负载均衡器 ID",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_id.placeholder": "请输入负载均衡器实例 ID(如 lb-xxxxxxxx)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_listener.label": "监听器 ID(对应监听器应已设置对应域名 HTTPS 转发, 且原证书对应域名应与待部署证书的一致)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_listener.placeholder": "请输入监听器 ID(如 lb-xxxxxxxx)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_domain.label": "部署到域名(支持泛域名)",
 | 
			
		||||
  "domain.deployment.form.tencent_clb_domain.placeholder": "请输入部署到的域名, 如未开启 SNI, 可置空忽略此项",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_region.label": "地域",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_region.placeholder": "请输入地域(如 cn-north-1)",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.label": "资源类型替换方式",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.placeholder": "请选择资源类型替换方式",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label": "按证书替换",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "按负载均衡器替换",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label": "按监听器替换",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_certificate_id.label": "证书 ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_certificate_id.placeholder": "请输入证书 ID(可从华为云控制面板获取)",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_loadbalancer_id.label": "负载均衡器 ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder": "请输入负载均衡器 ID(可从华为云控制面板获取)",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_listener_id.label": "监听器 ID",
 | 
			
		||||
  "domain.deployment.form.huaweicloud_elb_listener_id.placeholder": "请输入监听器 ID(可从华为云控制面板获取)",
 | 
			
		||||
  "domain.deployment.form.ssh_key_path.label": "私钥保存路径",
 | 
			
		||||
  "domain.deployment.form.ssh_key_path.placeholder": "请输入私钥保存路径",
 | 
			
		||||
  "domain.deployment.form.ssh_cert_path.label": "证书保存路径",
 | 
			
		||||
| 
						 | 
				
			
			@ -77,10 +94,6 @@
 | 
			
		|||
  "domain.deployment.form.ssh_pre_command.placeholder": "在部署证书前执行的命令",
 | 
			
		||||
  "domain.deployment.form.ssh_command.label": "命令",
 | 
			
		||||
  "domain.deployment.form.ssh_command.placeholder": "请输入要执行的命令",
 | 
			
		||||
  "domain.deployment.form.oss_endpoint.label": "Endpoint",
 | 
			
		||||
  "domain.deployment.form.oss_endpoint.placeholder": "请输入 Endpoint",
 | 
			
		||||
  "domain.deployment.form.oss_bucket.label": "存储桶",
 | 
			
		||||
  "domain.deployment.form.oss_bucket.placeholder": "请输入存储桶名",
 | 
			
		||||
  "domain.deployment.form.k8s_namespace.label": "命名空间",
 | 
			
		||||
  "domain.deployment.form.k8s_namespace.placeholder": "请输入 K8S 命名空间",
 | 
			
		||||
  "domain.deployment.form.k8s_secret_name.label": "Secret 名称",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue