From 07037e8d49c138c738df7fec771eea284c655dfc Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 12 May 2025 23:19:13 +0800 Subject: [PATCH] feat: support replacing old certificate on deployment to gcore cdn --- internal/deployer/providers.go | 5 +- .../providers/baishan-cdn/baishan_cdn.go | 1 + .../deployer/providers/gcore-cdn/gcore_cdn.go | 80 ++++++++++++++----- .../uploader/providers/gcore-cdn/gcore_cdn.go | 18 ++--- .../DeployNodeConfigFormGcoreCDNConfig.tsx | 17 ++++ .../i18n/locales/en/nls.workflow.nodes.json | 7 +- .../i18n/locales/zh/nls.workflow.nodes.json | 7 +- 7 files changed, 101 insertions(+), 34 deletions(-) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 77ed8acb..30cf9d41 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -561,8 +561,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer switch options.Provider { case domain.DeploymentProviderTypeGcoreCDN: deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{ - ApiToken: access.ApiToken, - ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), + ApiToken: access.ApiToken, + ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err diff --git a/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go b/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go index 95c0f9f4..2294ed03 100644 --- a/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go +++ b/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go @@ -63,6 +63,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, errors.New("config `domain` is required") } + // 如果原证书 ID 为空,则新增证书;否则替换证书。 if d.config.CertificateId == "" { // 新增证书 // REF: https://portal.baishancloud.com/track/document/downloadPdf/1441 diff --git a/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go b/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go index 66e56796..af2db885 100644 --- a/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go +++ b/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go @@ -8,8 +8,9 @@ import ( "strconv" "github.com/G-Core/gcorelabscdn-go/gcore" - gprovider "github.com/G-Core/gcorelabscdn-go/gcore/provider" + "github.com/G-Core/gcorelabscdn-go/gcore/provider" "github.com/G-Core/gcorelabscdn-go/resources" + "github.com/G-Core/gcorelabscdn-go/sslcerts" "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" @@ -22,25 +23,33 @@ type DeployerConfig struct { ApiToken string `json:"apiToken"` // CDN 资源 ID。 ResourceId int64 `json:"resourceId"` + // 证书 ID。 + // 选填。 + CertificateId int64 `json:"certificateId,omitempty"` } type DeployerProvider struct { config *DeployerConfig logger *slog.Logger - sdkClient *resources.Service + sdkClients *wSdkClients sslUploader uploader.Uploader } var _ deployer.Deployer = (*DeployerProvider)(nil) +type wSdkClients struct { + Resources *resources.Service + SSLCerts *sslcerts.Service +} + func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { if config == nil { panic("config is nil") } - client, err := createSdkClient(config.ApiToken) + clients, err := createSdkClients(config.ApiToken) if err != nil { - return nil, fmt.Errorf("failed to create sdk client: %w", err) + return nil, fmt.Errorf("failed to create sdk clients: %w", err) } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ @@ -53,7 +62,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { return &DeployerProvider{ config: config, logger: slog.Default(), - sdkClient: client, + sdkClients: clients, sslUploader: uploader, }, nil } @@ -73,17 +82,47 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, errors.New("config `resourceId` is required") } - // 上传证书到 CDN - upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) - if err != nil { - return nil, fmt.Errorf("failed to upload certificate file: %w", err) + // 如果原证书 ID 为空,则创建证书;否则更新证书。 + var cloudCertId int64 + if d.config.CertificateId == 0 { + // 上传证书到 CDN + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + cloudCertId, _ = strconv.ParseInt(upres.CertId, 10, 64) } else { - d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + // 获取证书 + // REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/paths/~1cdn~1sslData~1%7Bssl_id%7D/get + getCertificateDetailResp, err := d.sdkClients.SSLCerts.Get(context.TODO(), d.config.CertificateId) + d.logger.Debug("sdk request 'sslcerts.Get'", slog.Any("sslId", d.config.CertificateId), slog.Any("response", getCertificateDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Get': %w", err) + } + + // 更新证书 + // REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/paths/~1cdn~1sslData~1%7Bssl_id%7D/get + changeCertificateReq := &sslcerts.UpdateRequest{ + Name: getCertificateDetailResp.Name, + Cert: certPEM, + PrivateKey: privkeyPEM, + ValidateRootCA: false, + } + changeCertificateResp, err := d.sdkClients.SSLCerts.Update(context.TODO(), getCertificateDetailResp.ID, changeCertificateReq) + d.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", changeCertificateReq), slog.Any("response", changeCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Update': %w", err) + } + + cloudCertId = changeCertificateResp.ID } // 获取 CDN 资源详情 // REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/paths/~1cdn~1resources~1%7Bresource_id%7D/get - getResourceResp, err := d.sdkClient.Get(context.TODO(), d.config.ResourceId) + getResourceResp, err := d.sdkClients.Resources.Get(context.TODO(), d.config.ResourceId) d.logger.Debug("sdk request 'resources.Get'", slog.Any("resourceId", d.config.ResourceId), slog.Any("response", getResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Get': %w", err) @@ -91,7 +130,6 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 更新 CDN 资源详情 // REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/operation/change_cdn_resource - updateResourceCertId, _ := strconv.ParseInt(upres.CertId, 10, 64) updateResourceReq := &resources.UpdateRequest{ Description: getResourceResp.Description, Active: getResourceResp.Active, @@ -99,7 +137,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE OriginProtocol: getResourceResp.OriginProtocol, SecondaryHostnames: getResourceResp.SecondaryHostnames, SSlEnabled: true, - SSLData: int(updateResourceCertId), + SSLData: int(cloudCertId), ProxySSLEnabled: getResourceResp.ProxySSLEnabled, Options: &gcore.Options{}, } @@ -109,7 +147,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE if getResourceResp.ProxySSLData != 0 { updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData } - updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq) + updateResourceResp, err := d.sdkClients.Resources.Update(context.TODO(), d.config.ResourceId, updateResourceReq) d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Update': %w", err) @@ -118,15 +156,19 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return &deployer.DeployResult{}, nil } -func createSdkClient(apiToken string) (*resources.Service, error) { +func createSdkClients(apiToken string) (*wSdkClients, error) { if apiToken == "" { return nil, errors.New("invalid gcore api token") } - requester := gprovider.NewClient( + requester := provider.NewClient( gcoresdk.BASE_URL, - gprovider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)), + provider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)), ) - service := resources.NewService(requester) - return service, nil + resourcesSrv := resources.NewService(requester) + sslCertsSrv := sslcerts.NewService(requester) + return &wSdkClients{ + Resources: resourcesSrv, + SSLCerts: sslCertsSrv, + }, nil } diff --git a/internal/pkg/core/uploader/providers/gcore-cdn/gcore_cdn.go b/internal/pkg/core/uploader/providers/gcore-cdn/gcore_cdn.go index c5dc5296..5987136e 100644 --- a/internal/pkg/core/uploader/providers/gcore-cdn/gcore_cdn.go +++ b/internal/pkg/core/uploader/providers/gcore-cdn/gcore_cdn.go @@ -7,8 +7,8 @@ import ( "log/slog" "time" - gprovider "github.com/G-Core/gcorelabscdn-go/gcore/provider" - gsslcerts "github.com/G-Core/gcorelabscdn-go/sslcerts" + "github.com/G-Core/gcorelabscdn-go/gcore/provider" + "github.com/G-Core/gcorelabscdn-go/sslcerts" "github.com/usual2970/certimate/internal/pkg/core/uploader" gcoresdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/gcore/common" @@ -22,7 +22,7 @@ type UploaderConfig struct { type UploaderProvider struct { config *UploaderConfig logger *slog.Logger - sdkClient *gsslcerts.Service + sdkClient *sslcerts.Service } var _ uploader.Uploader = (*UploaderProvider)(nil) @@ -59,8 +59,8 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli()) // 新增证书 - // REF: https://api.gcore.com/docs/cdn#tag/CA-certificates/operation/ca_certificates-add - createCertificateReq := &gsslcerts.CreateRequest{ + // REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/operation/add_ssl_certificates + createCertificateReq := &sslcerts.CreateRequest{ Name: certName, Cert: certPEM, PrivateKey: privkeyPEM, @@ -81,15 +81,15 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE }, nil } -func createSdkClient(apiToken string) (*gsslcerts.Service, error) { +func createSdkClient(apiToken string) (*sslcerts.Service, error) { if apiToken == "" { return nil, errors.New("invalid gcore api token") } - requester := gprovider.NewClient( + requester := provider.NewClient( gcoresdk.BASE_URL, - gprovider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)), + provider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)), ) - service := gsslcerts.NewService(requester) + service := sslcerts.NewService(requester) return service, nil } diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx index c06087de..4d548949 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx @@ -5,6 +5,7 @@ import { z } from "zod"; type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{ resourceId: string | number; + certificateId?: string | number; }>; export type DeployNodeConfigFormGcoreCDNConfigProps = { @@ -28,6 +29,13 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled resourceId: z.union([z.string(), z.number()]).refine((v) => { return /^\d+$/.test(v + "") && +v > 0; }, t("workflow_node.deploy.form.gcore_cdn_resource_id.placeholder")), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (!v) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder")), }); const formRule = createSchemaFieldRule(formSchema); @@ -52,6 +60,15 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled > + + } + > + + ); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index da776fc9..0b503461 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -356,6 +356,9 @@ "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "Please enter Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "For more information, see https://cdn.gcore.com/resources/list", + "workflow_node.deploy.form.gcore_cdn_certificate_id.label": "Gcore CDN certificate ID (Optional)", + "workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder": "Please enter Gcore CDN certificate ID", + "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "For more information, see https://cdn.gcore.com/ssl", "workflow_node.deploy.form.goedge_resource_type.label": "Resource type", "workflow_node.deploy.form.goedge_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "Certificate", @@ -441,7 +444,7 @@ "workflow_node.deploy.form.local_format.label": "File format", "workflow_node.deploy.form.local_format.placeholder": "Please select file format", "workflow_node.deploy.form.local_format.option.pem.label": "PEM (*.pem, *.crt, *.key)", - "workflow_node.deploy.form.local_format.option.pfx.label": "PFX (*.pfx)", + "workflow_node.deploy.form.local_format.option.pfx.label": "PFX (*.pfx, *.p12)", "workflow_node.deploy.form.local_format.option.jks.label": "JKS (*.jks)", "workflow_node.deploy.form.local_cert_path.label": "Certificate file saving path", "workflow_node.deploy.form.local_cert_path.placeholder": "Please enter saving path for certificate file", @@ -506,7 +509,7 @@ "workflow_node.deploy.form.ssh_format.label": "File format", "workflow_node.deploy.form.ssh_format.placeholder": "Please select file format", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)", - "workflow_node.deploy.form.ssh_format.option.pfx.label": "PFX (*.pfx)", + "workflow_node.deploy.form.ssh_format.option.pfx.label": "PFX (*.pfx, *.p12)", "workflow_node.deploy.form.ssh_format.option.jks.label": "JKS (*.jks)", "workflow_node.deploy.form.ssh_cert_path.label": "Certificate file uploading path", "workflow_node.deploy.form.ssh_cert_path.placeholder": "Please enter uploading path for certificate file", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 3f38a490..85d2aa6e 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -355,6 +355,9 @@ "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "请输入 Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", + "workflow_node.deploy.form.gcore_cdn_certificate_id.label": "Gcore CDN 原证书 ID(可选)", + "workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder": "请输入 Gcore CDN 原证书 ID", + "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/ssl

不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.goedge_resource_type.label": "证书替换方式", "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", @@ -440,7 +443,7 @@ "workflow_node.deploy.form.local_format.label": "文件格式", "workflow_node.deploy.form.local_format.placeholder": "请选择文件格式", "workflow_node.deploy.form.local_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)", - "workflow_node.deploy.form.local_format.option.pfx.label": "PFX 格式(*.pfx)", + "workflow_node.deploy.form.local_format.option.pfx.label": "PFX 格式(*.pfx, *.p12)", "workflow_node.deploy.form.local_format.option.jks.label": "JKS 格式(*.jks)", "workflow_node.deploy.form.local_cert_path.label": "证书文件保存路径", "workflow_node.deploy.form.local_cert_path.placeholder": "请输入证书文件保存路径", @@ -505,7 +508,7 @@ "workflow_node.deploy.form.ssh_format.label": "文件格式", "workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)", - "workflow_node.deploy.form.ssh_format.option.pfx.label": "PFX 格式(*.pfx)", + "workflow_node.deploy.form.ssh_format.option.pfx.label": "PFX 格式(*.pfx, *.p12)", "workflow_node.deploy.form.ssh_format.option.jks.label": "JKS 格式(*.jks)", "workflow_node.deploy.form.ssh_cert_path.label": "证书文件上传路径", "workflow_node.deploy.form.ssh_cert_path.placeholder": "请输入证书文件上传路径",