From dc720a5d999b5fd2656487b8b8700a3563c87e2a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 24 Oct 2024 22:37:55 +0800 Subject: [PATCH] feat: add huaweicloud elb deployer --- internal/deployer/deployer.go | 3 + internal/deployer/huaweicloud_cdn.go | 4 +- internal/deployer/huaweicloud_elb.go | 365 ++++++++++++++++++ .../core/uploader/uploader_huaweicloud_elb.go | 64 ++- .../core/uploader/uploader_huaweicloud_scm.go | 6 +- .../components/certimate/DeployEditDialog.tsx | 6 +- .../certimate/DeployToHuaweiCloudCDN.tsx | 37 +- .../certimate/DeployToHuaweiCloudELB.tsx | 190 +++++++++ ui/src/domain/domain.ts | 4 +- ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/en/nls.domain.json | 11 + ui/src/i18n/locales/zh/nls.common.json | 8 +- ui/src/i18n/locales/zh/nls.domain.json | 11 + 13 files changed, 673 insertions(+), 37 deletions(-) create mode 100644 internal/deployer/huaweicloud_elb.go create mode 100644 ui/src/components/certimate/DeployToHuaweiCloudELB.tsx diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 9d296065..8ae0014a 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -21,6 +21,7 @@ const ( targetTencentCDN = "tencent-cdn" targetTencentCOS = "tencent-cos" targetHuaweiCloudCDN = "huaweicloud-cdn" + targetHuaweiCloudELB = "huaweicloud-elb" targetQiniuCdn = "qiniu-cdn" targetLocal = "local" targetSSH = "ssh" @@ -110,6 +111,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: diff --git a/internal/deployer/huaweicloud_cdn.go b/internal/deployer/huaweicloud_cdn.go index bf87fb89..f7835dcb 100644 --- a/internal/deployer/huaweicloud_cdn.go +++ b/internal/deployer/huaweicloud_cdn.go @@ -31,9 +31,9 @@ func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) { } client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient( - option.DeployConfig.GetConfigAsString("region"), access.AccessKeyId, access.SecretAccessKey, + option.DeployConfig.GetConfigAsString("region"), ) if err != nil { return nil, err @@ -119,7 +119,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error { return nil } -func (d *HuaweiCloudCDNDeployer) createSdkClient(region, accessKeyId, secretAccessKey string) (*hcCdn.CdnClient, error) { +func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdn.CdnClient, error) { if region == "" { region = "cn-north-1" // CDN 服务默认区域:华北一北京 } diff --git a/internal/deployer/huaweicloud_elb.go b/internal/deployer/huaweicloud_elb.go new file mode 100644 index 00000000..e9a6f243 --- /dev/null +++ b/internal/deployer/huaweicloud_elb.go @@ -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 +} diff --git a/internal/pkg/core/uploader/uploader_huaweicloud_elb.go b/internal/pkg/core/uploader/uploader_huaweicloud_elb.go index 5eb60d88..090362af 100644 --- a/internal/pkg/core/uploader/uploader_huaweicloud_elb.go +++ b/internal/pkg/core/uploader/uploader_huaweicloud_elb.go @@ -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 { @@ -28,9 +31,9 @@ type HuaweiCloudELBUploader struct { func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (Uploader, error) { client, err := (&HuaweiCloudELBUploader{}).createSdkClient( - config.Region, config.AccessKeyId, config.SecretAccessKey, + config.Region, ) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) @@ -100,6 +103,13 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri } } + // 获取项目 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 certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) @@ -109,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), @@ -129,7 +139,7 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri }, nil } -func (u *HuaweiCloudELBUploader) createSdkClient(region, accessKeyId, secretAccessKey string) (*hcElb.ElbClient, error) { +func (u *HuaweiCloudELBUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) { if region == "" { region = "cn-north-4" // ELB 服务默认区域:华北四北京 } @@ -158,3 +168,47 @@ func (u *HuaweiCloudELBUploader) createSdkClient(region, accessKeyId, secretAcce 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 +} diff --git a/internal/pkg/core/uploader/uploader_huaweicloud_scm.go b/internal/pkg/core/uploader/uploader_huaweicloud_scm.go index 30864a48..2b09ca19 100644 --- a/internal/pkg/core/uploader/uploader_huaweicloud_scm.go +++ b/internal/pkg/core/uploader/uploader_huaweicloud_scm.go @@ -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 { @@ -27,9 +27,9 @@ type HuaweiCloudSCMUploader struct { func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (Uploader, error) { client, err := (&HuaweiCloudSCMUploader{}).createSdkClient( - config.Region, config.AccessKeyId, config.SecretAccessKey, + config.Region, ) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) @@ -137,7 +137,7 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri }, nil } -func (u *HuaweiCloudSCMUploader) createSdkClient(region, accessKeyId, secretAccessKey string) (*hcScm.ScmClient, error) { +func (u *HuaweiCloudSCMUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcScm.ScmClient, error) { if region == "" { region = "cn-north-4" // SCM 服务默认区域:华北四北京 } diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx index 54710e49..974dc590 100644 --- a/ui/src/components/certimate/DeployEditDialog.tsx +++ b/ui/src/components/certimate/DeployEditDialog.tsx @@ -14,6 +14,7 @@ import DeployToAliyunCDN from "./DeployToAliyunCDN"; import DeployToTencentCDN from "./DeployToTencentCDN"; import DeployToTencentCOS from "./DeployToTencentCOS"; import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN"; +import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB"; import DeployToQiniuCDN from "./DeployToQiniuCDN"; import DeployToSSH from "./DeployToSSH"; import DeployToWebhook from "./DeployToWebhook"; @@ -82,7 +83,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 = () => { @@ -125,6 +126,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro case "huaweicloud-cdn": childComponent = ; break; + case "huaweicloud-elb": + childComponent = ; + break; case "qiniu-cdn": childComponent = ; break; diff --git a/ui/src/components/certimate/DeployToHuaweiCloudCDN.tsx b/ui/src/components/certimate/DeployToHuaweiCloudCDN.tsx index 738fd4a9..bdf968c4 100644 --- a/ui/src/components/certimate/DeployToHuaweiCloudCDN.tsx +++ b/ui/src/components/certimate/DeployToHuaweiCloudCDN.tsx @@ -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({}); }, []); @@ -46,12 +58,12 @@ const DeployToHuaweiCloudCDN = () => { onChange={(e) => { const newData = produce(data, (draft) => { draft.config ??= {}; - draft.config.region = e.target.value; + draft.config.region = e.target.value?.trim(); }); setDeploy(newData); }} /> -
{error?.domain}
+
{error?.region}
@@ -61,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); }} diff --git a/ui/src/components/certimate/DeployToHuaweiCloudELB.tsx b/ui/src/components/certimate/DeployToHuaweiCloudELB.tsx new file mode 100644 index 00000000..9cb5e686 --- /dev/null +++ b/ui/src/components/certimate/DeployToHuaweiCloudELB.tsx @@ -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 ( +
+
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.region = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.region}
+
+ +
+ + +
{error?.resourceType}
+
+ + {data?.config?.resourceType === "certificate" ? ( +
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.certificateId = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.certificateId}
+
+ ) : ( + <> + )} + + {data?.config?.resourceType === "loadbalancer" ? ( +
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.loadbalancerId = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.loadbalancerId}
+
+ ) : ( + <> + )} + + {data?.config?.resourceType === "listener" ? ( +
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.listenerId = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.listenerId}
+
+ ) : ( + <> + )} +
+ ); +}; + +export default DeployToHuaweiCloudCDN; diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index 350f89a4..f8bfc691 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -65,6 +65,7 @@ export type Statistic = { type DeployTarget = { type: string; + provider: string; name: string; icon: string; }; @@ -77,10 +78,11 @@ export const deployTargetsMap: Map = new Map ["tencent-cdn", "common.provider.tencent.cdn", "/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 }]) ); diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index d3a17019..cf22bdd1 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -61,6 +61,7 @@ "common.provider.tencent.cos": "Tencent - COS", "common.provider.huaweicloud": "Huawei Cloud", "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN", + "common.provider.huaweicloud.elb": "Huawei Cloud - ELB", "common.provider.qiniu": "Qiniu", "common.provider.qiniu.cdn": "Qiniu - CDN", "common.provider.aws": "AWS", diff --git a/ui/src/i18n/locales/en/nls.domain.json b/ui/src/i18n/locales/en/nls.domain.json index 4bd4b837..3a5d5ba2 100644 --- a/ui/src/i18n/locales/en/nls.domain.json +++ b/ui/src/i18n/locales/en/nls.domain.json @@ -66,6 +66,17 @@ "domain.deployment.form.tencent_cos_bucket.placeholder": "Please enter bucket", "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", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 212d7b2e..8a2431a4 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -52,15 +52,16 @@ "common.errmsg.ip_invalid": "请输入正确的 IP 地址", "common.errmsg.url_invalid": "请输入正确的 URL", - "common.provider.tencent": "腾讯云", - "common.provider.tencent.cdn": "腾讯云 - CDN", - "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.cdn": "腾讯云 - CDN", + "common.provider.tencent.cos": "腾讯云 - COS", "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", @@ -78,4 +79,3 @@ "common.provider.telegram": "Telegram", "common.provider.lark": "飞书" } - diff --git a/ui/src/i18n/locales/zh/nls.domain.json b/ui/src/i18n/locales/zh/nls.domain.json index 9a670d1d..78d77319 100644 --- a/ui/src/i18n/locales/zh/nls.domain.json +++ b/ui/src/i18n/locales/zh/nls.domain.json @@ -66,6 +66,17 @@ "domain.deployment.form.tencent_cos_bucket.placeholder": "请输入存储桶名", "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": "证书保存路径",