From 7e4aa244598cc0370e556caa991de32c3a7cbfea Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:36:44 +0800 Subject: [PATCH 01/13] fix: #539 --- internal/pkg/vendors/cdnfly-sdk/models.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/vendors/cdnfly-sdk/models.go b/internal/pkg/vendors/cdnfly-sdk/models.go index 873b80b0..87a1b4f1 100644 --- a/internal/pkg/vendors/cdnfly-sdk/models.go +++ b/internal/pkg/vendors/cdnfly-sdk/models.go @@ -25,7 +25,7 @@ type GetSiteRequest struct { type GetSiteResponse struct { baseResponse Data *struct { - Id string `json:"id"` + Id int64 `json:"id"` Name string `json:"name"` Domain string `json:"domain"` HttpsListen string `json:"https_listen"` From 09b1bf6e2d1181cc93603f94dc4cb022c4e8f62c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:37:02 +0800 Subject: [PATCH 02/13] fix: #523 --- ui/src/components/access/AccessFormGcoreConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/access/AccessFormGcoreConfig.tsx b/ui/src/components/access/AccessFormGcoreConfig.tsx index 8619f7fe..858dac02 100644 --- a/ui/src/components/access/AccessFormGcoreConfig.tsx +++ b/ui/src/components/access/AccessFormGcoreConfig.tsx @@ -28,7 +28,7 @@ const AccessFormGcoreConfig = ({ form: formInst, formName, disabled, initialValu apiToken: z .string() .min(1, t("access.form.gcore_api_token.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) + .max(256, t("common.errmsg.string_max", { max: 256 })) .trim(), }); const formRule = createSchemaFieldRule(formSchema); From 8e4b3d12bd91b1de96182ee0318b3a7e2d3aa8cf Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:41:56 +0800 Subject: [PATCH 03/13] fix: #527 --- .../deployer/providers/aliyun-fc/aliyun_fc.go | 4 +-- .../providers/aliyun-waf/aliyun_waf.go | 28 +++++++++++++++---- .../DeployNodeConfigFormAliyunFCConfig.tsx | 18 ++++++------ .../DeployNodeConfigFormAliyunWAFConfig.tsx | 18 ++++++++++-- .../i18n/locales/en/nls.workflow.nodes.json | 4 ++- .../i18n/locales/zh/nls.workflow.nodes.json | 8 ++++-- 6 files changed, 57 insertions(+), 23 deletions(-) diff --git a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go index edd2bc76..d5450017 100644 --- a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go +++ b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go @@ -69,12 +69,12 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { switch d.config.ServiceVersion { - case "3.0": + case "3", "3.0": if err := d.deployToFC3(ctx, certPem, privkeyPem); err != nil { return nil, err } - case "2.0": + case "2", "2.0": if err := d.deployToFC2(ctx, certPem, privkeyPem); err != nil { return nil, err } diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go index dd9248b6..998ee7e7 100644 --- a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go @@ -24,6 +24,8 @@ type DeployerConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 阿里云地域。 Region string `json:"region"` + // 服务版本。 + ServiceVersion string `json:"serviceVersion"` // WAF 实例 ID。 InstanceId string `json:"instanceId"` // 接入域名(支持泛域名)。 @@ -77,10 +79,24 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe return nil, errors.New("config `instanceId` is required") } + switch d.config.ServiceVersion { + case "3", "3.0": + if err := d.deployToWAF3(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + default: + return nil, xerrors.Errorf("unsupported service version: %s", d.config.ServiceVersion) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToWAF3(ctx context.Context, certPem string, privkeyPem string) error { // 上传证书到 CAS upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) if err != nil { - return nil, xerrors.Wrap(err, "failed to upload certificate file") + return xerrors.Wrap(err, "failed to upload certificate file") } else { d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } @@ -97,7 +113,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttps(describeDefaultHttpsReq) d.logger.Debug("sdk request 'waf.DescribeDefaultHttps'", slog.Any("request", describeDefaultHttpsReq), slog.Any("response", describeDefaultHttpsResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDefaultHttps'") + return xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDefaultHttps'") } // 修改默认 SSL/TLS 设置 @@ -116,7 +132,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttps(modifyDefaultHttpsReq) d.logger.Debug("sdk request 'waf.ModifyDefaultHttps'", slog.Any("request", modifyDefaultHttpsReq), slog.Any("response", modifyDefaultHttpsResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDefaultHttps'") + return xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDefaultHttps'") } } else { // 指定接入域名 @@ -131,7 +147,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetail(describeDomainDetailReq) d.logger.Debug("sdk request 'waf.DescribeDomainDetail'", slog.Any("request", describeDomainDetailReq), slog.Any("response", describeDomainDetailResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDomainDetail'") + return xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDomainDetail'") } // 修改 CNAME 接入资源 @@ -163,11 +179,11 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe modifyDomainResp, err := d.sdkClient.ModifyDomain(modifyDomainReq) d.logger.Debug("sdk request 'waf.ModifyDomain'", slog.Any("request", modifyDomainReq), slog.Any("response", modifyDomainResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDomain'") + return xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDomain'") } } - return &deployer.DeployResult{}, nil + return nil } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunWaf.Client, error) { diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx index 87212953..e1d16d07 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx @@ -55,6 +55,15 @@ const DeployNodeConfigFormAliyunFCConfig = ({ form: formInst, formName, disabled name={formName} onValuesChange={handleFormChange} > + } + > + + + - } - > - - - ; @@ -20,7 +21,9 @@ export type DeployNodeConfigFormAliyunWAFConfigProps = { }; const initFormModel = (): DeployNodeConfigFormAliyunWAFConfigFieldValues => { - return {}; + return { + serviceVersion: "3.0", + }; }; const DeployNodeConfigFormAliyunWAFConfig = ({ @@ -37,6 +40,9 @@ const DeployNodeConfigFormAliyunWAFConfig = ({ .string({ message: t("workflow_node.deploy.form.aliyun_waf_region.placeholder") }) .nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")) .trim(), + serviceVersion: z.literal("3.0", { + message: t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder"), + }), instanceId: z .string({ message: t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder") }) .nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")) @@ -73,6 +79,14 @@ const DeployNodeConfigFormAliyunWAFConfig = ({ + + + + https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", + "workflow_node.deploy.form.aliyun_waf_service_version.label": "Alibaba Cloud WAF version", + "workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "Please select Alibaba Cloud WAF version", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index cbb6a2cf..3144bfa8 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -39,7 +39,7 @@ "workflow_node.apply.form.provider_access.button": "新建", "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 服务区域", "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS Route53 服务区域(例如:us-east-1)", - "workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 托管区域 ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "请输入 AWS Route53 托管区域 ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/Route53/latest/DeveloperGuide/hosted-zones-working-with.html", @@ -88,7 +88,7 @@ "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。
请注意,如果你使用 Docker 安装 Certimate,“本地部署”将会部署到容器内而非宿主机上。", "workflow_node.deploy.form.certificate.label": "待部署证书", "workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书", - "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请阶段。如果选项为空请先确保前序节点配置正确。", + "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.params_config.label": "参数设置", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启面板服务", "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", @@ -199,6 +199,8 @@ "workflow_node.deploy.form.aliyun_waf_region.label": "阿里云 WAF 服务地域", "workflow_node.deploy.form.aliyun_waf_region.placeholder": "请输入阿里云 WAF 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", + "workflow_node.deploy.form.aliyun_waf_service_version.label": "阿里云 WAF 服务版本", + "workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "请选择阿里云 WAF 服务版本", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com

仅支持 CNAME 接入。", @@ -207,7 +209,7 @@ "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "这是什么?请参阅 waf.console.aliyun.com

不填写时,将替换实例的默认证书。", "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront 服务区域", "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS CloudFront 服务区域(例如:us-east-1)", - "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", From 16f20dc01df9b6481bcf3d569343db99ec253079 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:43:09 +0800 Subject: [PATCH 04/13] feat: add upyun ssl uploader --- .../providers/qiniu-sslcert/qiniu_sslcert.go | 11 +- .../uploader/providers/upyun-ssl/upyun_ssl.go | 83 +++++++++++ .../providers/upyun-ssl/upyun_ssl_test.go | 72 +++++++++ internal/pkg/vendors/upyun-sdk/console/api.go | 104 +++++++++++++ .../pkg/vendors/upyun-sdk/console/client.go | 94 ++++++++++++ .../pkg/vendors/upyun-sdk/console/models.go | 141 ++++++++++++++++++ 6 files changed, 504 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl.go create mode 100644 internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl_test.go create mode 100644 internal/pkg/vendors/upyun-sdk/console/api.go create mode 100644 internal/pkg/vendors/upyun-sdk/console/client.go create mode 100644 internal/pkg/vendors/upyun-sdk/console/models.go diff --git a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go index ce18a335..6bc71c3f 100644 --- a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go @@ -2,6 +2,7 @@ import ( "context" + "errors" "fmt" "log/slog" "time" @@ -69,7 +70,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe // 上传新证书 // REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate uploadSslCertResp, err := u.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPem, privkeyPem) - u.logger.Debug("sdk request 'ssl.UploadCertificate'", slog.Any("response", uploadSslCertResp)) + u.logger.Debug("sdk request 'cdn.UploadSslCert'", slog.Any("response", uploadSslCertResp)) if err != nil { return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'") } @@ -82,6 +83,14 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKey, secretKey string) (*qiniusdk.Client, error) { + if secretKey == "" { + return nil, errors.New("invalid qiniu access key") + } + + if secretKey == "" { + return nil, errors.New("invalid qiniu secret key") + } + credential := auth.New(accessKey, secretKey) client := qiniusdk.NewClient(credential) return client, nil diff --git a/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl.go b/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl.go new file mode 100644 index 00000000..3e4d3c40 --- /dev/null +++ b/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl.go @@ -0,0 +1,83 @@ +package upyunssl + +import ( + "context" + "errors" + "log/slog" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/upyun-sdk/console" +) + +type UploaderConfig struct { + // 又拍云账号用户名。 + Username string `json:"username"` + // 又拍云账号密码。 + Password string `json:"password"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *upyunsdk.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.Username, config.Password) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + 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.Default() + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 上传证书 + uploadHttpsCertificateReq := &upyunsdk.UploadHttpsCertificateRequest{ + Certificate: certPem, + PrivateKey: privkeyPem, + } + uploadHttpsCertificateResp, err := u.sdkClient.UploadHttpsCertificate(uploadHttpsCertificateReq) + u.logger.Debug("sdk request 'ssl.UploadHttpsCertificate'", slog.Any("response", uploadHttpsCertificateResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.UploadHttpsCertificate'") + } + + return &uploader.UploadResult{ + CertId: uploadHttpsCertificateResp.Data.Result.CertificateId, + }, nil +} + +func createSdkClient(username, password string) (*upyunsdk.Client, error) { + if username == "" { + return nil, errors.New("invalid upyun username") + } + + if password == "" { + return nil, errors.New("invalid upyun password") + } + + client := upyunsdk.NewClient(username, password) + return client, nil +} diff --git a/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl_test.go b/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl_test.go new file mode 100644 index 00000000..1e6d81ec --- /dev/null +++ b/internal/pkg/core/uploader/providers/upyun-ssl/upyun_ssl_test.go @@ -0,0 +1,72 @@ +package upyunssl_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/upyun-ssl" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_UPYUNSSL_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") +} + +/* +Shell command to run this test: + + go test -v ./upyun_ssl_test.go -args \ + --CERTIMATE_UPLOADER_UPYUNSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_UPYUNSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_UPLOADER_UPYUNSSL_USERNAME="your-username" \ + --CERTIMATE_UPLOADER_UPYUNSSL_PASSWORD="your-password" +*/ +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("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + Username: fUsername, + Password: fPassword, + }) + 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/vendors/upyun-sdk/console/api.go b/internal/pkg/vendors/upyun-sdk/console/api.go new file mode 100644 index 00000000..afcb0b5b --- /dev/null +++ b/internal/pkg/vendors/upyun-sdk/console/api.go @@ -0,0 +1,104 @@ +package console + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" +) + +func (c *Client) getCookie() error { + req := &signinRequest{Username: c.username, Password: c.password} + res, err := c.sendRequest(http.MethodPost, "/accounts/signin/", req) + if err != nil { + return err + } + + resp := &signinResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("upyun api error: failed to parse response: %w", err) + } else if !resp.Data.Result { + return errors.New("upyun console signin failed") + } + + c.loginCookie = res.Header().Get("Set-Cookie") + + return nil +} + +func (c *Client) UploadHttpsCertificate(req *UploadHttpsCertificateRequest) (*UploadHttpsCertificateResponse, error) { + if c.loginCookie == "" { + if err := c.getCookie(); err != nil { + return nil, err + } + } + + resp := UploadHttpsCertificateResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/api/https/certificate/", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) GetHttpsCertificateManager(certificateId string) (*GetHttpsCertificateManagerResponse, error) { + if c.loginCookie == "" { + if err := c.getCookie(); err != nil { + return nil, err + } + } + + req := GetHttpsCertificateManagerRequest{CertificateId: certificateId} + resp := GetHttpsCertificateManagerResponse{} + err := c.sendRequestWithResult(http.MethodGet, "/api/https/certificate/manager/", &req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) UpdateHttpsCertificateManager(req *UpdateHttpsCertificateManagerRequest) (*UpdateHttpsCertificateManagerResponse, error) { + if c.loginCookie == "" { + if err := c.getCookie(); err != nil { + return nil, err + } + } + + resp := UpdateHttpsCertificateManagerResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/api/https/certificate/manager", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) GetHttpsServiceManager(domain string) (*GetHttpsServiceManagerResponse, error) { + if c.loginCookie == "" { + if err := c.getCookie(); err != nil { + return nil, err + } + } + + req := GetHttpsServiceManagerRequest{Domain: domain} + resp := GetHttpsServiceManagerResponse{} + err := c.sendRequestWithResult(http.MethodGet, "/api/https/services/manager", &req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) MigrateHttpsDomain(req *MigrateHttpsDomainRequest) (*MigrateHttpsDomainResponse, error) { + if c.loginCookie == "" { + if err := c.getCookie(); err != nil { + return nil, err + } + } + + resp := MigrateHttpsDomainResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/api/https/migrate/domain", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/internal/pkg/vendors/upyun-sdk/console/client.go b/internal/pkg/vendors/upyun-sdk/console/client.go new file mode 100644 index 00000000..74758b82 --- /dev/null +++ b/internal/pkg/vendors/upyun-sdk/console/client.go @@ -0,0 +1,94 @@ +package console + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + username string + password string + loginCookie string + + client *resty.Client +} + +func NewClient(username, password string) *Client { + client := resty.New() + + return &Client{ + username: username, + password: password, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.username, c.password) + req.Method = method + req.URL = "https://console.upyun.com" + path + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req. + SetQueryParams(qs). + SetHeader("Cookie", c.loginCookie) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("Cookie", c.loginCookie). + SetBody(params) + } + + req = req.SetDebug(true) + resp, err := req.Send() + if err != nil { + return nil, fmt.Errorf("upyun api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("upyun api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result interface{}) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("upyun api error: failed to parse response: %w", err) + } + + tresp := &baseResponse{} + if err := json.Unmarshal(resp.Body(), &tresp); err != nil { + return fmt.Errorf("upyun api error: failed to parse response: %w", err) + } else if tdata := tresp.GetData(); tdata == nil { + return fmt.Errorf("upyun api error: empty data") + } else if errcode := tdata.GetErrorCode(); errcode > 0 { + return fmt.Errorf("upyun api error: %d - %s", errcode, tdata.GetErrorMessage()) + } + + return nil +} diff --git a/internal/pkg/vendors/upyun-sdk/console/models.go b/internal/pkg/vendors/upyun-sdk/console/models.go new file mode 100644 index 00000000..982993fe --- /dev/null +++ b/internal/pkg/vendors/upyun-sdk/console/models.go @@ -0,0 +1,141 @@ +package console + +import ( + "encoding/json" +) + +type baseResponse struct { + Data *baseResponseData `json:"data,omitempty"` +} + +func (r *baseResponse) GetData() *baseResponseData { + return r.Data +} + +type baseResponseData struct { + ErrorCode json.Number `json:"error_code"` + ErrorMessage string `json:"message"` +} + +func (r *baseResponseData) GetErrorCode() int { + if r.ErrorCode.String() == "" { + return 0 + } + + errcode, err := r.ErrorCode.Int64() + if err != nil { + return -1 + } + + return int(errcode) +} + +func (r *baseResponseData) GetErrorMessage() string { + return r.ErrorMessage +} + +type signinRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type signinResponse struct { + baseResponse + Data struct { + baseResponseData + Result bool `json:"result"` + } `json:"data"` +} + +type UploadHttpsCertificateRequest struct { + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` +} + +type UploadHttpsCertificateResponse struct { + baseResponse + Data *struct { + baseResponseData + Status int `json:"status"` + Result struct { + CertificateId string `json:"certificate_id"` + CommonName string `json:"commonName"` + Serial string `json:"serial"` + } `json:"result"` + } `json:"data"` +} + +type GetHttpsCertificateManagerRequest struct { + CertificateId string `json:"certificate_id"` +} + +type GetHttpsCertificateManagerResponse struct { + baseResponse + Data *struct { + baseResponseData + AuthenticateNum int32 `json:"authenticate_num"` + AuthenticateDomains []string `json:"authenticate_domain"` + Domains []HttpsCertificateManagerDomain `json:"domains"` + } `json:"data"` +} + +type HttpsCertificateManagerDomain struct { + Name string `json:"name"` + Type string `json:"type"` + BucketId int64 `json:"bucket_id"` + BucketName string `json:"bucket_name"` +} + +type UpdateHttpsCertificateManagerRequest struct { + CertificateId string `json:"certificate_id"` + Domain string `json:"domain"` + Https bool `json:"https"` + ForceHttps bool `json:"force_https"` +} + +type UpdateHttpsCertificateManagerResponse struct { + baseResponse + Data *struct { + baseResponseData + Status bool `json:"status"` + } `json:"data"` +} + +type GetHttpsServiceManagerRequest struct { + Domain string `json:"domain"` +} + +type GetHttpsServiceManagerResponse struct { + baseResponse + Data *struct { + baseResponseData + Status int `json:"status"` + Domains []HttpsServiceManagerDomain `json:"result"` + } `json:"data"` +} + +type HttpsServiceManagerDomain struct { + CertificateId string `json:"certificate_id"` + CommonName string `json:"commonName"` + Https bool `json:"https"` + ForceHttps bool `json:"force_https"` + PaymentType string `json:"payment_type"` + DomainType string `json:"domain_type"` + Validity struct { + Start int64 `json:"start"` + End int64 `json:"end"` + } `json:"validity"` +} + +type MigrateHttpsDomainRequest struct { + CertificateId string `json:"crt_id"` + Domain string `json:"domain_name"` +} + +type MigrateHttpsDomainResponse struct { + baseResponse + Data *struct { + baseResponseData + Status bool `json:"status"` + } `json:"data"` +} From 4acbbf6e135b5de9e91f95feebaa3f90a10efb6d Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:49:16 +0800 Subject: [PATCH 05/13] feat: add upyun cdn deployer --- internal/app/scheduler.go | 1 + internal/deployer/providers.go | 23 ++++ internal/domain/access.go | 5 + internal/domain/provider.go | 3 + .../deployer/providers/qiniu-cdn/qiniu_cdn.go | 14 +- .../deployer/providers/upyun-cdn/upyun_cdn.go | 129 ++++++++++++++++++ .../providers/upyun-cdn/upyun_cdn_test.go | 75 ++++++++++ migrations/1742392800_upgrade.go | 81 +++++++++++ ui/public/imgs/providers/upyun.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormUpyunConfig.tsx | 76 +++++++++++ .../DeployNodeConfigFormUpyunCDNConfig.tsx | 59 ++++++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 + ui/src/i18n/locales/en/nls.provider.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 3 + ui/src/i18n/locales/zh/nls.access.json | 6 + ui/src/i18n/locales/zh/nls.provider.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 3 + 20 files changed, 495 insertions(+), 7 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go create mode 100644 internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go create mode 100644 migrations/1742392800_upgrade.go create mode 100644 ui/public/imgs/providers/upyun.svg create mode 100644 ui/src/components/access/AccessFormUpyunConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx diff --git a/internal/app/scheduler.go b/internal/app/scheduler.go index 1b16ac32..c5e93c9f 100644 --- a/internal/app/scheduler.go +++ b/internal/app/scheduler.go @@ -3,6 +3,7 @@ package app import ( "sync" "time" + _ "time/tzdata" "github.com/pocketbase/pocketbase/tools/cron" ) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 4e03ad2e..7d62ac41 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -56,6 +56,7 @@ import ( pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf" pUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" pUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" + pUpyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/upyun-cdn" pVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" pVolcEngineCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-clb" pVolcEngineDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-dcdn" @@ -225,6 +226,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, Region: maputil.GetString(options.ProviderDeployConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), }) @@ -775,6 +777,27 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } + case domain.DeployProviderTypeUpyunCDN: + { + access := domain.AccessConfigForUpyun{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderTypeUpyunCDN: + deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ + Username: access.Username, + Password: access.Password, + Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + }) + return deployer, err + + default: + break + } + } + case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineImageX, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS: { access := domain.AccessConfigForVolcEngine{} diff --git a/internal/domain/access.go b/internal/domain/access.go index fc6a7eb1..963d4083 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -201,6 +201,11 @@ type AccessConfigForUCloud struct { ProjectId string `json:"projectId,omitempty"` } +type AccessConfigForUpyun struct { + Username string `json:"username"` + Password string `json:"password"` +} + type AccessConfigForVolcEngine struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 78c79d4a..cbc034fa 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -28,6 +28,7 @@ const ( AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留) AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") + AccessProviderTypeDynv6 = AccessProviderType("dynv6") // dynv6(预留) AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) AccessProviderTypeGname = AccessProviderType("gname") @@ -50,6 +51,7 @@ const ( AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") + AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWestcn = AccessProviderType("westcn") @@ -158,6 +160,7 @@ const ( DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") + DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn") DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go index 02dac427..e8166afd 100644 --- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -87,18 +87,18 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" { - modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) - d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) - if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") - } - } else { + if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, upres.CertId, true, true) d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", enableDomainHttpsResp)) if err != nil { return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'") } + } else if getDomainInfoResp.Https.CertID != upres.CertId { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") + } } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go new file mode 100644 index 00000000..84d6cafb --- /dev/null +++ b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go @@ -0,0 +1,129 @@ +package upyuncdn + +import ( + "context" + "errors" + "log/slog" + + xerrors "github.com/pkg/errors" + "golang.org/x/exp/slices" + + "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/upyun-ssl" + upyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/upyun-sdk/console" +) + +type DeployerConfig struct { + // 又拍云账号用户名。 + Username string `json:"username"` + // 又拍云账号密码。 + Password string `json:"password"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *upyunsdk.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.Username, config.Password) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + Username: config.Username, + Password: config.Password, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + 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.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.GetHttpsServiceManager'") + } + + // 判断域名是否已启用 HTTPS。如果已启用,迁移域名证书;否则,设置新证书 + lastCertIndex := slices.IndexFunc(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.UpdateHttpsCertificateManager'") + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.MigrateHttpsDomain'") + } + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(username, password string) (*upyunsdk.Client, error) { + if username == "" { + return nil, errors.New("invalid upyun username") + } + + if password == "" { + return nil, errors.New("invalid upyun password") + } + + client := upyunsdk.NewClient(username, password) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go new file mode 100644 index 00000000..8a7b4485 --- /dev/null +++ b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go @@ -0,0 +1,75 @@ +package upyuncdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/upyun-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_UPYUNCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./upyun_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_UPYUNCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_USERNAME="your-username" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_PASSWORD="your-password" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_DOMAIN="example.com" \ +*/ +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("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + Username: fUsername, + Password: fPassword, + Domain: fDomain, + }) + 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/migrations/1742392800_upgrade.go b/migrations/1742392800_upgrade.go new file mode 100644 index 00000000..a06bdc75 --- /dev/null +++ b/migrations/1742392800_upgrade.go @@ -0,0 +1,81 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update field + if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{ + "hidden": false, + "id": "hwy7m03o", + "maxSelect": 1, + "name": "provider", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "1panel", + "acmehttpreq", + "akamai", + "aliyun", + "aws", + "azure", + "baiducloud", + "baishan", + "baotapanel", + "byteplus", + "cachefly", + "cdnfly", + "cloudflare", + "cloudns", + "cmcccloud", + "ctcccloud", + "cucccloud", + "dnsla", + "dogecloud", + "dynv6", + "edgio", + "fastly", + "gname", + "gcore", + "godaddy", + "goedge", + "huaweicloud", + "jdcloud", + "k8s", + "local", + "namecheap", + "namedotcom", + "namesilo", + "ns1", + "powerdns", + "qiniu", + "qingcloud", + "rainyun", + "safeline", + "ssh", + "tencentcloud", + "ucloud", + "upyun", + "volcengine", + "webhook", + "westcn" + ] + }`)); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/public/imgs/providers/upyun.svg b/ui/public/imgs/providers/upyun.svg new file mode 100644 index 00000000..cc13793d --- /dev/null +++ b/ui/public/imgs/providers/upyun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 7f2143ac..caf5c605 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -44,6 +44,7 @@ import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; +import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; @@ -170,6 +171,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.UCLOUD: return ; + case ACCESS_PROVIDERS.UPYUN: + return ; case ACCESS_PROVIDERS.VOLCENGINE: return ; case ACCESS_PROVIDERS.WEBHOOK: diff --git a/ui/src/components/access/AccessFormUpyunConfig.tsx b/ui/src/components/access/AccessFormUpyunConfig.tsx new file mode 100644 index 00000000..8cc06d97 --- /dev/null +++ b/ui/src/components/access/AccessFormUpyunConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForUpyun } from "@/domain/access"; + +type AccessFormUpyunConfigFieldValues = Nullish; + +export type AccessFormUpyunConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormUpyunConfigFieldValues; + onValuesChange?: (values: AccessFormUpyunConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormUpyunConfigFieldValues => { + return { + username: "", + password: "", + }; +}; + +const AccessFormUpyunConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormUpyunConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + username: z + .string() + .trim() + .min(1, t("access.form.upyun_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + password: z + .string() + .min(1, t("access.form.upyun_password.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormUpyunConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx new file mode 100644 index 00000000..e09f5266 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx @@ -0,0 +1,59 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormUpyunCDNConfigFieldValues = Nullish<{ + domain: string; +}>; + +export type DeployNodeConfigFormUpyunCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUpyunCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUpyunCDNConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUpyunCDNConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormUpyunCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormUpyunCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domain: z + .string({ message: t("workflow_node.deploy.form.upyun_cdn_domain.placeholder") }) + .refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormUpyunCDNConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 1b5adf45..59a41cc6 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -40,6 +40,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForSSH | AccessConfigForTencentCloud | AccessConfigForUCloud + | AccessConfigForUpyun | AccessConfigForVolcEngine | AccessConfigForWebhook | AccessConfigForWestcn @@ -224,6 +225,11 @@ export type AccessConfigForUCloud = { projectId?: string; }; +export type AccessConfigForUpyun = { + username: string; + password: string; +}; + export type AccessConfigForVolcEngine = { accessKeyId: string; secretAccessKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index f3d6deb3..998ec718 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -39,6 +39,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ SSH: "ssh", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", + UPYUN: "upyun", VOLCENGINE: "volcengine", WEBHOOK: "webhook", WESTCN: "westcn", @@ -80,6 +81,7 @@ export const accessProvidersMap: Maphttps://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en", + "access.form.upyun_username.label": "UPYUN subaccount username", + "access.form.upyun_username.placeholder": "Please enter UPYUN subaccount username", + "access.form.upyun_username.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", + "access.form.upyun_password.label": "UPYUN subaccount password", + "access.form.upyun_password.placeholder": "Please enter UPYUN subaccount password", + "access.form.upyun_password.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", "access.form.baishan_api_token.label": "Baishan Cloud API token", "access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token", "access.form.baotapanel_api_url.label": "aaPanel URL", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 4c6091e1..9034aeea 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -93,6 +93,8 @@ "provider.ucloud": "UCloud", "provider.ucloud.ucdn": "UCloud - UCDN (UCloud Content Delivery Network)", "provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)", + "provider.upyun": "UPYUN", + "provider.upyun.cdn": "UPYUN - CDN (Content Delivery Network)", "provider.volcengine": "Volcengine", "provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)", "provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 6d4b0f97..666ef424 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -508,6 +508,9 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "UCloud US3 domain", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 domain name", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see https://console.ucloud-global.com/ufile", + "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", + "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", + "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "For more information, see https://console.upyun.com/services/cdn/", "workflow_node.deploy.form.volcengine_cdn_domain.label": "VolcEngine CDN domain", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "Please enter VolcEngine CDN domain name", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "For more information, see https://console.volcengine.com/cdn/homepage", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index d72cd259..bb2f829a 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -243,6 +243,12 @@ "access.form.ucloud_project_id.label": "优刻得项目 ID(可选)", "access.form.ucloud_project_id.placeholder": "请输入优刻得项目 ID", "access.form.ucloud_project_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/uaccount/iam/project_manage", + "access.form.upyun_username.label": "又拍云子账号用户名", + "access.form.upyun_username.placeholder": "请输入又拍云子账号用户名", + "access.form.upyun_username.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/

请关闭该账号的二次登录验证。", + "access.form.upyun_password.label": "又拍云子账号密码", + "access.form.upyun_password.placeholder": "请输入又拍云子账号密码", + "access.form.upyun_password.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/

请关闭该账号的二次登录验证。", "access.form.volcengine_access_key_id.label": "火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.placeholder": "请输入火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e8580e41..2e830cf3 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -93,6 +93,8 @@ "provider.ucloud": "优刻得", "provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", "provider.ucloud.us3": "优刻得 - 对象存储 US3", + "provider.upyun": "又拍云", + "provider.upyun.cdn": "又拍云 - 云分发 CDN", "provider.volcengine": "火山引擎", "provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 3144bfa8..63d881f8 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -508,6 +508,9 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", + "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", + "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", + "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/cdn/", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage", From e4fd1e78f5371580edf9beb83f1e50c81f486a53 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:53:02 +0800 Subject: [PATCH 06/13] feat: add upyun file deployer --- internal/deployer/providers.go | 4 +- internal/domain/provider.go | 1 + .../DeployNodeConfigFormUpyunFileConfig.tsx | 65 +++++++++++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 3 + ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 3 + 8 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormUpyunFileConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 7d62ac41..2508fe5c 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -777,7 +777,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeUpyunCDN: + case domain.DeployProviderTypeUpyunCDN, domain.DeployProviderTypeUpyunFile: { access := domain.AccessConfigForUpyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -785,7 +785,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeUpyunCDN: + case domain.DeployProviderTypeUpyunCDN, domain.DeployProviderTypeUpyunFile: deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ Username: access.Username, Password: access.Password, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index cbc034fa..472d8aeb 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -161,6 +161,7 @@ const ( DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn") + DeployProviderTypeUpyunFile = DeployProviderType("upyun-file") DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUpyunFileConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunFileConfig.tsx new file mode 100644 index 00000000..c5b3902d --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunFileConfig.tsx @@ -0,0 +1,65 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormUpyunFileConfigFieldValues = Nullish<{ + domain: string; +}>; + +export type DeployNodeConfigFormUpyunFileConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUpyunFileConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUpyunFileConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUpyunFileConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormUpyunFileConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormUpyunFileConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domain: z + .string({ message: t("workflow_node.deploy.form.upyun_file_domain.placeholder") }) + .refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormUpyunFileConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 998ec718..f2a8a2b2 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -266,6 +266,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ UCLOUD_UCDN: `${ACCESS_PROVIDERS.UCLOUD}-ucdn`, UCLOUD_US3: `${ACCESS_PROVIDERS.UCLOUD}-us3`, UPYUN_CDN: `${ACCESS_PROVIDERS.UPYUN}-cdn`, + UPYUN_FILE: `${ACCESS_PROVIDERS.UPYUN}-file`, VOLCENGINE_CDN: `${ACCESS_PROVIDERS.VOLCENGINE}-cdn`, VOLCENGINE_CLB: `${ACCESS_PROVIDERS.VOLCENGINE}-clb`, VOLCENGINE_DCDN: `${ACCESS_PROVIDERS.VOLCENGINE}-dcdn`, @@ -347,6 +348,7 @@ export const deployProvidersMap: Maphttps://console.upyun.com/services/cdn/", + "workflow_node.deploy.form.upyun_file_domain.label": "UPYUN bucket domain", + "workflow_node.deploy.form.upyun_file_domain.placeholder": "Please enter UPYUN bucket domain name", + "workflow_node.deploy.form.upyun_file_domain.tooltip": "For more information, see https://console.upyun.com/services/file/", "workflow_node.deploy.form.volcengine_cdn_domain.label": "VolcEngine CDN domain", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "Please enter VolcEngine CDN domain name", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "For more information, see https://console.volcengine.com/cdn/homepage", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 2e830cf3..03c6aaee 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -95,6 +95,7 @@ "provider.ucloud.us3": "优刻得 - 对象存储 US3", "provider.upyun": "又拍云", "provider.upyun.cdn": "又拍云 - 云分发 CDN", + "provider.upyun.file": "又拍云 - 云存储", "provider.volcengine": "火山引擎", "provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 63d881f8..5880613d 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -511,6 +511,9 @@ "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/cdn/", + "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储加速域名", + "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储加速域名", + "workflow_node.deploy.form.upyun_file_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/file/", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage", From ef22d9d07b69b47d2ad835b253b76fcc10640cf6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 21:56:29 +0800 Subject: [PATCH 07/13] feat: add qiniu kodo deployer --- internal/deployer/providers.go | 4 ++-- internal/domain/provider.go | 1 + ui/src/components/workflow/node/DeployNodeConfigForm.tsx | 9 +++++++++ ui/src/domain/provider.ts | 2 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/en/nls.workflow.nodes.json | 3 +++ ui/src/i18n/locales/zh/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.workflow.nodes.json | 3 +++ 8 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 2508fe5c..308b5b43 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -563,7 +563,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { return deployer, err } - case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili: + case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuKodo, domain.DeployProviderTypeQiniuPili: { access := domain.AccessConfigForQiniu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -571,7 +571,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeQiniuCDN: + case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuKodo: deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 472d8aeb..5016a238 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -145,6 +145,7 @@ const ( DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") DeployProviderTypeLocal = DeployProviderType("local") DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") + DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo") DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") DeployProviderTypeSafeLine = DeployProviderType("safeline") DeployProviderTypeSSH = DeployProviderType("ssh") diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 4f05c1c8..3e791bf6 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -49,6 +49,7 @@ import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudV import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig"; import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig"; import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig"; +import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig"; import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; @@ -64,6 +65,8 @@ import DeployNodeConfigFormTencentCloudVODConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudWAFConfig from "./DeployNodeConfigFormTencentCloudWAFConfig"; import DeployNodeConfigFormUCloudUCDNConfig from "./DeployNodeConfigFormUCloudUCDNConfig.tsx"; import DeployNodeConfigFormUCloudUS3Config from "./DeployNodeConfigFormUCloudUS3Config.tsx"; +import DeployNodeConfigFormUpyunCDNConfig from "./DeployNodeConfigFormUpyunCDNConfig.tsx"; +import DeployNodeConfigFormUpyunFileConfig from "./DeployNodeConfigFormUpyunFileConfig.tsx"; import DeployNodeConfigFormVolcEngineCDNConfig from "./DeployNodeConfigFormVolcEngineCDNConfig.tsx"; import DeployNodeConfigFormVolcEngineCLBConfig from "./DeployNodeConfigFormVolcEngineCLBConfig.tsx"; import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolcEngineDCDNConfig.tsx"; @@ -210,6 +213,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.QINIU_CDN: return ; + case DEPLOY_PROVIDERS.QINIU_KODO: + return ; case DEPLOY_PROVIDERS.QINIU_PILI: return ; case DEPLOY_PROVIDERS.SAFELINE: @@ -240,6 +245,10 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.UCLOUD_US3: return ; + case DEPLOY_PROVIDERS.UPYUN_CDN: + return ; + case DEPLOY_PROVIDERS.UPYUN_FILE: + return ; case DEPLOY_PROVIDERS.VOLCENGINE_CDN: return ; case DEPLOY_PROVIDERS.VOLCENGINE_CLB: diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index f2a8a2b2..bb85d802 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -250,6 +250,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`, LOCAL: `${ACCESS_PROVIDERS.LOCAL}`, QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`, + QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`, QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`, SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`, SSH: `${ACCESS_PROVIDERS.SSH}`, @@ -346,6 +347,7 @@ export const deployProvidersMap: Maphttps://portal.qiniu.com/cdn", + "workflow_node.deploy.form.qiniu_kodo_domain.label": "Qiniu Kodo bucket domain", + "workflow_node.deploy.form.qiniu_kodo_domain.placeholder": "Please enter Qiniu Kodo bucket domain name", + "workflow_node.deploy.form.qiniu_kodo_domain.tooltip": "For more information, see https://portal.qiniu.com/kodo", "workflow_node.deploy.form.qiniu_pili_hub.label": "Qiniu Pili hub", "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "Please enter Qiniu Pili hub name", "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "For more information, see https://portal.qiniu.com/hub", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 03c6aaee..2d415277 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -74,6 +74,7 @@ "provider.powerdns": "PowerDNS", "provider.qiniu": "七牛云", "provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN", + "provider.qiniu.kodo": "七牛云 - 对象存储 Kodo", "provider.qiniu.pili": "七牛云 - 视频直播 Pili", "provider.rainyun": "雨云", "provider.safeline": "雷池", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 5880613d..3805b234 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -370,6 +370,9 @@ "workflow_node.deploy.form.qiniu_cdn_domain.label": "七牛云 CDN 加速域名", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "请输入七牛云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/cdn", + "workflow_node.deploy.form.qiniu_kodo_domain.label": "七牛云对象存储加速域名", + "workflow_node.deploy.form.qiniu_kodo_domain.placeholder": "请输入七牛云对象存储加速域名", + "workflow_node.deploy.form.qiniu_kodo_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/kodo", "workflow_node.deploy.form.qiniu_pili_hub.label": "七牛云视频直播空间名", "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "请输入七牛云视频直播空间名", "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "这是什么?请参阅 https://portal.qiniu.com/hub", From 347d1662504980c526b6d1930bbc68f5a37737e0 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 22:01:01 +0800 Subject: [PATCH 08/13] feat: add aliyun cas, tencentcloud ssl, aws acm, azure keyvault deployer --- internal/deployer/providers.go | 56 ++++++++++++- internal/domain/provider.go | 4 + .../aliyun-cas-deploy/aliyun_cas_deploy.go | 15 ++-- .../providers/aliyun-cas/aliyun_cas.go | 72 +++++++++++++++++ .../deployer/providers/aws-acm/aws_acm.go | 72 +++++++++++++++++ .../azure-keyvault/azure_keyvault.go | 78 +++++++++++++++++++ .../tencentcloud-ssl/tencentcloud_ssl.go | 69 ++++++++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 9 +++ .../node/DeployNodeConfigFormAWSACMConfig.tsx | 58 ++++++++++++++ .../DeployNodeConfigFormAliyunCASConfig.tsx | 64 +++++++++++++++ ...eployNodeConfigFormAzureKeyVaultConfig.tsx | 64 +++++++++++++++ .../DeployNodeConfigFormQiniuKodoConfig.tsx | 65 ++++++++++++++++ ui/src/domain/provider.ts | 40 ++++++---- ui/src/i18n/locales/en/nls.provider.json | 4 + .../i18n/locales/en/nls.workflow.nodes.json | 9 +++ ui/src/i18n/locales/zh/nls.provider.json | 4 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 +++ 17 files changed, 663 insertions(+), 29 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/aliyun-cas/aliyun_cas.go create mode 100644 internal/pkg/core/deployer/providers/aws-acm/aws_acm.go create mode 100644 internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-ssl/tencentcloud_ssl.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormQiniuKodoConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 308b5b43..a83fb681 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -9,6 +9,7 @@ import ( p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console" p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site" pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" + pAliyunCAS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas" pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy" pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" pAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" @@ -20,7 +21,9 @@ import ( pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" pAliyunVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-vod" pAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" + pAWSACM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-acm" pAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" + pAzureKeyVault "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/azure-keyvault" pBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn" pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console" @@ -51,6 +54,7 @@ import ( pTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" pTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo" pTencentCloudSCF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-scf" + pTencentCloudSSL "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ssl" pTencentCloudSSLDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy" pTencentCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-vod" pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf" @@ -105,7 +109,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: + case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -125,6 +129,14 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { }) return deployer, err + case domain.DeployProviderTypeAliyunCAS: + deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maputil.GetString(options.ProviderDeployConfig, "region"), + }) + return deployer, err + case domain.DeployProviderTypeAliyunCASDeploy: deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{ AccessKeyId: access.AccessKeyId, @@ -237,7 +249,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAWSCloudFront: + case domain.DeployProviderTypeAWSACM, domain.DeployProviderTypeAWSCloudFront: { access := domain.AccessConfigForAWS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -245,6 +257,14 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { + case domain.DeployProviderTypeAWSACM: + deployer, err := pAWSACM.NewDeployer(&pAWSACM.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Region: maputil.GetString(options.ProviderDeployConfig, "region"), + }) + return deployer, err + case domain.DeployProviderTypeAWSCloudFront: deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ AccessKeyId: access.AccessKeyId, @@ -259,6 +279,29 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } + case domain.DeployProviderTypeAzureKeyVault: + { + access := domain.AccessConfigForAzure{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderTypeAzureKeyVault: + deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{ + TenantId: access.TenantId, + ClientId: access.ClientId, + ClientSecret: access.ClientSecret, + CloudName: access.CloudName, + KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"), + }) + return deployer, err + + default: + break + } + } + case domain.DeployProviderTypeBaiduCloudCDN: { access := domain.AccessConfigForBaiduCloud{} @@ -638,7 +681,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { return deployer, err } - case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSCF, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF: + case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSCF, domain.DeployProviderTypeTencentCloudSSL, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF: { access := domain.AccessConfigForTencentCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -710,6 +753,13 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { }) return deployer, err + case domain.DeployProviderTypeTencentCloudSSL: + deployer, err := pTencentCloudSSL.NewDeployer(&pTencentCloudSSL.DeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + }) + return deployer, err + case domain.DeployProviderTypeTencentCloudSSLDeploy: deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 5016a238..6e0808ce 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -113,6 +113,7 @@ const ( DeployProviderType1PanelConsole = DeployProviderType("1panel-console") DeployProviderType1PanelSite = DeployProviderType("1panel-site") DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") + DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas") DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") @@ -124,7 +125,9 @@ const ( DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") DeployProviderTypeAliyunVOD = DeployProviderType("aliyun-vod") DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") + DeployProviderTypeAWSACM = DeployProviderType("aws-acm") DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") + DeployProviderTypeAzureKeyVault = DeployProviderType("azure-keyvault") DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn") DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console") @@ -156,6 +159,7 @@ const ( DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf") + DeployProviderTypeTencentCloudSSL = DeployProviderType("tencentcloud-ssl") DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy") DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod") DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") diff --git a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index 4a95e5ad..7c53358d 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -51,7 +51,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: config.Region, + }) if err != nil { return nil, xerrors.Wrap(err, "failed to create ssl uploader") } @@ -178,12 +182,3 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl return client, nil } - -func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { - uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ - AccessKeyId: accessKeyId, - AccessKeySecret: accessKeySecret, - Region: region, - }) - return uploader, err -} diff --git a/internal/pkg/core/deployer/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/deployer/providers/aliyun-cas/aliyun_cas.go new file mode 100644 index 00000000..e00d3788 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cas/aliyun_cas.go @@ -0,0 +1,72 @@ +package aliyuncas + +import ( + "context" + "log/slog" + + xerrors "github.com/pkg/errors" + + "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/aliyun-cas" +) + +type DeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go b/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go new file mode 100644 index 00000000..88482de3 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go @@ -0,0 +1,72 @@ +package awsacm + +import ( + "context" + "log/slog" + + xerrors "github.com/pkg/errors" + + "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/aws-acm" +) + +type DeployerConfig struct { + // AWS AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // AWS SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // AWS 区域。 + Region string `json:"region"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 ACM + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go b/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go new file mode 100644 index 00000000..4439aa68 --- /dev/null +++ b/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go @@ -0,0 +1,78 @@ +package azurekeyvault + +import ( + "context" + "log/slog" + + xerrors "github.com/pkg/errors" + + "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/azure-keyvault" +) + +type DeployerConfig struct { + // Azure TenantId。 + TenantId string `json:"tenantId"` + // Azure ClientId。 + ClientId string `json:"clientId"` + // Azure ClientSecret。 + ClientSecret string `json:"clientSecret"` + // Azure 主权云环境。 + CloudName string `json:"cloudName,omitempty"` + // Key Vault 名称。 + KeyVaultName string `json:"keyvaultName"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + TenantId: config.TenantId, + ClientId: config.ClientId, + ClientSecret: config.ClientSecret, + CloudName: config.CloudName, + KeyVaultName: config.KeyVaultName, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 KeyVault + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl/tencentcloud_ssl.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl/tencentcloud_ssl.go new file mode 100644 index 00000000..8f8676de --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl/tencentcloud_ssl.go @@ -0,0 +1,69 @@ +package tencentcloudssl + +import ( + "context" + "log/slog" + + xerrors "github.com/pkg/errors" + + "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/tencentcloud-ssl" +) + +type DeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + return &deployer.DeployResult{}, nil +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 3e791bf6..6b7a6547 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -18,6 +18,7 @@ import { useWorkflowStore } from "@/stores/workflow"; import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig"; import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig"; import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig"; +import DeployNodeConfigFormAliyunCASConfig from "./DeployNodeConfigFormAliyunCASConfig"; import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig"; import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; import DeployNodeConfigFormAliyunCLBConfig from "./DeployNodeConfigFormAliyunCLBConfig"; @@ -29,7 +30,9 @@ import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLB import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; import DeployNodeConfigFormAliyunVODConfig from "./DeployNodeConfigFormAliyunVODConfig"; import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; +import DeployNodeConfigFormAWSACMConfig from "./DeployNodeConfigFormAWSACMConfig"; import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig"; +import DeployNodeConfigFormAzureKeyVaultConfig from "./DeployNodeConfigFormAzureKeyVaultConfig"; import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig"; import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig"; import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig"; @@ -151,6 +154,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_ALB: return ; + case DEPLOY_PROVIDERS.ALIYUN_CAS: + return ; case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY: return ; case DEPLOY_PROVIDERS.ALIYUN_CLB: @@ -173,8 +178,12 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_WAF: return ; + case DEPLOY_PROVIDERS.AWS_ACM: + return ; case DEPLOY_PROVIDERS.AWS_CLOUDFRONT: return ; + case DEPLOY_PROVIDERS.AZURE_KEYVAULT: + return ; case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN: return ; case DEPLOY_PROVIDERS.BAISHAN_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx new file mode 100644 index 00000000..60b49f54 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx @@ -0,0 +1,58 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{ + region: string; +}>; + +export type DeployNodeConfigFormAWSACMConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAWSACMConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAWSACMConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAWSACMConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAWSACMConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAWSACMConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASConfig.tsx new file mode 100644 index 00000000..f4aed907 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASConfig.tsx @@ -0,0 +1,64 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAliyunCASConfigFieldValues = Nullish<{ + region: string; +}>; + +export type DeployNodeConfigFormAliyunCASConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunCASConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunCASConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAliyunCASConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunCASConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAliyunCASConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_cas_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_cas_region.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunCASConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx new file mode 100644 index 00000000..91d48cdf --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx @@ -0,0 +1,64 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAzureKeyVaultConfigFieldValues = Nullish<{ + keyvaultName: string; +}>; + +export type DeployNodeConfigFormAzureKeyVaultConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAzureKeyVaultConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAzureKeyVaultConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAzureKeyVaultConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAzureKeyVaultConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAzureKeyVaultConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + keyvaultName: z + .string({ message: t("workflow_node.deploy.form.azure_keyvault_name.placeholder") }) + .nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAzureKeyVaultConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormQiniuKodoConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuKodoConfig.tsx new file mode 100644 index 00000000..e7a7dfb7 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuKodoConfig.tsx @@ -0,0 +1,65 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormQiniuKodoConfigFieldValues = Nullish<{ + domain: string; +}>; + +export type DeployNodeConfigFormQiniuKodoConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormQiniuKodoConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormQiniuKodoConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormQiniuKodoConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormQiniuKodoConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormQiniuKodoConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domain: z + .string({ message: t("workflow_node.deploy.form.qiniu_kodo_domain.placeholder") }) + .refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormQiniuKodoConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index bb85d802..b27c23da 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -63,9 +63,9 @@ export type AccessProvider = { export const accessProvidersMap: Map = new Map( /* - 注意:此处的顺序决定显示在前端的顺序。 - NOTICE: The following order determines the order displayed at the frontend. - */ + 注意:此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ [ [ACCESS_PROVIDERS.LOCAL, "provider.local", "/imgs/providers/local.svg", [ACCESS_USAGES.DEPLOY]], [ACCESS_PROVIDERS.SSH, "provider.ssh", "/imgs/providers/ssh.svg", [ACCESS_USAGES.DEPLOY]], @@ -78,6 +78,7 @@ export const accessProvidersMap: Map = new Map( /* - 注意:此处的顺序决定显示在前端的顺序。 - NOTICE: The following order determines the order displayed at the frontend. - */ + 注意:此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ [ [APPLY_DNS_PROVIDERS.ALIYUN_DNS, "provider.aliyun.dns"], [APPLY_DNS_PROVIDERS.TENCENTCLOUD_DNS, "provider.tencentcloud.dns"], @@ -211,13 +211,14 @@ export const applyDNSProvidersMap: Map = new Map( /* - 注意:此处的顺序决定显示在前端的顺序。 - NOTICE: The following order determines the order displayed at the frontend. - */ + 注意:此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ [ [DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER], @@ -322,6 +326,7 @@ export const deployProvidersMap: Maphttps://slb.console.aliyun.com/alb", + "workflow_node.deploy.form.aliyun_cas_region.label": "Alibaba Cloud CAS region", + "workflow_node.deploy.form.aliyun_cas_region.placeholder": "Please enter Alibaba Cloud CAS region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cas_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/endpoints", "workflow_node.deploy.form.aliyun_cas_deploy.guide": "TIPS: You need to go to the Alibaba Cloud console to check the actual deployment results by yourself, because Alibaba Cloud deployment tasks are running asynchronously.", "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "Alibaba Cloud CAS region", "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "Please enter Alibaba Cloud CAS region (e.g. cn-hangzhou)", @@ -207,12 +210,18 @@ "workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF domain (Optional)", "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF domain name", "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "For more information, see https://waf.console.aliyun.com", + "workflow_node.deploy.form.aws_acm_region.label": "AWS ACM Region", + "workflow_node.deploy.form.aws_acm_region.placeholder": "Please enter AWS ACM region (e.g. us-east-1)", + "workflow_node.deploy.form.aws_acm_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront Region", "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS CloudFront region (e.g. us-east-1)", "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront distribution ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "Please enter AWS CloudFront distribution ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", + "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault name", + "workflow_node.deploy.form.azure_keyvault_name.placeholder": "Please enter Azure KeyVault name", + "workflow_node.deploy.form.azure_keyvault_name.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see https://console.bce.baidu.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 2d415277..d400cfbc 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -5,6 +5,7 @@ "provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", + "provider.aliyun.cas": "阿里云 - 数字证书管理服务 CAS", "provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", "provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN", "provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", @@ -20,10 +21,12 @@ "provider.akamai": "Akamai", "provider.akamai.cdn": "Akamai - 内容分发网络 CDN", "provider.aws": "AWS", + "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", "provider.aws.route53": "AWS - Route53", "provider.azure": "Azure", "provider.azure.dns": "Azure - DNS", + "provider.azure.keyvault": "Azure - KeyVault", "provider.baiducloud": "百度智能云", "provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN", "provider.baiducloud.dns": "百度智能云 - 智能云解析 DNS", @@ -88,6 +91,7 @@ "provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", "provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", "provider.tencentcloud.scf": "腾讯云 - 云函数 SCF", + "provider.tencentcloud.ssl": "腾讯云 - SSL 证书服务", "provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务", "provider.tencentcloud.vod": "腾讯云 - 云点播 VOD", "provider.tencentcloud.waf": "腾讯云 - Web 应用防火墙 WAF", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 3805b234..aecc8599 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -110,6 +110,9 @@ "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名(支持泛域名)", "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

不填写时,将替换监听器的默认证书。", + "workflow_node.deploy.form.aliyun_cas_region.label": "阿里云 CAS 服务地域", + "workflow_node.deploy.form.aliyun_cas_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cas_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints", "workflow_node.deploy.form.aliyun_cas_deploy.guide": "小贴士:由于阿里云证书部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "阿里云 CAS 服务地域", "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", @@ -207,12 +210,18 @@ "workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 接入域名(可选)", "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 接入域名(支持泛域名)", "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "这是什么?请参阅 waf.console.aliyun.com

不填写时,将替换实例的默认证书。", + "workflow_node.deploy.form.aws_acm_region.label": "AWS ACM 服务区域", + "workflow_node.deploy.form.aws_acm_region.placeholder": "请输入 AWS ACM 服务区域(例如:us-east-1)", + "workflow_node.deploy.form.aws_acm_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront 服务区域", "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS CloudFront 服务区域(例如:us-east-1)", "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", + "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault 名称", + "workflow_node.deploy.form.azure_keyvault_name.placeholder": "请输入 Azure KeyVault 名称", + "workflow_node.deploy.form.azure_keyvault_name.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn", From da6526d5faea1b97efb6709b50e1629f2aa5854f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 23:11:43 +0800 Subject: [PATCH 09/13] feat: add dynv6 dns-01 applicant --- go.mod | 2 + go.sum | 5 + internal/applicant/providers.go | 16 ++ internal/domain/access.go | 4 + internal/domain/provider.go | 3 +- .../acme-dns-01/lego-providers/dynv6/dnsla.go | 37 ++++ .../lego-providers/dynv6/internal/lego.go | 167 ++++++++++++++++++ ui/public/imgs/providers/dynv6.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormDynv6Config.tsx | 61 +++++++ ui/src/domain/access.ts | 5 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 3 + ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 3 + ui/src/i18n/locales/zh/nls.provider.json | 1 + 16 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/dnsla.go create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go create mode 100644 ui/public/imgs/providers/dynv6.svg create mode 100644 ui/src/components/access/AccessFormDynv6Config.tsx diff --git a/go.mod b/go.mod index 123c69ae..389cdaef 100644 --- a/go.mod +++ b/go.mod @@ -99,6 +99,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/libdns/dynv6 v1.0.0 // indirect + github.com/libdns/libdns v0.2.3 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect diff --git a/go.sum b/go.sum index 04555b1e..bce17d98 100644 --- a/go.sum +++ b/go.sum @@ -646,6 +646,11 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/libdns/dynv6 v1.0.0 h1:JpOK9TYRTHETAe+SIw3lk8SgUi3eD250GK+4fAHu4ys= +github.com/libdns/dynv6 v1.0.0/go.mod h1:65PL/bAlyH0J+0WGlOJYnMpoIuXcg/FmW4dTBYWtYUU= +github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8= +github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 0dbf8844..dcfc1dde 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -15,6 +15,7 @@ import ( pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud" pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla" + pDynv6 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6" pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore" pGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname" pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy" @@ -186,6 +187,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeDynv6: + { + access := domain.AccessConfigForDynv6{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pDynv6.NewChallengeProvider(&pDynv6.ChallengeProviderConfig{ + HttpToken: access.HttpToken, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeGcore: { access := domain.AccessConfigForGcore{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 963d4083..f19a0871 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -108,6 +108,10 @@ type AccessConfigForDogeCloud struct { SecretKey string `json:"secretKey"` } +type AccessConfigForDynv6 struct { + HttpToken string `json:"httpToken"` +} + type AccessConfigForEdgio struct { ClientId string `json:"clientId"` ClientSecret string `json:"clientSecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 6e0808ce..addb3c87 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -28,7 +28,7 @@ const ( AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留) AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") - AccessProviderTypeDynv6 = AccessProviderType("dynv6") // dynv6(预留) + AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) AccessProviderTypeGname = AccessProviderType("gname") @@ -80,6 +80,7 @@ const ( ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud") ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla") + ApplyDNSProviderTypeDynv6 = ApplyDNSProviderType("dynv6") ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore") ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname") ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/dnsla.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/dnsla.go new file mode 100644 index 00000000..e5a1ea3c --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/dnsla.go @@ -0,0 +1,37 @@ +package dynv6 + +import ( + "time" + + "github.com/go-acme/lego/v4/challenge" + + internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal" +) + +type ChallengeProviderConfig struct { + HttpToken string `json:"httpToken"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) { + if config == nil { + panic("config is nil") + } + + providerConfig := internal.NewDefaultConfig() + providerConfig.HTTPToken = config.HttpToken + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := internal.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go new file mode 100644 index 00000000..f83949a2 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go @@ -0,0 +1,167 @@ +package lego_dynv6 + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/libdns/dynv6" + "github.com/libdns/libdns" +) + +const ( + envNamespace = "DYNV6_" + + EnvHTTPToken = envNamespace + "HTTP_TOKEN" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +type Config struct { + HTTPToken string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int +} + +type DNSProvider struct { + client *dynv6.Provider + config *Config +} + +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + } +} + +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvHTTPToken) + if err != nil { + return nil, fmt.Errorf("dynv6: %w", err) + } + + config := NewDefaultConfig() + config.HTTPToken = values[EnvHTTPToken] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dynv6: the configuration of the DNS provider is nil") + } + + client := &dynv6.Provider{Token: config.HTTPToken} + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + return nil +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil { + return fmt.Errorf("dynv6: %w", err) + } + + return nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) getDNSRecord(zoneName, subDomain string) (*libdns.Record, error) { + records, err := d.client.GetRecords(context.Background(), zoneName) + if err != nil { + return nil, err + } + + for _, record := range records { + if record.Type == "TXT" && record.Name == subDomain { + return &record, nil + } + } + + return nil, nil +} + +func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error { + record, err := d.getDNSRecord(zoneName, subDomain) + if err != nil { + return err + } + + if record == nil { + record = &libdns.Record{ + Type: "TXT", + Name: subDomain, + Value: value, + TTL: time.Duration(d.config.TTL) * time.Second, + } + _, err := d.client.AppendRecords(context.Background(), zoneName, []libdns.Record{*record}) + return err + } else { + record.Value = value + _, err := d.client.SetRecords(context.Background(), zoneName, []libdns.Record{*record}) + return err + } +} + +func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error { + record, err := d.getDNSRecord(zoneName, subDomain) + if err != nil { + return err + } + + if record == nil { + return nil + } else { + _, err = d.client.DeleteRecords(context.Background(), zoneName, []libdns.Record{*record}) + return err + } +} diff --git a/ui/public/imgs/providers/dynv6.svg b/ui/public/imgs/providers/dynv6.svg new file mode 100644 index 00000000..652e4e45 --- /dev/null +++ b/ui/public/imgs/providers/dynv6.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index caf5c605..63a66875 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -25,6 +25,7 @@ import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig"; import AccessFormDNSLAConfig from "./AccessFormDNSLAConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; +import AccessFormDynv6Config from "./AccessFormDynv6Config"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; import AccessFormGnameConfig from "./AccessFormGnameConfig"; @@ -133,6 +134,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.DOGECLOUD: return ; + case ACCESS_PROVIDERS.DYNV6: + return ; case ACCESS_PROVIDERS.GCORE: return ; case ACCESS_PROVIDERS.GNAME: diff --git a/ui/src/components/access/AccessFormDynv6Config.tsx b/ui/src/components/access/AccessFormDynv6Config.tsx new file mode 100644 index 00000000..92385302 --- /dev/null +++ b/ui/src/components/access/AccessFormDynv6Config.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForDynv6 } from "@/domain/access"; + +type AccessFormDynv6ConfigFieldValues = Nullish; + +export type AccessFormDynv6ConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormDynv6ConfigFieldValues; + onValuesChange?: (values: AccessFormDynv6ConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormDynv6ConfigFieldValues => { + return { + httpToken: "", + }; +}; + +const AccessFormDynv6Config = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormDynv6ConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + httpToken: z + .string() + .min(1, t("access.form.dynv6_http_token.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessFormDynv6Config; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 59a41cc6..8c011857 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -22,6 +22,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForCMCCCloud | AccessConfigForDNSLA | AccessConfigForDogeCloud + | AccessConfigForDynv6 | AccessConfigForEdgio | AccessConfigForGcore | AccessConfigForGname @@ -132,6 +133,10 @@ export type AccessConfigForDogeCloud = { secretKey: string; }; +export type AccessConfigForDynv6 = { + httpToken: string; +}; + export type AccessConfigForEdgio = { clientId: string; clientSecret: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index b27c23da..999ae22d 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -20,6 +20,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ CMCCCLOUD: "cmcccloud", DNSLA: "dnsla", DOGECLOUD: "dogecloud", + DYNV6: "dynv6", GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", @@ -97,6 +98,7 @@ export const accessProvidersMap: Maphttps://console.dogecloud.com/", + "access.form.dynv6_http_token.label": "dynv6 HTTP token", + "access.form.dynv6_http_token.placeholder": "Please enter dynv6 HTTP token", + "access.form.dynv6_http_token.tooltip": "For more information, see https://dynv6.com/keys", "access.form.edgio_client_id.label": "Edgio ClientId", "access.form.edgio_client_id.placeholder": "Please enter Edgio ClientId", "access.form.edgio_client_id.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 88219c27..d68d813f 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -47,6 +47,7 @@ "provider.dnsla": "DNS.LA", "provider.dogecloud": "Doge Cloud", "provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", + "provider.dynv6": "dynv6", "provider.edgio": "Edgio", "provider.edgio.applications": "Edgio - Applications", "provider.fastly": "Fastly", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index bb2f829a..12c10595 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -132,6 +132,9 @@ "access.form.dogecloud_secret_key.label": "多吉云 SecretKey", "access.form.dogecloud_secret_key.placeholder": "请输入多吉云 SecretKey", "access.form.dogecloud_secret_key.tooltip": "这是什么?请参阅 https://console.dogecloud.com/", + "access.form.dynv6_http_token.label": "dynv6 HTTP Token", + "access.form.dynv6_http_token.placeholder": "请输入 dynv6 HTTP Token", + "access.form.dynv6_http_token.tooltip": "这是什么?请参阅 https://dynv6.com/keys", "access.form.edgio_client_id.label": "Edgio 客户端 ID", "access.form.edgio_client_id.placeholder": "请输入 Edgio 客户端 ID", "access.form.edgio_client_id.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index d400cfbc..7b7098f9 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -47,6 +47,7 @@ "provider.dnsla": "DNS.LA", "provider.dogecloud": "多吉云", "provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", + "provider.dynv6": "dynv6", "provider.edgio": "Edgio", "provider.edgio.applications": "Edgio - Applications", "provider.fastly": "Fastly", From 02f806ab99b8e31ebde7af8ecfdd0452e17307e1 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 20 Mar 2025 23:35:37 +0800 Subject: [PATCH 10/13] feat: preset script for backup files on deployment to local and ssh --- .../node/DeployNodeConfigFormLocalConfig.tsx | 72 +++++++++++++++---- .../node/DeployNodeConfigFormSSHConfig.tsx | 52 ++++++++++++-- .../i18n/locales/en/nls.workflow.nodes.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 2 + 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx index bd1cced6..1ca2fe8c 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx @@ -134,7 +134,24 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i } }; - const handlePresetScriptClick = (key: string) => { + const handlePresetPreScriptClick = (key: string) => { + switch (key) { + case "backup_files": + { + formInst.setFieldValue("shellEnv", SHELLENV_SH); + formInst.setFieldValue( + "preCommand", + `# 请将以下路径替换为实际值 +cp "${formInst.getFieldValue("certPath")}" "${formInst.getFieldValue("certPath")}.bak" 2>/dev/null || : +cp "${formInst.getFieldValue("keyPath")}" "${formInst.getFieldValue("keyPath")}.bak" 2>/dev/null || : + `.trim() + ); + } + break; + } + }; + + const handlePresetPostScriptClick = (key: string) => { switch (key) { case "reload_nginx": { @@ -149,8 +166,8 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i formInst.setFieldValue( "postCommand", `# 请将以下变量替换为实际值 -$pfxPath = "" # PFX 文件路径 -$pfxPassword = "" # PFX 密码 +$pfxPath = "${formInst.getFieldValue("certPath")}" # PFX 文件路径 +$pfxPassword = "${formInst.getFieldValue("pfxPassword")}" # PFX 密码 $siteName = "" # IIS 网站名称 $domain = "" # 域名 $ipaddr = "" # 绑定 IP,“*”表示所有 IP 绑定 @@ -186,8 +203,8 @@ Remove-Item -Path "$pfxPath" -Force formInst.setFieldValue( "postCommand", `# 请将以下变量替换为实际值 -$pfxPath = "" # PFX 文件路径 -$pfxPassword = "" # PFX 密码 +$pfxPath = "${formInst.getFieldValue("certPath")}" # PFX 文件路径 +$pfxPassword = "${formInst.getFieldValue("pfxPassword")}" # PFX 密码 $ipaddr = "" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名。 $port = "" # 绑定端口 @@ -208,14 +225,15 @@ Remove-Item -Path "$pfxPath" -Force ); } break; + case "binding_rdp": { formInst.setFieldValue("shellEnv", SHELLENV_POWERSHELL); formInst.setFieldValue( "postCommand", `# 请将以下变量替换为实际值 -$pfxPath = "" # PFX 文件路径 -$pfxPassword = "" # PFX 密码 +$pfxPath = "${formInst.getFieldValue("certPath")}" # PFX 文件路径 +$pfxPassword = "${formInst.getFieldValue("pfxPassword")}" # PFX 密码 # 导入证书到本地计算机的个人存储区 $cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable @@ -332,8 +350,36 @@ Set-ItemProperty -Path $rdpCertPath -Name "SSLCertificateSHA1Hash" -Value "$thum
- - + + + + + @@ -349,22 +395,22 @@ Set-ItemProperty -Path $rdpCertPath -Name "SSLCertificateSHA1Hash" -Value "$thum { key: "reload_nginx", label: t("workflow_node.deploy.form.local_preset_scripts.option.reload_nginx.label"), - onClick: () => handlePresetScriptClick("reload_nginx"), + onClick: () => handlePresetPostScriptClick("reload_nginx"), }, { key: "binding_iis", label: t("workflow_node.deploy.form.local_preset_scripts.option.binding_iis.label"), - onClick: () => handlePresetScriptClick("binding_iis"), + onClick: () => handlePresetPostScriptClick("binding_iis"), }, { key: "binding_netsh", label: t("workflow_node.deploy.form.local_preset_scripts.option.binding_netsh.label"), - onClick: () => handlePresetScriptClick("binding_netsh"), + onClick: () => handlePresetPostScriptClick("binding_netsh"), }, { key: "binding_rdp", label: t("workflow_node.deploy.form.local_preset_scripts.option.binding_rdp.label"), - onClick: () => handlePresetScriptClick("binding_rdp"), + onClick: () => handlePresetPostScriptClick("binding_rdp"), }, ], }} diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index 1e176d7b..65a7df7b 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -127,7 +127,23 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini } }; - const handlePresetScriptClick = (key: string) => { + const handlePresetPreScriptClick = (key: string) => { + switch (key) { + case "backup_files": + { + formInst.setFieldValue( + "preCommand", + `# 请将以下路径替换为实际值 +cp "${formInst.getFieldValue("certPath")}" "${formInst.getFieldValue("certPath")}.bak" 2>/dev/null || : +cp "${formInst.getFieldValue("keyPath")}" "${formInst.getFieldValue("keyPath")}.bak" 2>/dev/null || : + `.trim() + ); + } + break; + } + }; + + const handlePresetPostScriptClick = (key: string) => { switch (key) { case "reload_nginx": { @@ -228,8 +244,36 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini