From 898311c6e52d8b5af9b3e6fa5584adc57d719b0c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 14 Jun 2025 21:37:18 +0800 Subject: [PATCH] feat: new deployment provider: ctcccloud elb --- internal/deployer/providers.go | 14 +- internal/domain/provider.go | 2 +- .../providers/ctcccloud-elb/consts.go | 10 + .../providers/ctcccloud-elb/ctcccloud_elb.go | 199 ++++++++++++++++++ .../ctcccloud-elb/ctcccloud_elb_test.go | 118 +++++++++++ .../providers/ctcccloud-elb/ctcccloud_elb.go | 133 ++++++++++++ .../ctcccloud-elb/ctcccloud_elb_test.go | 77 +++++++ .../ctyun/elb/api_create_certificate.go | 45 ++++ .../sdk3rd/ctyun/elb/api_list_certificate.go | 56 +++++ .../pkg/sdk3rd/ctyun/elb/api_list_listener.go | 64 ++++++ .../pkg/sdk3rd/ctyun/elb/api_show_listener.go | 48 +++++ .../sdk3rd/ctyun/elb/api_update_listener.go | 44 ++++ internal/pkg/sdk3rd/ctyun/elb/client.go | 50 +++++ internal/pkg/sdk3rd/ctyun/elb/types.go | 104 +++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...DeployNodeConfigFormCTCCCloudELBConfig.tsx | 121 +++++++++++ ui/src/domain/provider.ts | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 14 ++ .../i18n/locales/zh/nls.workflow.nodes.json | 13 ++ 19 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/ctcccloud-elb/consts.go create mode 100644 internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb.go create mode 100644 internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb_test.go create mode 100644 internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb.go create mode 100644 internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb_test.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/api_create_certificate.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/api_list_certificate.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/api_list_listener.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/api_show_listener.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/api_update_listener.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/client.go create mode 100644 internal/pkg/sdk3rd/ctyun/elb/types.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormCTCCCloudELBConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index f905285d..8479cade 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -46,6 +46,7 @@ import ( pCTCCCloudAO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-ao" pCTCCCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-cdn" pCTCCCloudCMS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-cms" + pCTCCCloudELB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-elb" pCTCCCloudICDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-icdn" pCTCCCloudLVDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-lvdn" pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" @@ -626,7 +627,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return deployer, err } - case domain.DeploymentProviderTypeCTCCCloudAO, domain.DeploymentProviderTypeCTCCCloudCDN, domain.DeploymentProviderTypeCTCCCloudCMS, domain.DeploymentProviderTypeCTCCCloudICDN, domain.DeploymentProviderTypeCTCCCloudLVDN: + case domain.DeploymentProviderTypeCTCCCloudAO, domain.DeploymentProviderTypeCTCCCloudCDN, domain.DeploymentProviderTypeCTCCCloudCMS, domain.DeploymentProviderTypeCTCCCloudELB, domain.DeploymentProviderTypeCTCCCloudICDN, domain.DeploymentProviderTypeCTCCCloudLVDN: { access := domain.AccessConfigForCTCCCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -657,6 +658,17 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer }) return deployer, err + case domain.DeploymentProviderTypeCTCCCloudELB: + deployer, err := pCTCCCloudELB.NewDeployer(&pCTCCCloudELB.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"), + ResourceType: pCTCCCloudELB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + }) + return deployer, err + case domain.DeploymentProviderTypeCTCCCloudICDN: deployer, err := pCTCCCloudICDN.NewDeployer(&pCTCCCloudICDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 924f58bd..47ba0e72 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -218,7 +218,7 @@ const ( DeploymentProviderTypeCTCCCloudAO = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-ao") DeploymentProviderTypeCTCCCloudCDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-cdn") DeploymentProviderTypeCTCCCloudCMS = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-cms") - DeploymentProviderTypeCTCCCloudELB = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-elb") // (预留) + DeploymentProviderTypeCTCCCloudELB = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-elb") DeploymentProviderTypeCTCCCloudICDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-icdn") DeploymentProviderTypeCTCCCloudLVDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-ldvn") DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") diff --git a/internal/pkg/core/deployer/providers/ctcccloud-elb/consts.go b/internal/pkg/core/deployer/providers/ctcccloud-elb/consts.go new file mode 100644 index 00000000..263e66ed --- /dev/null +++ b/internal/pkg/core/deployer/providers/ctcccloud-elb/consts.go @@ -0,0 +1,10 @@ +package ctcccloudelb + +type ResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + RESOURCE_TYPE_LISTENER = ResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb.go b/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb.go new file mode 100644 index 00000000..f1dfb3f6 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb.go @@ -0,0 +1,199 @@ +package ctcccloudelb + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ctcccloud-elb" + ctyunelb "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/elb" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // 天翼云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 天翼云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 天翼云资源池 ID。 + RegionId string `json:"regionId"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听器 ID。 + // 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *ctyunelb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + RegionId: config.RegionId, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到 ELB + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case RESOURCE_TYPE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + // 查询监听列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5654&data=88&isNormal=1&vid=82 + listenerIds := make([]string, 0) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + listListenerReq := &ctyunelb.ListListenerRequest{ + RegionID: typeutil.ToPtr(d.config.RegionId), + LoadBalancerID: typeutil.ToPtr(d.config.LoadbalancerId), + } + listListenerResp, err := d.sdkClient.ListListener(listListenerReq) + d.logger.Debug("sdk request 'elb.ListListener'", slog.Any("request", listListenerReq), slog.Any("response", listListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'elb.ListListener': %w", err) + } + + for _, listener := range listListenerResp.ReturnObj { + if strings.EqualFold(listener.Protocol, "HTTPS") { + listenerIds = append(listenerIds, listener.ID) + } + } + + break + } + + // 遍历更新监听证书 + if len(listenerIds) == 0 { + d.logger.Info("no elb listeners to deploy") + } else { + d.logger.Info("found https listeners to deploy", slog.Any("listenerIds", listenerIds)) + var errs []error + + for _, listenerId := range listenerIds { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 更新监听器 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5652&data=88&isNormal=1&vid=82 + setLoadBalancerHTTPSListenerAttributeReq := &ctyunelb.UpdateListenerRequest{ + RegionID: typeutil.ToPtr(d.config.RegionId), + ListenerID: typeutil.ToPtr(cloudListenerId), + CertificateID: typeutil.ToPtr(cloudCertId), + } + setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.UpdateListener(setLoadBalancerHTTPSListenerAttributeReq) + d.logger.Debug("sdk request 'elb.UpdateListener'", slog.Any("request", setLoadBalancerHTTPSListenerAttributeReq), slog.Any("response", setLoadBalancerHTTPSListenerAttributeResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'elb.UpdateListener': %w", err) + } + + return nil +} + +func createSdkClient(accessKeyId, secretAccessKey string) (*ctyunelb.Client, error) { + return ctyunelb.NewClient(accessKeyId, secretAccessKey) +} diff --git a/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb_test.go b/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb_test.go new file mode 100644 index 00000000..86a23a2f --- /dev/null +++ b/internal/pkg/core/deployer/providers/ctcccloud-elb/ctcccloud_elb_test.go @@ -0,0 +1,118 @@ +package ctcccloudelb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ctcccloud-elb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegionId string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_CTCCCLOUDELB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fRegionId, argsPrefix+"REGIONID", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ctcccloud_elb_test.go -args \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_REGIONID="your-region-id" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_LOADBALANCERID="your-elb-instance-id" \ + --CERTIMATE_DEPLOYER_CTCCCLOUDELB_LISTENERID="your-elb-listener-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGIONID: %v", fRegionId), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + RegionId: fRegionId, + ResourceType: provider.RESOURCE_TYPE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGIONID: %v", fRegionId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + RegionId: fRegionId, + ResourceType: provider.RESOURCE_TYPE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb.go b/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb.go new file mode 100644 index 00000000..f6fa16c9 --- /dev/null +++ b/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb.go @@ -0,0 +1,133 @@ +package ctcccloudelb + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/google/uuid" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + ctyunelb "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/elb" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type UploaderConfig struct { + // 天翼云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 天翼云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 天翼云资源池 ID。 + RegionId string `json:"regionId"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *ctyunelb.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &UploaderProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader { + if logger == nil { + u.logger = slog.New(slog.DiscardHandler) + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*uploader.UploadResult, error) { + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + // 查询证书列表,避免重复上传 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5692&data=88&isNormal=1&vid=82 + listCertificateReq := &ctyunelb.ListCertificateRequest{ + RegionID: typeutil.ToPtr(u.config.RegionId), + } + listCertificateResp, err := u.sdkClient.ListCertificate(listCertificateReq) + u.logger.Debug("sdk request 'elb.ListCertificate'", slog.Any("request", listCertificateReq), slog.Any("response", listCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificate': %w", err) + } else { + for _, certRecord := range listCertificateResp.ReturnObj { + var isSameCert bool + if certRecord.Certificate == certPEM { + isSameCert = true + } else { + oldCertX509, err := certutil.ParseCertificateFromPEM(certRecord.Certificate) + if err != nil { + continue + } + + isSameCert = certutil.EqualCertificate(certX509, oldCertX509) + } + + // 如果已存在相同证书,直接返回 + if isSameCert { + u.logger.Info("ssl certificate already exists") + return &uploader.UploadResult{ + CertId: certRecord.ID, + CertName: certRecord.Name, + }, nil + } + } + } + + // 生成新证书名(需符合天翼云命名规则) + certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + + // 创建证书 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5685&data=88&isNormal=1&vid=82 + createCertificateReq := &ctyunelb.CreateCertificateRequest{ + ClientToken: typeutil.ToPtr(generateClientToken()), + RegionID: typeutil.ToPtr(u.config.RegionId), + Name: typeutil.ToPtr(certName), + Description: typeutil.ToPtr("upload from certimate"), + Type: typeutil.ToPtr("Server"), + Certificate: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + } + createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq) + u.logger.Debug("sdk request 'elb.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'elb.CreateCertificate': %w", err) + } + + return &uploader.UploadResult{ + CertId: createCertificateResp.ReturnObj.ID, + CertName: certName, + }, nil +} + +func createSdkClient(accessKeyId, secretAccessKey string) (*ctyunelb.Client, error) { + return ctyunelb.NewClient(accessKeyId, secretAccessKey) +} + +func generateClientToken() string { + return uuid.New().String() +} diff --git a/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb_test.go b/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb_test.go new file mode 100644 index 00000000..a3c1c752 --- /dev/null +++ b/internal/pkg/core/uploader/providers/ctcccloud-elb/ctcccloud_elb_test.go @@ -0,0 +1,77 @@ +package ctcccloudelb_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ctcccloud-elb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegionId string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_CTCCCLOUDELB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fRegionId, argsPrefix+"REGIONID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ctcccloud_elb_test.go -args \ + --CERTIMATE_UPLOADER_CTCCCLOUDELB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_CTCCCLOUDELB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_UPLOADER_CTCCCLOUDELB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_UPLOADER_CTCCCLOUDELB_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_UPLOADER_CTCCCLOUDELB_REGIONID="your-region-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGIONID: %v", fRegionId), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + RegionId: fRegionId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + sres, _ := json.Marshal(res) + t.Logf("ok: %s", string(sres)) + }) +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/api_create_certificate.go b/internal/pkg/sdk3rd/ctyun/elb/api_create_certificate.go new file mode 100644 index 00000000..af17cdb7 --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/api_create_certificate.go @@ -0,0 +1,45 @@ +package elb + +import ( + "context" + "net/http" +) + +type CreateCertificateRequest struct { + ClientToken *string `json:"clientToken,omitempty"` + RegionID *string `json:"regionID,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Type *string `json:"type,omitempty"` + Certificate *string `json:"certificate,omitempty"` + PrivateKey *string `json:"privateKey,omitempty"` +} + +type CreateCertificateResponse struct { + baseResult + + ReturnObj *struct { + ID string `json:"id"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + return c.CreateCertificateWithContext(context.Background(), req) +} + +func (c *Client) CreateCertificateWithContext(ctx context.Context, req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + httpreq, err := c.newRequest(http.MethodPost, "/v4/elb/create-certificate") + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &CreateCertificateResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/api_list_certificate.go b/internal/pkg/sdk3rd/ctyun/elb/api_list_certificate.go new file mode 100644 index 00000000..7a55adfb --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/api_list_certificate.go @@ -0,0 +1,56 @@ +package elb + +import ( + "context" + "net/http" +) + +type ListCertificateRequest struct { + ClientToken *string `json:"clientToken,omitempty"` + RegionID *string `json:"regionID,omitempty"` + IDs *string `json:"IDs,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` +} + +type ListCertificateResponse struct { + baseResult + + ReturnObj []*CertificateRecord `json:"returnObj,omitempty"` +} + +func (c *Client) ListCertificate(req *ListCertificateRequest) (*ListCertificateResponse, error) { + return c.ListCertificateWithContext(context.Background(), req) +} + +func (c *Client) ListCertificateWithContext(ctx context.Context, req *ListCertificateRequest) (*ListCertificateResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v4/elb/list-certificate") + if err != nil { + return nil, err + } else { + if req.ClientToken != nil { + httpreq.SetQueryParam("clientToken", *req.ClientToken) + } + if req.RegionID != nil { + httpreq.SetQueryParam("regionID", *req.RegionID) + } + if req.IDs != nil { + httpreq.SetQueryParam("IDs", *req.IDs) + } + if req.Name != nil { + httpreq.SetQueryParam("name", *req.Name) + } + if req.Type != nil { + httpreq.SetQueryParam("type", *req.Type) + } + + httpreq.SetContext(ctx) + } + + result := &ListCertificateResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/api_list_listener.go b/internal/pkg/sdk3rd/ctyun/elb/api_list_listener.go new file mode 100644 index 00000000..fe37deaa --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/api_list_listener.go @@ -0,0 +1,64 @@ +package elb + +import ( + "context" + "net/http" +) + +type ListListenerRequest struct { + ClientToken *string `json:"clientToken,omitempty"` + RegionID *string `json:"regionID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + IDs *string `json:"IDs,omitempty"` + Name *string `json:"name,omitempty"` + LoadBalancerID *string `json:"loadBalancerID,omitempty"` + AccessControlID *string `json:"accessControlID,omitempty"` +} + +type ListListenerResponse struct { + baseResult + + ReturnObj []*ListenerRecord `json:"returnObj,omitempty"` +} + +func (c *Client) ListListener(req *ListListenerRequest) (*ListListenerResponse, error) { + return c.ListListenerWithContext(context.Background(), req) +} + +func (c *Client) ListListenerWithContext(ctx context.Context, req *ListListenerRequest) (*ListListenerResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v4/elb/list-listener") + if err != nil { + return nil, err + } else { + if req.ClientToken != nil { + httpreq.SetQueryParam("clientToken", *req.ClientToken) + } + if req.RegionID != nil { + httpreq.SetQueryParam("regionID", *req.RegionID) + } + if req.ProjectID != nil { + httpreq.SetQueryParam("projectID", *req.ProjectID) + } + if req.IDs != nil { + httpreq.SetQueryParam("IDs", *req.IDs) + } + if req.Name != nil { + httpreq.SetQueryParam("name", *req.Name) + } + if req.LoadBalancerID != nil { + httpreq.SetQueryParam("loadBalancerID", *req.LoadBalancerID) + } + if req.LoadBalancerID != nil { + httpreq.SetQueryParam("accessControlID", *req.AccessControlID) + } + + httpreq.SetContext(ctx) + } + + result := &ListListenerResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/api_show_listener.go b/internal/pkg/sdk3rd/ctyun/elb/api_show_listener.go new file mode 100644 index 00000000..a57a8bea --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/api_show_listener.go @@ -0,0 +1,48 @@ +package elb + +import ( + "context" + "net/http" +) + +type ShowListenerRequest struct { + ClientToken *string `json:"clientToken,omitempty"` + RegionID *string `json:"regionID,omitempty"` + ListenerID *string `json:"listenerID,omitempty"` +} + +type ShowListenerResponse struct { + baseResult + + ReturnObj []*ListenerRecord `json:"returnObj,omitempty"` +} + +func (c *Client) ShowListener(req *ShowListenerRequest) (*ShowListenerResponse, error) { + return c.ShowListenerWithContext(context.Background(), req) +} + +func (c *Client) ShowListenerWithContext(ctx context.Context, req *ShowListenerRequest) (*ShowListenerResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v4/elb/show-listener") + if err != nil { + return nil, err + } else { + if req.ClientToken != nil { + httpreq.SetQueryParam("clientToken", *req.ClientToken) + } + if req.RegionID != nil { + httpreq.SetQueryParam("regionID", *req.RegionID) + } + if req.ListenerID != nil { + httpreq.SetQueryParam("listenerID", *req.ListenerID) + } + + httpreq.SetContext(ctx) + } + + result := &ShowListenerResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/api_update_listener.go b/internal/pkg/sdk3rd/ctyun/elb/api_update_listener.go new file mode 100644 index 00000000..845d9100 --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/api_update_listener.go @@ -0,0 +1,44 @@ +package elb + +import ( + "context" + "net/http" +) + +type UpdateListenerRequest struct { + ClientToken *string `json:"clientToken,omitempty"` + RegionID *string `json:"regionID,omitempty"` + ListenerID *string `json:"listenerID,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + CertificateID *string `json:"certificateID,omitempty"` + CaEnabled *bool `json:"caEnabled,omitempty"` + ClientCertificateID *string `json:"clientCertificateID,omitempty"` +} + +type UpdateListenerResponse struct { + baseResult + + ReturnObj []*ListenerRecord `json:"returnObj,omitempty"` +} + +func (c *Client) UpdateListener(req *UpdateListenerRequest) (*UpdateListenerResponse, error) { + return c.UpdateListenerWithContext(context.Background(), req) +} + +func (c *Client) UpdateListenerWithContext(ctx context.Context, req *UpdateListenerRequest) (*UpdateListenerResponse, error) { + httpreq, err := c.newRequest(http.MethodPost, "/v4/elb/update-listener") + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &UpdateListenerResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/client.go b/internal/pkg/sdk3rd/ctyun/elb/client.go new file mode 100644 index 00000000..a71effa3 --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/client.go @@ -0,0 +1,50 @@ +package elb + +import ( + "fmt" + "time" + + "github.com/go-resty/resty/v2" + "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/openapi" +) + +const endpoint = "https://ctelb-global.ctapi.ctyun.cn" + +type Client struct { + client *openapi.Client +} + +func NewClient(accessKeyId, secretAccessKey string) (*Client, error) { + client, err := openapi.NewClient(endpoint, accessKeyId, secretAccessKey) + if err != nil { + return nil, err + } + + return &Client{client: client}, nil +} + +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) newRequest(method string, path string) (*resty.Request, error) { + return c.client.NewRequest(method, path) +} + +func (c *Client) doRequest(request *resty.Request) (*resty.Response, error) { + return c.client.DoRequest(request) +} + +func (c *Client) doRequestWithResult(request *resty.Request, result baseResultInterface) (*resty.Response, error) { + response, err := c.client.DoRequestWithResult(request, result) + if err == nil { + statusCode := result.GetStatusCode() + errorCode := result.GetError() + if (statusCode != "" && statusCode != "200" && statusCode != "800") || (errorCode != "" && errorCode != "SUCCESS") { + return response, fmt.Errorf("sdkerr: api error, code='%s', message='%s', errorCode='%s', description='%s'", statusCode, result.GetMessage(), result.GetMessage(), result.GetDescription()) + } + } + + return response, err +} diff --git a/internal/pkg/sdk3rd/ctyun/elb/types.go b/internal/pkg/sdk3rd/ctyun/elb/types.go new file mode 100644 index 00000000..4f2971b8 --- /dev/null +++ b/internal/pkg/sdk3rd/ctyun/elb/types.go @@ -0,0 +1,104 @@ +package elb + +import ( + "bytes" + "encoding/json" + "strconv" +) + +type baseResultInterface interface { + GetStatusCode() string + GetMessage() string + GetError() string + GetDescription() string +} + +type baseResult struct { + StatusCode json.RawMessage `json:"statusCode,omitempty"` + Message *string `json:"message,omitempty"` + Error *string `json:"error,omitempty"` + Description *string `json:"description,omitempty"` + RequestId *string `json:"requestId,omitempty"` +} + +func (r *baseResult) GetStatusCode() string { + if r.StatusCode == nil { + return "" + } + + decoder := json.NewDecoder(bytes.NewReader(r.StatusCode)) + token, err := decoder.Token() + if err != nil { + return "" + } + + switch t := token.(type) { + case string: + return t + case float64: + return strconv.FormatFloat(t, 'f', -1, 64) + case json.Number: + return t.String() + default: + return "" + } +} + +func (r *baseResult) GetMessage() string { + if r.Message == nil { + return "" + } + + return *r.Message +} + +func (r *baseResult) GetError() string { + if r.Error == nil { + return "" + } + + return *r.Error +} + +func (r *baseResult) GetDescription() string { + if r.Description == nil { + return "" + } + + return *r.Description +} + +var _ baseResultInterface = (*baseResult)(nil) + +type CertificateRecord struct { + ID string `json:"ID"` + RegionID string `json:"regionID"` + AzName string `json:"azName"` + ProjectID string `json:"projectID"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privateKey"` + Status string `json:"status"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} + +type ListenerRecord struct { + ID string `json:"ID"` + RegionID string `json:"regionID"` + AzName string `json:"azName"` + ProjectID string `json:"projectID"` + Name string `json:"name"` + Description string `json:"description"` + LoadBalancerID string `json:"loadBalancerID"` + Protocol string `json:"protocol"` + ProtocolPort int32 `json:"protocolPort"` + CertificateID string `json:"certificateID,omitempty"` + CaEnabled bool `json:"caEnabled"` + ClientCertificateID string `json:"clientCertificateID,omitempty"` + Status string `json:"status"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index e1bee0c9..15f627a9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -50,6 +50,7 @@ import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlu import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; import DeployNodeConfigFormCTCCCloudAOConfig from "./DeployNodeConfigFormCTCCCloudAOConfig"; import DeployNodeConfigFormCTCCCloudCDNConfig from "./DeployNodeConfigFormCTCCCloudCDNConfig"; +import DeployNodeConfigFormCTCCCloudELBConfig from "./DeployNodeConfigFormCTCCCloudELBConfig"; import DeployNodeConfigFormCTCCCloudICDNConfig from "./DeployNodeConfigFormCTCCCloudICDNConfig"; import DeployNodeConfigFormCTCCCloudLVDNConfig from "./DeployNodeConfigFormCTCCCloudLVDNConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; @@ -273,6 +274,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.CTCCCLOUD_CDN: return ; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_ELB: + return ; case DEPLOYMENT_PROVIDERS.CTCCCLOUD_ICDN: return ; case DEPLOYMENT_PROVIDERS.CTCCCLOUD_LVDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormCTCCCloudELBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormCTCCCloudELBConfig.tsx new file mode 100644 index 00000000..6577c35c --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormCTCCCloudELBConfig.tsx @@ -0,0 +1,121 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormCTCCCloudELBConfigFieldValues = Nullish<{ + regionId: string; + resourceType: string; + loadbalancerId?: string; + listenerId?: string; +}>; + +export type DeployNodeConfigFormCTCCCloudELBConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormCTCCCloudELBConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormCTCCCloudELBConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const initFormModel = (): DeployNodeConfigFormCTCCCloudELBConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const DeployNodeConfigFormCTCCCloudELBConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormCTCCCloudELBConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.union([z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER)], { + message: t("workflow_node.deploy.form.ctcccloud_elb_resource_type.placeholder"), + }), + regionId: z + .string({ message: t("workflow_node.deploy.form.ctcccloud_elb_region_id.placeholder") }) + .nonempty(t("workflow_node.deploy.form.ctcccloud_elb_region_id.placeholder")), + loadbalancerId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_LOADBALANCER || !!v?.trim(), t("workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.placeholder")), + listenerId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.ctcccloud_elb_listener_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + } + > + + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormCTCCCloudELBConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index d54494ad..1100e797 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -416,6 +416,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ CTCCCLOUD_AO: `${ACCESS_PROVIDERS.CTCCCLOUD}-ao`, CTCCCLOUD_CDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-cdn`, CTCCCLOUD_CMS: `${ACCESS_PROVIDERS.CTCCCLOUD}-cms`, + CTCCCLOUD_ELB: `${ACCESS_PROVIDERS.CTCCCLOUD}-elb`, CTCCCLOUD_ICDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-icdn`, CTCCCLOUD_LVDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-lvdn`, DOGECLOUD_CDN: `${ACCESS_PROVIDERS.DOGECLOUD}-cdn`, @@ -578,6 +579,7 @@ export const deploymentProvidersMap: Maphttps://cdn-console.ctyun.cn", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.label": "Resource type", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.option.certificate.label": "ELB certificate", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.option.loadbalancer.label": "ELB load balancer", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.option.listener.label": "ELB listener", + "workflow_node.deploy.form.ctcccloud_elb_region_id.label": "CTCC StateCloud ELB region ID", + "workflow_node.deploy.form.ctcccloud_elb_region_id.placeholder": "Please enter CTCC StateCloud ELB region ID", + "workflow_node.deploy.form.ctcccloud_elb_region_id.tooltip": "For more information, see https://www.ctyun.cn/document/10026755/10196575", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.label": "CTCC StateCloud ELB load balancer ID", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.placeholder": "Please enter CTCC StateCloud ELB load balancer ID", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.tooltip": "For more information, see https://console.ctyun.cn/network/index/#/elb/elbList", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.label": "CTCC StateCloud ELB listener ID", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.placeholder": "Please enter CTCC StateCloud ELB listener ID", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.tooltip": "For more information, see https://console.ctyun.cn/network/index/#/elb/elbList", "workflow_node.deploy.form.ctcccloud_icdn_domain.label": "CTCC StateCloud ICDN domain", "workflow_node.deploy.form.ctcccloud_icdn_domain.placeholder": "Please enter CTCC StateCloud ICDN domain name", "workflow_node.deploy.form.ctcccloud_icdn_domain.tooltip": "For more information, see https://cdn-console.ctyun.cn", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index f22a23e7..18ad43fe 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -403,6 +403,19 @@ "workflow_node.deploy.form.ctcccloud_cdn_domain.label": "天翼云 CDN 加速域名", "workflow_node.deploy.form.ctcccloud_cdn_domain.placeholder": "请输入天翼云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.ctcccloud_cdn_domain.tooltip": "这是什么?请参阅 https://cdn-console.ctyun.cn", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听器的证书", + "workflow_node.deploy.form.ctcccloud_elb_resource_type.option.listener.label": "替换指定监听器的证书", + "workflow_node.deploy.form.ctcccloud_elb_region_id.label": "天翼云 ELB 资源池 ID", + "workflow_node.deploy.form.ctcccloud_elb_region_id.placeholder": "请输入天翼云 ELB 资源池 ID", + "workflow_node.deploy.form.ctcccloud_elb_region_id.tooltip": "这是什么?请参阅 https://www.ctyun.cn/document/10026755/10196575", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.label": "天翼云 ELB 负载均衡器 ID", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.placeholder": "请输入天翼云 ELB 负载均衡器 ID", + "workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.tooltip": "这是什么?请参阅 https://console.ctyun.cn/network/index/#/elb/elbList", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.label": "天翼云 ELB 监听器 ID", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.placeholder": "请输入天翼云 ELB 监听器 ID", + "workflow_node.deploy.form.ctcccloud_elb_listener_id.tooltip": "这是什么?请参阅 https://console.ctyun.cn/network/index/#/elb/elbList", "workflow_node.deploy.form.ctcccloud_icdn_domain.label": "天翼云 ICDN 加速域名", "workflow_node.deploy.form.ctcccloud_icdn_domain.placeholder": "请输入天翼云 ICDN 加速域名(支持泛域名)", "workflow_node.deploy.form.ctcccloud_icdn_domain.tooltip": "这是什么?请参阅 https://cdn-console.ctyun.cn",