From b7fbb21a96729f97c69d1fe3aaf36a7e93047201 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 6 Sep 2025 00:59:58 +0800 Subject: [PATCH] feat: optimize deployment provider of huaweicloud obs --- .../deployers/sp_huaweicloud_obs.go | 30 +++++++++++++ .../huaweicloud-obs/huaweicloud_obs.go | 44 ++++++++++--------- .../huaweicloud-obs/huaweicloud_obs_test.go | 24 +++++----- ...NodeConfigFieldsProviderHuaweiCloudOBS.tsx | 18 ++++---- ui/src/i18n/locales/en/nls.provider.json | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 18 ++++---- ui/src/i18n/locales/zh/nls.provider.json | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 14 +++--- 8 files changed, 92 insertions(+), 60 deletions(-) create mode 100644 internal/certdeploy/deployers/sp_huaweicloud_obs.go diff --git a/internal/certdeploy/deployers/sp_huaweicloud_obs.go b/internal/certdeploy/deployers/sp_huaweicloud_obs.go new file mode 100644 index 000000000..05f365ce5 --- /dev/null +++ b/internal/certdeploy/deployers/sp_huaweicloud_obs.go @@ -0,0 +1,30 @@ +package deployers + +import ( + "fmt" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core" + huaweicloudobs "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/huaweicloud-obs" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := Registries.Register(domain.DeploymentProviderTypeHuaweiCloudOBS, func(options *ProviderFactoryOptions) (core.SSLDeployer, error) { + credentials := domain.AccessConfigForHuaweiCloud{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := huaweicloudobs.NewSSLDeployerProvider(&huaweicloudobs.SSLDeployerProviderConfig{ + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs.go b/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs.go index 6e8c2fa54..0a580ca1d 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs.go @@ -11,7 +11,6 @@ import ( "fmt" "log/slog" "net/http" - "strings" "time" "github.com/certimate-go/certimate/pkg/core" @@ -22,11 +21,11 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 华为云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 华为云 Bucket 对应的 Endpoint。 - Endpoint string `json:"endpoint"` - // 华为云 OBS 桶名。 + // 华为云区域。 + Region string `json:"region"` + // 存储桶名。 Bucket string `json:"bucket"` - // 自定义域名。 + // 自定义域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -42,8 +41,6 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv return nil, errors.New("the configuration of the ssl deployer provider is nil") } - config.Endpoint = strings.TrimPrefix(strings.TrimPrefix(config.Endpoint, "http://"), "https://") - return &SSLDeployerProvider{ config: config, logger: slog.Default(), @@ -58,14 +55,20 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } } -// REF: https://support.huaweicloud.com/usermanual-obs/obs_06_3200.html -// REF: https://support.huaweicloud.com/api-obs/obs_04_0059.html func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + if d.config.Region == "" { + return nil, fmt.Errorf("config `region` is required") + } + if d.config.Bucket == "" { + return nil, fmt.Errorf("config `bucket` is required") + } if d.config.Domain == "" { return nil, fmt.Errorf("config `domain` is required") } - url := fmt.Sprintf("https://%s.%s/?customdomain=%s", d.config.Bucket, d.config.Endpoint, d.config.Domain) + // REF: https://support.huaweicloud.com/usermanual-obs/obs_06_3200.html + // REF: https://support.huaweicloud.com/api-obs/obs_04_0059.html + url := fmt.Sprintf("https://%s.obs.%s.myhuaweicloud.com/?customdomain=%s", d.config.Bucket, d.config.Region, d.config.Domain) bodyXML := fmt.Sprintf(` %s @@ -78,16 +81,14 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 计算 Content-MD5(Base64 编码) md5sum := md5.Sum([]byte(bodyXML)) - contentMD5 := base64.StdEncoding.EncodeToString(md5sum[:]) - - // 日期 - date := time.Now().UTC().Format(http.TimeFormat) + md5sumEncoded := base64.StdEncoding.EncodeToString(md5sum[:]) // 构造签名字符串 + date := time.Now().UTC().Format(http.TimeFormat) method := "PUT" contentType := "application/xml" canonicalizedResource := fmt.Sprintf("/%s/?customdomain=%s", d.config.Bucket, d.config.Domain) - stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s", method, contentMD5, contentType, date, canonicalizedResource) + stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s", method, md5sumEncoded, contentType, date, canonicalizedResource) // HMAC-SHA1 签名 h := hmac.New(sha1.New, []byte(d.config.SecretAccessKey)) @@ -100,25 +101,26 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 创建请求 req, err := http.NewRequest(method, url, bytes.NewBuffer([]byte(bodyXML))) if err != nil { - return nil, err + return nil, fmt.Errorf("huaweicloud obs api error: %w", err) } - req.Header.Set("Date", date) req.Header.Set("Authorization", authHeader) - req.Header.Set("Content-MD5", contentMD5) + req.Header.Set("Content-MD5", md5sumEncoded) req.Header.Set("Content-Type", contentType) // 请求 resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("huaweicloud obs api error: %w", err) } defer resp.Body.Close() + // 响应 if resp.StatusCode != http.StatusOK { - body := new(bytes.Buffer) + body := &bytes.Buffer{} body.ReadFrom(resp.Body) - return nil, fmt.Errorf("HTTP request failed with status %d: %s", resp.StatusCode, body.String()) + return nil, fmt.Errorf("huaweicloud obs api error: unexpected status code: %d, resp: %s", resp.StatusCode, body.String()) } + return &core.SSLDeployResult{}, nil } diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs_test.go b/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs_test.go index a1d06f004..140fae745 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs_test.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-obs/huaweicloud_obs_test.go @@ -16,19 +16,19 @@ var ( fInputKeyPath string fAccessKeyId string fSecretAccessKey string - fEndpoint string + fRegion string fBucket string fDomain string ) func init() { - argsPrefix := "CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_" + argsPrefix := "CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_" flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") - flag.StringVar(&fEndpoint, argsPrefix+"ENDPOINT", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } @@ -37,13 +37,13 @@ func init() { Shell command to run this test: go test -v ./huaweicloud_obs_test.go -args \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_ACCESSKEYID="your-access-key-id" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_ENDPOINT="https://your-endpoint" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_BUCKET="your-bucket" \ - --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_DOMAIN="example.com" + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_REGION="cn-north-4" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_BUCKET="your-bucket" \ + --CERTIMATE_SSLDEPLOYER_HUAWEICLOUDOBS_DOMAIN="example.com" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -55,7 +55,7 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), - fmt.Sprintf("ENDPOINT: %v", fEndpoint), + fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("BUCKET: %v", fBucket), fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) @@ -63,7 +63,7 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ AccessKeyId: fAccessKeyId, SecretAccessKey: fSecretAccessKey, - Endpoint: fEndpoint, + Region: fRegion, Bucket: fBucket, Domain: fDomain, }) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudOBS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudOBS.tsx index 4406cf1f5..f614d20b4 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudOBS.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudOBS.tsx @@ -20,13 +20,13 @@ const BizDeployNodeConfigFieldsProviderHuaweiCloudOBS = () => { return ( <> } + tooltip={} > - + { const getInitialValues = (): Nullish>> => { return { - endpoint: "", + region: "", bucket: "", domain: "", }; @@ -64,9 +64,9 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) const { t } = i18n; return z.object({ - endpoint: z - .string(t("workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder")) - .nonempty(t("workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder")), + region: z + .string(t("workflow_node.deploy.form.huaweicloud_obs_region.placeholder")) + .nonempty(t("workflow_node.deploy.form.huaweicloud_obs_region.placeholder")), bucket: z .string(t("workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder")) .nonempty(t("workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder")), diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index a0b99edbc..dbc9c4526 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -95,8 +95,8 @@ "provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)", "provider.huaweicloud.dns": "Huawei Cloud - DNS", "provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)", - "provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)", "provider.huaweicloud.obs": "Huawei Cloud - OBS (Object Storage Service)", + "provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)", "provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)", "provider.jdcloud": "JD Cloud", "provider.jdcloud.alb": "JD Cloud - ALB (Application 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 499158bcc..611cb22ff 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -518,15 +518,15 @@ "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "Huawei Cloud CDN domain", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "Please enter Huawei Cloud CDN domain name", "workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "For more information, see https://console-intl.huaweicloud.com/cdn", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.label": "Huawei Cloud OBS Endpoint", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder": "Please enter the Huawei Cloud OBS endpoint (e.g., obs.cn-north-4.myhuaweicloud.com)", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.tooltip": "What is this? Please refer to https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html", - "workflow_node.deploy.form.huaweicloud_obs_bucket.label": "Huawei Cloud COS Bucket Name", - "workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "Please enter the Huawei Cloud COS bucket name", - "workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip": "What is this? Please refer to https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", - "workflow_node.deploy.form.huaweicloud_obs_domain.label": "Huawei Cloud COS Custom Domain", - "workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "Please enter the Huawei Cloud COS custom domain", - "workflow_node.deploy.form.huaweicloud_obs_domain.tooltip": "What is this? Please refer to https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", + "workflow_node.deploy.form.huaweicloud_obs_region.label": "Huawei Cloud OBS region", + "workflow_node.deploy.form.huaweicloud_obs_region.placeholder": "Please enter the Huawei Cloud OBS region (e.g. cn-north-1)", + "workflow_node.deploy.form.huaweicloud_obs_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", + "workflow_node.deploy.form.huaweicloud_obs_bucket.label": "Huawei Cloud OBS bucket name", + "workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "Please enter the Huawei Cloud OBS bucket name", + "workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip": "For more information, see https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", + "workflow_node.deploy.form.huaweicloud_obs_domain.label": "Huawei Cloud OBS custom domain", + "workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "Please enter the Huawei Cloud OBS custom domain name", + "workflow_node.deploy.form.huaweicloud_obs_domain.tooltip": "For more information, see https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", "workflow_node.deploy.form.huaweicloud_elb_region.label": "Huawei Cloud ELB region", "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "Please enter Huawei Cloud ELB region (e.g. cn-north-1)", "workflow_node.deploy.form.huaweicloud_elb_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index a9be81f08..92a0ee0f8 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -95,8 +95,8 @@ "provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN", "provider.huaweicloud.dns": "华为云 - 云解析 DNS", "provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB", + "provider.huaweicloud.obs": "华为云 - 对象储存服务 OBS", "provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM", - "provider.huaweicloud.obs": "华为云 - 对象储存 OBS", "provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF", "provider.jdcloud": "京东云", "provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 090f18a37..100a84a2f 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -516,14 +516,14 @@ "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "请输入华为云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/cdn", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.label": "华为云 OBS 接口端点", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder": "请输入华为云 OBS 接口端点(例如:obs.cn-north-4.myhuaweicloud.com)", - "workflow_node.deploy.form.huaweicloud_obs_endpoint.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html", - "workflow_node.deploy.form.huaweicloud_obs_bucket.label": "华为云 COS 存储桶名", - "workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "请输入华为云 COS 存储桶名", + "workflow_node.deploy.form.huaweicloud_obs_region.label": "华为云 OBS 服务区域", + "workflow_node.deploy.form.huaweicloud_obs_region.placeholder": "请输入华为云 OBS 服务区域(例如:cn-north-1)", + "workflow_node.deploy.form.huaweicloud_obs_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", + "workflow_node.deploy.form.huaweicloud_obs_bucket.label": "华为云 OBS 存储桶名", + "workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "请输入华为云 OBS 存储桶名", "workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", - "workflow_node.deploy.form.huaweicloud_obs_domain.label": "华为云 COS 自定义域名", - "workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "请输入华为云 COS 自定义域名", + "workflow_node.deploy.form.huaweicloud_obs_domain.label": "华为云 OBS 自定义域名", + "workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "请输入华为云 OBS 自定义域名", "workflow_node.deploy.form.huaweicloud_obs_domain.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html", "workflow_node.deploy.form.huaweicloud_elb_region.label": "华为云 ELB 服务区域", "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "请输入华为云 ELB 服务区域(例如:cn-north-1)",