From 45e4d148975c3fa027375c01d4358892c66e6388 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 25 Jun 2025 14:32:37 +0800 Subject: [PATCH 01/27] feat: optimize uploading certificates to huaweicloud scm --- internal/domain/workflow.go | 5 +- .../huaweicloud-scm/huaweicloud_scm.go | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 04ba1e4f..16895229 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -70,11 +70,11 @@ type WorkflowNodeConfigForApply struct { ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01 Provider string `json:"provider"` // DNS 提供商 ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID - ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置 + ProviderConfig map[string]any `json:"providerConfig,omitempty"` // DNS 提供商额外配置 CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值时使用全局配置) CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 - KeyAlgorithm string `json:"keyAlgorithm"` // 证书算法 + KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法 ACMEProfile string `json:"acmeProfile,omitempty"` // ACME Profiles Extension Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数 @@ -124,6 +124,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { return WorkflowNodeConfigForApply{ Domains: xmaps.GetString(n.Config, "domains"), ContactEmail: xmaps.GetString(n.Config, "contactEmail"), + ChallengeType: xmaps.GetString(n.Config, "challengeType"), Provider: xmaps.GetString(n.Config, "provider"), ProviderAccessId: xmaps.GetString(n.Config, "providerAccessId"), ProviderConfig: xmaps.GetKVMapAny(n.Config, "providerConfig"), diff --git a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go index 7084aaf2..e60010b8 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" @@ -95,6 +96,17 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if listCertificatesResp.Certificates != nil { for _, certDetail := range *listCertificatesResp.Certificates { + // 先对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certDetail.Domain) { + continue + } + + // 再对比证书有效期 + if certX509.NotAfter.Local().Format(time.DateTime) != strings.TrimSuffix(certDetail.ExpireTime, ".0") { + continue + } + + // 最后对比证书内容 exportCertificateReq := &hcscmmodel.ExportCertificateRequest{ CertificateId: certDetail.Id, } @@ -105,27 +117,27 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey continue } return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err) - } - - var isSameCert bool - if *exportCertificateResp.Certificate == certPEM { - isSameCert = true } else { - oldCertX509, err := xcert.ParseCertificateFromPEM(*exportCertificateResp.Certificate) - if err != nil { - continue + var isSameCert bool + if *exportCertificateResp.Certificate == certPEM { + isSameCert = true + } else { + oldCertX509, err := xcert.ParseCertificateFromPEM(*exportCertificateResp.Certificate) + if err != nil { + continue + } + + isSameCert = xcert.EqualCertificate(certX509, oldCertX509) } - isSameCert = xcert.EqualCertificate(certX509, oldCertX509) - } - - // 如果已存在相同证书,直接返回 - if isSameCert { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certDetail.Id, - CertName: certDetail.Name, - }, nil + // 如果已存在相同证书,直接返回 + if isSameCert { + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certDetail.Id, + CertName: certDetail.Name, + }, nil + } } } } From 64063554c2c9ab928357447c02e1c4b3e381ad69 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 25 Jun 2025 17:07:28 +0800 Subject: [PATCH 02/27] refactor: clean code --- .../providers/huaweicloud-elb/huaweicloud_elb.go | 13 ++++++------- .../providers/aliyun-cas/aliyun_cas.go | 10 +++++----- .../providers/aliyun-slb/aliyun_slb.go | 12 ++++++------ .../providers/byteplus-cdn/byteplus_cdn.go | 10 +++++----- .../providers/huaweicloud-elb/huaweicloud_elb.go | 10 +++++----- .../rainyun-sslcenter/rainyun_sslcenter.go | 10 +++++----- .../providers/ucloud-ussl/ucloud_ussl.go | 16 ++++++++-------- .../providers/volcengine-cdn/volcengine_cdn.go | 10 +++++----- .../providers/volcengine-live/volcengine_live.go | 8 ++++---- .../wangsu-certificate/wangsu_certificate.go | 12 ++++++------ 10 files changed, 55 insertions(+), 56 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go index a6c29e36..6325fbe8 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -297,21 +297,20 @@ func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'elb.ShowCertificate': %w", err) } - for _, certificate := range *listOldCertificateResp.Certificates { - oldCertificate := certificate - newCertificate := showNewCertificateResp.Certificate + for _, oldCertInfo := range *listOldCertificateResp.Certificates { + newCertInfo := showNewCertificateResp.Certificate - if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil { - if slices.Equal(*oldCertificate.SubjectAlternativeNames, *newCertificate.SubjectAlternativeNames) { + if oldCertInfo.SubjectAlternativeNames != nil && newCertInfo.SubjectAlternativeNames != nil { + if slices.Equal(*oldCertInfo.SubjectAlternativeNames, *newCertInfo.SubjectAlternativeNames) { continue } } else { - if oldCertificate.Domain == newCertificate.Domain { + if oldCertInfo.Domain == newCertInfo.Domain { continue } } - sniCertIds = append(sniCertIds, certificate.Id) + sniCertIds = append(sniCertIds, oldCertInfo.Id) } updateListenerReq.Body.Listener.SniContainerRefs = &sniCertIds diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go index bcbecd3b..52e480c4 100644 --- a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go @@ -93,13 +93,13 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listUserCertificateOrderResp.Body.CertificateOrderList != nil { - for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList { - if !strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) { + for _, certOrder := range listUserCertificateOrderResp.Body.CertificateOrderList { + if !strings.EqualFold(certX509.SerialNumber.Text(16), *certOrder.SerialNo) { continue } getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ - CertId: certDetail.CertificateId, + CertId: certOrder.CertificateId, } getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetail(getUserCertificateDetailReq) m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) @@ -123,8 +123,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)), - CertName: *certDetail.Name, + CertId: fmt.Sprintf("%d", tea.Int64Value(certOrder.CertificateId)), + CertName: *certOrder.Name, ExtendedData: map[string]any{ "instanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId), "certIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier), diff --git a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go index eced6360..3bf2aa90 100644 --- a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go +++ b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go @@ -86,16 +86,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil { fingerprint := sha256.Sum256(certX509.Raw) fingerprintHex := hex.EncodeToString(fingerprint[:]) - for _, certDetail := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { - isSameCert := *certDetail.IsAliCloudCertificate == 0 && - strings.EqualFold(fingerprintHex, strings.ReplaceAll(*certDetail.Fingerprint, ":", "")) && - strings.EqualFold(certX509.Subject.CommonName, *certDetail.CommonName) + for _, serverCert := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { + isSameCert := *serverCert.IsAliCloudCertificate == 0 && + strings.EqualFold(fingerprintHex, strings.ReplaceAll(*serverCert.Fingerprint, ":", "")) && + strings.EqualFold(certX509.Subject.CommonName, *serverCert.CommonName) // 如果已存在相同证书,直接返回 if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: *certDetail.ServerCertificateId, - CertName: *certDetail.ServerCertificateName, + CertId: *serverCert.ServerCertificateId, + CertName: *serverCert.ServerCertificateName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go index cc42c749..32a4b6ed 100644 --- a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go @@ -87,17 +87,17 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertInfoResp.Result.CertInfo != nil { - for _, certDetail := range listCertInfoResp.Result.CertInfo { + for _, certInfo := range listCertInfoResp.Result.CertInfo { fingerprintSha1 := sha1.Sum(certX509.Raw) fingerprintSha256 := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) && - strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256) + isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certInfo.CertFingerprint.Sha1) && + strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certInfo.CertFingerprint.Sha256) // 如果已存在相同证书,直接返回 if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.Desc, + CertId: certInfo.CertId, + CertName: certInfo.Desc, }, nil } } diff --git a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go index 131572a3..15ad5e6f 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go @@ -95,12 +95,12 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertificatesResp.Certificates != nil { - for _, certDetail := range *listCertificatesResp.Certificates { + for _, certInfo := range *listCertificatesResp.Certificates { var isSameCert bool - if certDetail.Certificate == certPEM { + if certInfo.Certificate == certPEM { isSameCert = true } else { - oldCertX509, err := xcert.ParseCertificateFromPEM(certDetail.Certificate) + oldCertX509, err := xcert.ParseCertificateFromPEM(certInfo.Certificate) if err != nil { continue } @@ -112,8 +112,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.Id, - CertName: certDetail.Name, + CertId: certInfo.Id, + CertName: certInfo.Name, }, nil } } diff --git a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go index 1fc930d6..50aa64da 100644 --- a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -114,19 +114,19 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin } if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil { - for _, sslItem := range sslCenterListResp.Data.Records { + for _, sslRecord := range sslCenterListResp.Data.Records { // 先对比证书的多域名 - if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") { + if sslRecord.Domain != strings.Join(certX509.DNSNames, ", ") { continue } // 再对比证书的有效期 - if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() { + if sslRecord.StartDate != certX509.NotBefore.Unix() || sslRecord.ExpireDate != certX509.NotAfter.Unix() { continue } // 最后对比证书内容 - sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslItem.ID) + sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslRecord.ID) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err) } @@ -148,7 +148,7 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin // 如果已存在相同证书,直接返回 if isSameCert { return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslItem.ID), + CertId: fmt.Sprintf("%d", sslRecord.ID), }, nil } } diff --git a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go index 66824412..b6a3f851 100644 --- a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go +++ b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go @@ -143,24 +143,24 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin } if getCertificateListResp.CertificateList != nil { - for _, certInfo := range getCertificateListResp.CertificateList { + for _, certItem := range getCertificateListResp.CertificateList { // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书 // 先分别对比证书的多域名、品牌、有效期,再对比签名算法 - if len(certX509.DNSNames) == 0 || certInfo.Domains != strings.Join(certX509.DNSNames, ",") { + if len(certX509.DNSNames) == 0 || certItem.Domains != strings.Join(certX509.DNSNames, ",") { continue } - if len(certX509.Issuer.Organization) == 0 || certInfo.Brand != certX509.Issuer.Organization[0] { + if len(certX509.Issuer.Organization) == 0 || certItem.Brand != certX509.Issuer.Organization[0] { continue } - if int64(certInfo.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certInfo.NotAfter) != certX509.NotAfter.UnixMilli() { + if int64(certItem.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certItem.NotAfter) != certX509.NotAfter.UnixMilli() { continue } getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest() - getCertificateDetailInfoReq.CertificateID = ucloud.Int(certInfo.CertificateID) + getCertificateDetailInfoReq.CertificateID = ucloud.Int(certItem.CertificateID) if m.config.ProjectId != "" { getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId) } @@ -212,10 +212,10 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin } return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", certInfo.CertificateID), - CertName: certInfo.Name, + CertId: fmt.Sprintf("%d", certItem.CertificateID), + CertName: certItem.Name, ExtendedData: map[string]any{ - "resourceId": certInfo.CertificateSN, + "resourceId": certItem.CertificateSN, }, }, nil } diff --git a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go index 9ad13187..fd12f830 100644 --- a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go @@ -88,17 +88,17 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertInfoResp.Result.CertInfo != nil { - for _, certDetail := range listCertInfoResp.Result.CertInfo { + for _, certInfo := range listCertInfoResp.Result.CertInfo { fingerprintSha1 := sha1.Sum(certX509.Raw) fingerprintSha256 := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) && - strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256) + isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certInfo.CertFingerprint.Sha1) && + strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certInfo.CertFingerprint.Sha256) // 如果已存在相同证书,直接返回 if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.Desc, + CertId: certInfo.CertId, + CertName: certInfo.Desc, }, nil } } diff --git a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go index 147a8ec9..2f06683a 100644 --- a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go @@ -70,11 +70,11 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'live.ListCertV2': %w", err) } if listCertResp.Result.CertList != nil { - for _, certDetail := range listCertResp.Result.CertList { + for _, certInfo := range listCertResp.Result.CertList { // 查询证书详细信息 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85 describeCertDetailSecretReq := &velive.DescribeCertDetailSecretV2Body{ - ChainID: ve.String(certDetail.ChainID), + ChainID: ve.String(certInfo.ChainID), } describeCertDetailSecretResp, err := m.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq) m.logger.Debug("sdk request 'live.DescribeCertDetailSecretV2'", slog.Any("request", describeCertDetailSecretReq), slog.Any("response", describeCertDetailSecretResp)) @@ -99,8 +99,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if isSameCert { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.ChainID, - CertName: certDetail.CertName, + CertId: certInfo.ChainID, + CertName: certInfo.CertName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go index d2523c9b..c94d8ca6 100644 --- a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go +++ b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go @@ -71,16 +71,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertificatesResp.Certificates != nil { - for _, certificate := range listCertificatesResp.Certificates { + for _, certRecord := range listCertificatesResp.Certificates { // 对比证书序列号 - if !strings.EqualFold(certX509.SerialNumber.Text(16), certificate.Serial) { + if !strings.EqualFold(certX509.SerialNumber.Text(16), certRecord.Serial) { continue } // 再对比证书有效期 cstzone := time.FixedZone("CST", 8*60*60) - oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certificate.ValidityFrom, cstzone) - oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certificate.ValidityTo, cstzone) + oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityFrom, cstzone) + oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityTo, cstzone) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } @@ -88,8 +88,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certificate.CertificateId, - CertName: certificate.Name, + CertId: certRecord.CertificateId, + CertName: certRecord.Name, }, nil } } From 2db6d5c163737de35d904028f85d1a04afd46b04 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 25 Jun 2025 20:50:18 +0800 Subject: [PATCH 03/27] feat: new deployment provider: kong --- go.mod | 7 + go.sum | 26 +-- internal/deployer/providers.go | 19 +++ internal/domain/access.go | 6 + internal/domain/provider.go | 2 + .../ssl-deployer/providers/kong/consts.go | 8 + pkg/core/ssl-deployer/providers/kong/kong.go | 149 ++++++++++++++++++ .../ssl-deployer/providers/kong/kong_test.go | 77 +++++++++ ui/public/imgs/providers/kong.png | Bin 0 -> 4820 bytes ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormKongConfig.tsx | 71 +++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormKongConfig.tsx | 89 +++++++++++ ui/src/domain/access.ts | 7 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 5 + ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 9 ++ ui/src/i18n/locales/zh/nls.access.json | 5 + ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 ++ 21 files changed, 491 insertions(+), 10 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/kong/consts.go create mode 100644 pkg/core/ssl-deployer/providers/kong/kong.go create mode 100644 pkg/core/ssl-deployer/providers/kong/kong_test.go create mode 100644 ui/public/imgs/providers/kong.png create mode 100644 ui/src/components/access/AccessFormKongConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormKongConfig.tsx diff --git a/go.mod b/go.mod index f9bd4fb6..712a9904 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.3.0 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.155 github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 + github.com/kong/go-kong v0.66.1 github.com/libdns/dynv6 v1.0.0 github.com/libdns/libdns v0.2.3 github.com/luthermonson/go-proxmox v0.2.2 @@ -109,12 +110,15 @@ require ( github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/copier v0.3.4 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/kong/semver/v4 v4.0.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magefile/mage v1.14.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect @@ -127,6 +131,9 @@ require ( github.com/qiniu/x v1.10.5 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index e574635c..149f9151 100644 --- a/go.sum +++ b/go.sum @@ -552,6 +552,8 @@ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.155/go.mod h1:Y/+YLCFCJtS29i2M github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -596,6 +598,10 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kong/go-kong v0.66.1 h1:UVdemzcCpfXEl6O/VHdf0rT2bXdIO5ykuJbf2z1JTko= +github.com/kong/go-kong v0.66.1/go.mod h1:wRMPAXGOB3kn53TF6zN4l2JhIWPUfXDFKNHkMHBB3iQ= +github.com/kong/semver/v4 v4.0.1 h1:DIcNR8W3gfx0KabFBADPalxxsp+q/5COwIFkkhrFQ2Y= +github.com/kong/semver/v4 v4.0.1/go.mod h1:LImQ0oT15pJvSns/hs2laLca2zcYoHu5EsSNY0J6/QA= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -656,6 +662,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -786,6 +794,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= +github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -829,8 +839,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1187 h1:x2q6BAFm2f+9YaE7/lGPWXL7HzRkovjoqOMbdtRdpBw= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1187/go.mod h1:GoIHP0ayv0QOWN4c9aUEaKi74lY/tbeJz7h5i8y2gdU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1193 h1:zOWZKDVA3kvA5/b+AwKzDtz5ewdiibeKxVqtCFJSTNI= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1193/go.mod h1:ufxDBGyS3X/9QKkZzuOFKLNra9FmSfgAHBO/FlFZaTU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1188 h1:zzaIE12soTfyAgRvBYhb5bYxFXRCelvYXDEfvtkT5Y4= @@ -840,26 +848,18 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1163/go.mod github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1172/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1182/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1183/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1187/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1188/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1189/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1191/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1192 h1:3K6aJXXkjBLxqFYnBqAqFW5YqxmwMT0HR2F4gxQiNMU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1192/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1193 h1:anxhOjL4WrQDqUcX7eT8VEaQITiKWllKwsH1fEt6lBw= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1193/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1163 h1:putqrH5n1SVRqFWHOylVqYI5yLQUjRTkHqZPLT2yeVY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1163/go.mod h1:aEWRXlAvovPUUoS3kVB/LVWEQ19WqzTj2lXGvR1YArY= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1192 h1:2430drceaOXASJZyVZ+e7QSzgBfgwSjDEDM5rh4046M= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1192/go.mod h1:JHZLo95Fde/0et2Ag2E5P6VmCZQIq74MClUtanJ4JcY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1193 h1:VtXqRnzGz3KheXu2msNPvA/fUYQGsVVRC30WgyAUEqg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1193/go.mod h1:42I1OwaedHR6Yvg7J6UYoOjNYUYfFqwaeEkvx3x+NZc= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1172 h1:6SUO0hTie3zxnUEMxmhnS1iRIXpAukSZV27Nrx4NwIk= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1172/go.mod h1:tmN4zfu70SD0iee3qfpc09NRLel30zGoAuzIs4X0Kfs= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1189 h1:Db7gmkey7On70PAohvrna6RMLZzLHRjbALxPlH5JC3c= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1189/go.mod h1:x+WlMCjbePO7M3R0qzKmrpmieUWrtsRpcKBDpxJNQ5A= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1193 h1:tmACSthp5JLjrdxzng6XFs4gfQcZHBTTVlXR0tO6hSk= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1193/go.mod h1:LWf5UPUl41EQICrq0jswgQEO/BtRQY+CxAI6X+i709o= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1191 h1:4l1Db+yFh9HgqNynYbG93khxLtXSBwnXZgNmc88jOE0= @@ -868,6 +868,12 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1183 h1:3fvxkF github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1183/go.mod h1:d47RTYqj/2xjIk/lmq8bQ9deUwfEQcWhPQxUgqZnz24= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1182 h1:2DaykFM5mXvQBvuhQEU/aOG5amissS31XI1wZh+FeMA= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1182/go.mod h1:pTAgdVcS28xFIARJXhg10hx2+g/Q9FqVkAkal3ARNfc= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 0b2a77bd..0f33f57f 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -63,6 +63,7 @@ import ( pJDCloudLive "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-live" pJDCloudVOD "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-vod" pK8sSecret "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/k8s-secret" + pKong "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/kong" pLeCDN "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/lecdn" pLocal "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/local" pNetlifySite "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/netlify-site" @@ -924,6 +925,24 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy return deployer, err } + case domain.DeploymentProviderTypeKong: + { + access := domain.AccessConfigForKong{} + if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pKong.NewSSLDeployerProvider(&pKong.SSLDeployerProviderConfig{ + ServerUrl: access.ServerUrl, + ApiToken: access.ApiToken, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pKong.ResourceType(xmaps.GetString(options.ProviderServiceConfig, "resourceType")), + Workspace: xmaps.GetString(options.ProviderServiceConfig, "workspace"), + CertificateId: xmaps.GetString(options.ProviderServiceConfig, "certificateId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeKubernetesSecret: { access := domain.AccessConfigForKubernetes{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 178a9d57..db34cf8a 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -227,6 +227,12 @@ type AccessConfigForJDCloud struct { AccessKeySecret string `json:"accessKeySecret"` } +type AccessConfigForKong struct { + ServerUrl string `json:"serverUrl"` + ApiToken string `json:"apiToken"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForKubernetes struct { KubeConfig string `json:"kubeConfig,omitempty"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 6deedee8..d86faa07 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -52,6 +52,7 @@ const ( AccessProviderTypeHetzner = AccessProviderType("hetzner") AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") AccessProviderTypeJDCloud = AccessProviderType("jdcloud") + AccessProviderTypeKong = AccessProviderType("kong") AccessProviderTypeKubernetes = AccessProviderType("k8s") AccessProviderTypeLarkBot = AccessProviderType("larkbot") AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt") @@ -236,6 +237,7 @@ const ( DeploymentProviderTypeJDCloudCDN = DeploymentProviderType(AccessProviderTypeJDCloud + "-cdn") DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live") DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod") + DeploymentProviderTypeKong = DeploymentProviderType(AccessProviderTypeKong) DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret") DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal) diff --git a/pkg/core/ssl-deployer/providers/kong/consts.go b/pkg/core/ssl-deployer/providers/kong/consts.go new file mode 100644 index 00000000..91b462bb --- /dev/null +++ b/pkg/core/ssl-deployer/providers/kong/consts.go @@ -0,0 +1,8 @@ +package kong + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/pkg/core/ssl-deployer/providers/kong/kong.go b/pkg/core/ssl-deployer/providers/kong/kong.go new file mode 100644 index 00000000..bd325240 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/kong/kong.go @@ -0,0 +1,149 @@ +package kong + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/http" + "net/url" + "strings" + + "github.com/kong/go-kong/kong" + + "github.com/certimate-go/certimate/pkg/core" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xhttp "github.com/certimate-go/certimate/pkg/utils/http" +) + +type SSLDeployerProviderConfig struct { + // Kong 服务地址。 + ServerUrl string `json:"serverUrl"` + // Kong Admin API Token。 + ApiToken string `json:"apiToken"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 工作空间。 + // 选填。 + Workspace string `json:"workspace,omitempty"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId string `json:"certificateId,omitempty"` +} + +type SSLDeployerProvider struct { + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *kong.Client +} + +var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) + +func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the ssl deployer provider is nil") + } + + client, err := createSDKClient(config.ServerUrl, config.Workspace, config.ApiToken, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("could not create sdk client: %w", err) + } + + return &SSLDeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } +} + +func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 解析证书内容 + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + if d.config.Workspace == "" { + // 更新证书 + // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate + updateCertificateReq := &kong.Certificate{ + ID: kong.String(d.config.CertificateId), + Cert: kong.String(certPEM), + Key: kong.String(privkeyPEM), + SNIs: kong.StringSlice(certX509.DNSNames...), + } + updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) + d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) + } + } else { + // 更新证书 + // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate-in-workspace + updateCertificateReq := &kong.Certificate{ + ID: kong.String(d.config.CertificateId), + Cert: kong.String(certPEM), + Key: kong.String(privkeyPEM), + SNIs: kong.StringSlice(certX509.DNSNames...), + } + updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) + d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) + } + } + + return nil +} + +func createSDKClient(serverUrl, workspace, apiKey string, skipTlsVerify bool) (*kong.Client, error) { + httpClient := &http.Client{ + Transport: xhttp.NewDefaultTransport(), + Timeout: http.DefaultClient.Timeout, + } + if skipTlsVerify { + transport := xhttp.NewDefaultTransport() + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + transport.TLSClientConfig.InsecureSkipVerify = true + httpClient.Transport = transport + } + + baseUrl := strings.TrimRight(serverUrl, "/") + if workspace != "" { + baseUrl = fmt.Sprintf("%s/%s", baseUrl, url.PathEscape(workspace)) + } + + return kong.NewClient(kong.String(baseUrl), httpClient) +} diff --git a/pkg/core/ssl-deployer/providers/kong/kong_test.go b/pkg/core/ssl-deployer/providers/kong/kong_test.go new file mode 100644 index 00000000..4d3c2aff --- /dev/null +++ b/pkg/core/ssl-deployer/providers/kong/kong_test.go @@ -0,0 +1,77 @@ +package kong_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/kong" +) + +var ( + fInputCertPath string + fInputKeyPath string + fServerUrl string + fApiToken string + fCertificateId string +) + +func init() { + argsPrefix := "CERTIMATE_SSLDEPLOYER_KONG_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "") + flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./kong_test.go -args \ + --CERTIMATE_SSLDEPLOYER_KONG_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLDEPLOYER_KONG_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLDEPLOYER_KONG_SERVERURL="http://127.0.0.1:9080" \ + --CERTIMATE_SSLDEPLOYER_KONG_APITOKEN="your-admin-token" \ + --CERTIMATE_SSLDEPLOYER_KONG_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SERVERURL: %v", fServerUrl), + fmt.Sprintf("APITOKEN: %v", fApiToken), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ + ServerUrl: fServerUrl, + ApiToken: fApiToken, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + 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/ui/public/imgs/providers/kong.png b/ui/public/imgs/providers/kong.png new file mode 100644 index 0000000000000000000000000000000000000000..2b95e955b8bc2d19f0986dcd57a6a6bc3c76ad36 GIT binary patch literal 4820 zcmb7{3p|tW`^UFA%qE7moN^2~Bok7Z)0`q^SR`Q%m1Bt;HdV*Y16;>w4dx``(^s&+Azt7GuH#6@dZ(03Oq$ zhSmT8P;dLi#m1_vV~&#m05$;D9A{+WXD&>475O9ntbY{+9^C%j6>V{2>3=eaN1?x& zzW^<+B0s16>cVTYe`7oA(tqp!E?WM}Lchs>?t!(?&ih&0QIpLY1%Afw3jAmB-{~*x z&%hs*zX5+vS%&{h`sda!{Wt!v!2Q3be{uf5v{>edh8|YHANH{LU;1z3uEySN;O;ht z_^G$knxE;*tZclZ~kJvFA4&3e=T7&7f4l)aLoQmgDEkgibOf3?DUQo`k}1PykQ%#tL?M>)}(>_o1vRL z)y}J@qCOryz#(<4!qYYJQ07|rxcg{VZw_%e&$3oz?`a;t!(D4pxN#+S-}U;}!j*9T*_18lSD>P!RgBi#H#In4a&$WTJwPN7SQIQy$%;d>mB5n+5~ zJT7M?m-5bpe=^Oz-|V{9W_-Vu*VenMqogr1UM$H(lwOGIj!Y1XJ!5lF>8src-8Bcl zH1+v=Rm>FT9>R4gc*I4k#=X9OAb11=^LahwkIC0B%fV|+&2jV7@1~y(ZSxx_YQk&P z09;<`9%t7|{jwYJ7Lzk?UXv*sh~T~yR1%2QoKH?ud?d;5oSI!vbd(BKvK}0Zc9cGN zmZP?bGsguk7DKLF_!aymUDw{ zD!71>*w^Ow_O`tfv6d!k&S$jQ##QCuM%DbqHmZ*Cjv7o#&A4bLbFUS zce>cs*Nk{SKMGeO?kVpsHl*J$g{&A<^HcQ*=2g0R6>!75b}I?*_#ZiT;9M972k}vYMaxc5ig&JyKC*0MZ{0Tz@;MLMySzO4jhsco1FMTZt*_ICUCEq{ z2B@zci3-<`TXh_fZ&q;i5DKkCx?0KQ*hKB2PJmo@@TjRKwm~ByRPO6vA)>V{Q)Hru zA40uRWGgY{S+*D3OOG7mR&z6*g*X}c+@5Q7S@wPzb(iK54W5vQt9SwYCaAV|Qs8l% zId0vek4MUcX5G zVf@_NCorS1^n=YY#=sK3>I4tIE8o&InyI$f#6zF;oamZIp8~Yv^)$F^-ghdwXW!Ye zcn30EeLTh8%qiL&D4k+SVcfXAD$MSoy)%l!qsH&@j3Yz`YPkMA@ygo)pyE!@<;$Zw zII-T%PeG=Iy`8|Ihd|*AC<7WV!9DcA;;Q%1UHkf0ZzgxD4pEFT`V6=x;sa=wV+4GLS62xc55v47>m5$@q_ zS!E+;?pAXUSs1~+Oey`r90S&h<(3X(h&s#5TUKK3k5sGN&{6wGIK8%tc{#&fL%crw z6=5J(D4M6l42RUzylnN|7MJj}BSIaFQw0S^0||v{9KK+rNn2Ioyk_v(jE^t)V+SMh zePa)%O-GILQnRvLBz+zv)Ez^)p7A7QDRjBE`1R1dEuJsDuy5IXyuKcJ$i&O*96u)} z_l}5K`EjOh`+2*kOM@@o4BGqR+>#ew>jP%#ikC|&&OsiWQ9tuoMo5hYE|r|;-3y57 zQQpxI>NmzKf*p5)gb*4Q1$A^q)ST0_}QOYYT%F>TXrVGB0<; zMqBER>I1Hf)l8R%Jd9tqk&tREU?R0!e0P0cq`SXCy{Rb#jW)r`~W2RcVMm z@nuxxt|to$~L_^1BvgidOp@c2vyggD&K|(O$h_C#Y*n?^aAhiJ2elraq|5)NXQ8_Pc+ z{zEe*XrOA^>;p(aT0ujUga&Y3G7WLlmfP@(rpK zi=1SZMZplKtmKnH(%QUubWI!q=|~dlM4NVYL2&2a6o{w^!XVd3+2(qQGV%VM4Kh{9 zSXd#)keYy*+zm?zTQFDI9{9sjOGeo-BBUW%(G9RNl!9|BwDq8mZ-7YB@8;3Eg@iPG zFIn8&*R_im*)t=3FL{<$Mh(sPB#ST0HlvQ&wCMFgmN_5xHpb8UdMc z$@dbjMM)~=uz1`$A+Q}KLBgiwT2qX*NhM{ZNS#Fz#g1kLD`YN3U)aASKm71Jx2&*e zCRUs}v=7R75IT(u)2TO>z0qUNuQZH6opH)(4cU#6ml$=xThT0a9}s#QK5|t`cQjTZ zI>`w?eGB%f0C*@GT36AV9{B}sXBtImi%*+6Zet_WJqi@-6r+>kcvzd+C~8skHxS^u zKv7-#EJwFVnhXjm(K66W+`V5wX;}W;^jSBcM_MUxSKRu23fRrv2Tn^HM3}{iF!1w+ zuVhoxk3O|h9HxQYI(FV@)_a0VM~CAUt#e^OZtiL#mSTKCOR4W5zU4!q9c?YN%2TlF z-I9&c@P_t@A%R7yR{k=qjlD^Z50qhs0}%sz=bOqyVK-+@0oozGi}J%yLORS}arcsI z#T15*tl-ZHR>izunN2wI9dDzC2UZ-ir7;%huHgb@u=$uXke-xZSFxt@9HCcp!-{PTUyO0IW8>xLe6&dJ$t;*T(1Nhi6g*1-hQ@V=U=F*oO4|F{$p?eh{OMY98D5O8_dr= zxeg3B(&uhOUkgQkgw4zgoC*s{+3LrP>f(d)RJ}T&8j-M%cSr%s#I%$5R0GPovtdHK zT>dBEv*|_q)}p9QP^T)i#dyqMq~2KBL_CJFmHRMM@1`;&2C$KELC{YVITM~Vo#e}U z$my^49fRtM_BC0BkJSb(QpA_s?ZS_BVokQ}izKw(X*V0qm0P1kV%qH>9q(da1xKZ4 zKD`+rb6w#^p&H0vgTnjy`|gP;Hd~3*KH>HcRFJO%bbaN$99+fP1zYfgYs#vK!ed-?Q0+RChhl8|UJ<k)VSH=xMW^kp4zKOkuepd8+mL(%8rQIW zYGptlW~ zl>{dCJ&8S1YZC={U5>ZSxaJSN7CVA6r+ohmKx*&dX#$c}|CE}_*O#qP;R||7ce7;S*eaN?cs^To> zG`dT8)^1_JDcs_c>(PeI9S!%OtCaLrC}T-t5_lW2L)uzdo?G{#z|L8)PCFaBl92f> zXNAQCj4OI`Bs?Sv&%q{Qrj)FEv0M2|+>fE0(#wEcl;Oz?H8byht)g4`1{@*jwL2E4 zjHAm%u-Ts+Woee^<1rFpXm&0fGRnumGo!v)2Z~-;l8^$iD+)FHIeEUe*MI#jxYlK& z+A!*zxU|tw_krWljccl^JEquV6onibobA_Qo1GeCggCb^xTJEgRh@T=(XaVp2~!MZ zelj)TsCHQCE7_`?*2ehoAF|SV6H)>b)wk|lnPXq^q}4erp4O6(bfBL&6RcE>l&`IH z+7ln`5!DRkwCh$Xu9T@N&4ZwM*rNN3E9|-TBJ>>&zEHe{wlu`6Y_4x`eNKM)7OrbN l7Q+VI{tGF|d})k5{`&xUeWAz*^_fz)d!|Mh!wNK>_%E1O>GS{q literal 0 HcmV?d00001 diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index d49fe661..ba9e1318 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -50,6 +50,7 @@ import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServices import AccessFormHetznerConfig from "./AccessFormHetznerConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; +import AccessFormKongConfig from "./AccessFormKongConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLarkBotConfig from "./AccessFormLarkBotConfig"; import AccessFormLeCDNConfig from "./AccessFormLeCDNConfig"; @@ -266,6 +267,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.JDCLOUD: return ; + case ACCESS_PROVIDERS.KONG: + return ; case ACCESS_PROVIDERS.KUBERNETES: return ; case ACCESS_PROVIDERS.LARKBOT: diff --git a/ui/src/components/access/AccessFormKongConfig.tsx b/ui/src/components/access/AccessFormKongConfig.tsx new file mode 100644 index 00000000..36669161 --- /dev/null +++ b/ui/src/components/access/AccessFormKongConfig.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod/v4"; + +import { type AccessConfigForKong } from "@/domain/access"; + +type AccessFormKongConfigFieldValues = Nullish; + +export type AccessFormKongConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormKongConfigFieldValues; + onValuesChange?: (values: AccessFormKongConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormKongConfigFieldValues => { + return { + serverUrl: "http://:8001/", + apiToken: "", + }; +}; + +const AccessFormKongConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormKongConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serverUrl: z.url(t("common.errmsg.url_invalid")), + apiToken: z.string().nonempty(t("access.form.kong_api_token.placeholder")), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormKongConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index baf4dcf4..10fb1765 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -65,6 +65,7 @@ import DeployNodeConfigFormJDCloudALBConfig from "./DeployNodeConfigFormJDCloudA import DeployNodeConfigFormJDCloudCDNConfig from "./DeployNodeConfigFormJDCloudCDNConfig"; import DeployNodeConfigFormJDCloudLiveConfig from "./DeployNodeConfigFormJDCloudLiveConfig"; import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudVODConfig"; +import DeployNodeConfigFormKongConfig from "./DeployNodeConfigFormKongConfig"; import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig"; import DeployNodeConfigFormLeCDNConfig from "./DeployNodeConfigFormLeCDNConfig"; import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig"; @@ -304,6 +305,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.JDCLOUD_VOD: return ; + case DEPLOYMENT_PROVIDERS.KONG: + return ; case DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET: return ; case DEPLOYMENT_PROVIDERS.LECDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormKongConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormKongConfig.tsx new file mode 100644 index 00000000..e023c014 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormKongConfig.tsx @@ -0,0 +1,89 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod/v4"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormKongConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string; +}>; + +export type DeployNodeConfigFormKongConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormKongConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormKongConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormKongConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormKongConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormKongConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.kong_resource_type.placeholder")), + workspace: z.string().nullish(), + certificateId: z + .string() + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_CERTIFICATE || !!v?.trim(), t("workflow_node.deploy.form.kong_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormKongConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index df0e8a06..51555f07 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -45,6 +45,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForHetzner | AccessConfigForHuaweiCloud | AccessConfigForJDCloud + | AccessConfigForKong | AccessConfigForKubernetes | AccessConfigForLarkBot | AccessConfigForLeCDN @@ -293,6 +294,12 @@ export type AccessConfigForJDCloud = { accessKeySecret: string; }; +export type AccessConfigForKong = { + serverUrl: string; + apiToken: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForKubernetes = { kubeConfig?: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 12511f81..9c098fc5 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -44,6 +44,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ HETZNER: "hetzner", HUAWEICLOUD: "huaweicloud", JDCLOUD: "jdcloud", + KONG: "kong", KUBERNETES: "k8s", LARKBOT: "larkbot", LECDN: "lecdn", @@ -146,6 +147,7 @@ export const accessProvidersMap: Map [ type, diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 76be1cfd..b5b79af7 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -286,6 +286,11 @@ "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", + "access.form.kong_server_url.label": "Kong admin API server URL", + "access.form.kong_server_url.placeholder": "Please enter Kong admin API server URL", + "access.form.kong_api_key.label": "Kong admin API token", + "access.form.kong_api_key.placeholder": "Please enter Kong admin API token", + "access.form.kong_api_key.tooltip": "For more information, see https://developer.konghq.com/", "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", "access.form.larkbot_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index fe76a727..9c7d0053 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -101,6 +101,7 @@ "provider.jdcloud.dns": "JD Cloud - DNS", "provider.jdcloud.live": "JD Cloud - Live Video", "provider.jdcloud.vod": "JD Cloud - VOD (Video on Demand)", + "provider.kong": "Kong", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "Lark Bot", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index caf55597..0eba1c73 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -528,6 +528,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.kong_resource_type.label": "Resource type", + "workflow_node.deploy.form.kong_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "SSL certificate", + "workflow_node.deploy.form.kong_workspace.label": "Kong workspace (Optional)", + "workflow_node.deploy.form.kong_workspace.placeholder": "Please enter Kong workspace", + "workflow_node.deploy.form.kong_workspace.tooltip": "You can find it on Kong dashboard.", + "workflow_node.deploy.form.kong_certificate_id.label": "Kong certificate ID", + "workflow_node.deploy.form.kong_certificate_id.placeholder": "Please enter Kong certificate ID", + "workflow_node.deploy.form.kong_certificate_id.tooltip": "You can find it on Kong dashboard.", "workflow_node.deploy.form.lecdn_resource_type.label": "Resource type", "workflow_node.deploy.form.lecdn_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "Certificate", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 837ccc17..8299c3a4 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -286,6 +286,11 @@ "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig 文件内容", "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

为空时,将使用 Pod 的 ServiceAccount 作为凭证。", + "access.form.kong_server_url.label": "Kong Admin API 服务地址", + "access.form.kong_server_url.placeholder": "请输入 Kong Admin API 服务地址", + "access.form.kong_api_key.label": "Kong Admin API Token", + "access.form.kong_api_key.placeholder": "请输入 Kong Admin API Token", + "access.form.kong_api_key.tooltip": "这是什么?请参阅 https://developer.konghq.com/", "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index fa16398a..73a3b63c 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -101,6 +101,7 @@ "provider.jdcloud.dns": "京东云 - 云解析 DNS", "provider.jdcloud.live": "京东云 - 视频直播", "provider.jdcloud.vod": "京东云 - 视频点播", + "provider.kong": "Kong", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "飞书群机器人", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 4e20f18c..cd571174 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -526,6 +526,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.kong_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.kong_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.kong_workspace.label": "Kong 工作空间(可选)", + "workflow_node.deploy.form.kong_workspace.placeholder": "请输入 Kong 工作空间", + "workflow_node.deploy.form.kong_workspace.tooltip": "请登录 Kong 控制台查看。", + "workflow_node.deploy.form.kong_certificate_id.label": "Kong 证书 ID", + "workflow_node.deploy.form.kong_certificate_id.placeholder": "请输入 Kong 证书 ID", + "workflow_node.deploy.form.kong_certificate_id.tooltip": "请登录 Kong 控制台查看。", "workflow_node.deploy.form.lecdn_resource_type.label": "证书部署方式", "workflow_node.deploy.form.lecdn_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "替换指定证书", From 6f9775bc095b1e924f5c22c0f2ef2da225a996f8 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 25 Jun 2025 20:55:50 +0800 Subject: [PATCH 04/27] feat(ui): improve i18n --- ...loyNodeConfigFormTencentCloudCDNConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudCLBConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudCSSConfig.tsx | 2 +- ...oyNodeConfigFormTencentCloudECDNConfig.tsx | 2 +- ...ployNodeConfigFormTencentCloudEOConfig.tsx | 2 +- ...oyNodeConfigFormTencentCloudGAAPConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudSCFConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudSSLConfig.tsx | 2 +- ...eConfigFormTencentCloudSSLDeployConfig.tsx | 2 +- ...eConfigFormTencentCloudSSLUpdateConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudVODConfig.tsx | 2 +- ...loyNodeConfigFormTencentCloudWAFConfig.tsx | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 24 +++++++++---------- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCDNConfig.tsx index bc9e938d..fbb5f9d5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCDNConfig.tsx @@ -58,7 +58,7 @@ const DeployNodeConfigFormTencentCloudCDNConfig = ({ rules={[formRule]} tooltip={} > - + } > - + } > - + } > - + } > - + } > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx index 3128a41a..2ec93233 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx @@ -60,7 +60,7 @@ const DeployNodeConfigFormTencentCloudSCFConfig = ({ rules={[formRule]} tooltip={} > - + } > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx index 1a104bf2..638fe688 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx @@ -75,7 +75,7 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({ rules={[formRule]} tooltip={} > - + } > - + } > - + } > - + https://cloud.tencent.com/document/product/228/30976", + "workflow_node.deploy.form.tencentcloud_cdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/228/30976

国际站用户请填写 cdn.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名", "workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn", "workflow_node.deploy.form.tencentcloud_clb_endpoint.label": "腾讯云 CLB 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_clb_endpoint.placeholder": "请输入腾讯云 CLB 接口端点(例如:clb.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_clb_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669", + "workflow_node.deploy.form.tencentcloud_clb_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669

国际站用户请填写 clb.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_clb_region.label": "腾讯云 CLB 产品地域", "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "请输入腾讯云 CLB 服务地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_clb_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/33415", @@ -709,19 +709,19 @@ "workflow_node.deploy.form.tencentcloud_cos_domain.tooltip": "这是什么?请参阅 see https://console.cloud.tencent.com/cos", "workflow_node.deploy.form.tencentcloud_css_endpoint.label": "腾讯云云直播接口端点(可选)", "workflow_node.deploy.form.tencentcloud_css_endpoint.placeholder": "请输入腾讯云云直播接口端点(例如:live.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_css_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/267/20458", + "workflow_node.deploy.form.tencentcloud_css_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/267/20458

国际站用户请填写 live.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_css_domain.label": "腾讯云云直播播放域名", "workflow_node.deploy.form.tencentcloud_css_domain.placeholder": "请输入腾讯云云直播播放域名", "workflow_node.deploy.form.tencentcloud_css_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/live", "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.label": "腾讯云 ECDN 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.placeholder": "请输入腾讯云 ECDN 接口端点(例如:cdn.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669", + "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669

国际站用户请填写 cdn.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ecdn_domain.label": "腾讯云 ECDN 加速域名", "workflow_node.deploy.form.tencentcloud_ecdn_domain.placeholder": "请输入腾讯云 ECDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_ecdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn", "workflow_node.deploy.form.tencentcloud_eo_endpoint.label": "腾讯云 EdgeOne 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_eo_endpoint.placeholder": "请输入腾讯云 EdgeOne 接口端点(例如:teo.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_eo_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/1552/80723", + "workflow_node.deploy.form.tencentcloud_eo_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/1552/80723

国际站用户请填写 teo.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_eo_zone_id.label": "腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", @@ -730,7 +730,7 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "腾讯云 GAAP 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/608/36934", + "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/608/36934

国际站用户请填写 gaap.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.label": "证书部署方式", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.option.listener.label": "替换指定监听器的证书", @@ -742,7 +742,7 @@ "workflow_node.deploy.form.tencentcloud_gaap_listener_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/gaap", "workflow_node.deploy.form.tencentcloud_scf_endpoint.label": "腾讯云 SCF 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_scf_endpoint.placeholder": "请输入腾讯云 SCF 接口端点(例如:scf.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_scf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17237", + "workflow_node.deploy.form.tencentcloud_scf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17237

国际站用户请填写 scf.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_scf_region.label": "腾讯云 SCF 产品地域", "workflow_node.deploy.form.tencentcloud_scf_region.placeholder": "输入腾讯云 SCF 产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_scf_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17299", @@ -751,11 +751,11 @@ "workflow_node.deploy.form.tencentcloud_scf_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/scf", "workflow_node.deploy.form.tencentcloud_ssl_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", + "workflow_node.deploy.form.tencentcloud_ssl_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

国际站用户请填写 ssl.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "小贴士:将通过腾讯云 OpenAPI DeployCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

国际站用户请填写 ssl.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "腾讯云云产品地域", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "请输入腾讯云云产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", @@ -771,7 +771,7 @@ "workflow_node.deploy.form.tencentcloud_ssl_update.guide": "小贴士:将通过腾讯云 OpenAPI UpdateCertificateInstanceUploadUpdateCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", + "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

国际站用户请填写 ssl.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.label": "腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.placeholder": "请输入腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/certoverview", @@ -788,7 +788,7 @@ "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.label": "是否更新原证书(即证书 ID 保持不变)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.label": "腾讯云云点播接口端点(可选)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.placeholder": "请输入腾讯云云点播接口端点(例如:vod.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/266/31755", + "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/266/31755

国际站用户请填写 vod.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.label": "腾讯云云点播应用 ID", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.placeholder": "请输入腾讯云云点播应用 ID", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/vod", @@ -797,7 +797,7 @@ "workflow_node.deploy.form.tencentcloud_vod_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/vod", "workflow_node.deploy.form.tencentcloud_waf_endpoint.label": "腾讯云 WAF 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_waf_endpoint.placeholder": "请输入腾讯云 WAF 接口端点(例如:waf.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_waf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/53611", + "workflow_node.deploy.form.tencentcloud_waf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/53611

国际站用户请填写 waf.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_waf_region.label": "腾讯云 WAF 产品地域", "workflow_node.deploy.form.tencentcloud_waf_region.placeholder": "请输入腾讯云 WAF 产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_waf_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/47525", From b1f2eee66a7f2f87c9f601de59a525336f5364b1 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 25 Jun 2025 21:01:28 +0800 Subject: [PATCH 05/27] build: packaging bilingual README on release --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 249d6b3c..ed10b5ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -185,7 +185,10 @@ jobs: tmpdir=$(mktemp -d) cp "$bin" "${tmpdir}/${entrypoint}" - cp ../LICENSE ../README.md ../CHANGELOG.md "$tmpdir" + cp ../LICENSE "$tmpdir/LICENSE" + cp ../README.md "$tmpdir/README_zhCN.md" + cp ../README_EN.md "$tmpdir/README_enUS.md" + cp ../CHANGELOG.md "$tmpdir/CHANGELOG.md" if [[ "$bin" == *".exe" ]]; then zip -j "${bin%.exe}.zip" "$tmpdir"/* From abcbb811c8cfd20cf87eb00824dbc2333c50b0e7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 26 Jun 2025 21:42:39 +0800 Subject: [PATCH 06/27] build: release sync to gitee --- .github/workflows/push_image.yml | 1 - .github/workflows/release_sync_gitee.py | 275 +++++++++++++++++++++++ .github/workflows/release_sync_gitee.yml | 27 +++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release_sync_gitee.py create mode 100644 .github/workflows/release_sync_gitee.yml diff --git a/.github/workflows/push_image.yml b/.github/workflows/push_image.yml index 0b5cb04a..52db22d6 100644 --- a/.github/workflows/push_image.yml +++ b/.github/workflows/push_image.yml @@ -16,7 +16,6 @@ on: jobs: build-and-push: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/release_sync_gitee.py b/.github/workflows/release_sync_gitee.py new file mode 100644 index 00000000..e6af715b --- /dev/null +++ b/.github/workflows/release_sync_gitee.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +import logging +import json +import mimetypes +import tempfile +import os +import random +import re +import shutil +import time +from urllib import request +from urllib.error import HTTPError + +GITHUB_REPO = "certimate-go/certimate" +GITEE_REPO = "certimate-go/certimate" +GITEE_TOKEN = os.getenv("GITEE_TOKEN", "") + +SYNC_MARKER = "SYNCING FROM GITHUB, PLEASE WAIT ..." +TEMP_DIR = tempfile.mkdtemp() + +logging.basicConfig(level=logging.INFO) + + +def do_httpreq(url, method="GET", headers=None, data=None): + req = request.Request(url, data=data, method=method) + headers = headers or {} + for key, value in headers.items(): + req.add_header(key, value) + + try: + with request.urlopen(req) as resp: + resp_data = resp.read().decode("utf-8") + if resp_data: + try: + return json.loads(resp_data) + except json.JSONDecodeError: + pass + return None + except HTTPError as e: + errmsg = "" + if e.readable(): + try: + errmsg = e.read().decode('utf-8') + errmsg = errmsg.replace("\r", "\\r").replace("\n", "\\n") + except: + pass + logging.error(f"Error occurred when sending request: status={e.status}, response={errmsg}") + raise e + except Exception as e: + raise e + + +def get_github_stable_release(): + page = 1 + while True: + releases = do_httpreq( + url=f"https://api.github.com/repos/{GITHUB_REPO}/releases?page={page}&per_page=100", + headers={"Accept": "application/vnd.github+json"}, + ) + if not releases or len(releases) == 0: + break + + for release in releases: + release_name = release.get("name", "") + if re.match(r"^v[0-9]", release_name): + if any( + x in release_name + for x in ["alpha", "beta", "rc", "preview", "test", "unstable"] + ): + continue + return release + + page += 1 + + return None + + +def get_gitee_release_list(): + page = 1 + list = [] + while True: + releases = do_httpreq( + url=f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases?access_token={GITEE_TOKEN}&page={page}&per_page=100", + ) + if not releases or len(releases) == 0: + break + + list.extend(releases) + page += 1 + + return list + + +def get_gitee_release_by_tag(tag_name): + releases = get_gitee_release_list() + for release in releases: + if release.get("tag_name") == tag_name: + return release + + return None + + +def delete_gitee_release(release_info): + if not release_info: + raise ValueError("Release info is invalid") + + release_id = release_info.get("id", "") + release_name = release_info.get("tag_name", "") + if not release_id: + raise ValueError("Release ID is missing") + + attachpage = 1 + attachfiles = [] + while True: + releases = do_httpreq( + url=f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases/{release_id}/attach_files?access_token={GITEE_TOKEN}&page={attachpage}&per_page=100", + ) + if not releases or len(releases) == 0: + break + + attachfiles.extend(releases) + attachpage += 1 + + for attachfile in attachfiles: + attachfile_id = attachfile.get("id") + attachfile_name = attachfile.get("name") + logging.info("Trying to delete Gitee attach file: %s/%s", release_name, attachfile_name) + do_httpreq( + url=f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases/{release_id}/attach_files/{attachfile_id}?access_token={GITEE_TOKEN}", + method="DELETE", + ) + + logging.info("Trying to delete Gitee release: %s", release_name) + do_httpreq( + url=f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases/{release_id}?access_token={GITEE_TOKEN}", + method="DELETE", + ) + + +def create_gitee_release(name, tag, body, prerelease, gh_assets): + release_info = do_httpreq( + f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases?access_token={GITEE_TOKEN}", + method="POST", + headers={"Content-Type": "application/json"}, + data=json.dumps({ + "tag_name": tag, + "name": name, + "body": SYNC_MARKER, + "prerelease": prerelease, + "target_commitish": "", + }).encode("utf-8"), + ) + + if not release_info or "id" not in release_info: + return None + logging.info("Gitee release created") + + release_id = release_info["id"] + + assets_dir = os.path.join(TEMP_DIR, "assets") + os.makedirs(assets_dir, exist_ok=True) + + gh_assets = gh_assets or [] + for asset in gh_assets: + logging.info("Tring to download asset from GitHub: %s", asset["name"]) + + opener = request.build_opener() + request.install_opener(opener) + download_ts = time.time() + download_url = asset.get("browser_download_url") + download_path = os.path.join(assets_dir, asset["name"]) + def _hook(blocknum, blocksize, totalsize): + nonlocal download_ts + TIMESPAN = 5 # print progress every 5sec + ts = time.time() + pct = min(round(100 * blocknum * blocksize / totalsize, 2), 100) + if (ts - download_ts < TIMESPAN) and (pct < 100): + return + download_ts = ts + logging.info(f"Downloading {download_url} >>> {pct}%") + + # request.urlretrieve(download_url, download_path, _hook) + + for asset in gh_assets: + logging.info("Tring to upload asset to Gitee: %s", asset["name"]) + + boundary = '----boundary' + ''.join(random.choice('0123456789abcdef') for _ in range(16)) + print(f"Using boundary: {boundary}") + with open(os.path.join(assets_dir, asset["name"]), 'rb') as f: + attachfile_mime = mimetypes.guess_type(asset["name"])[0] or 'application/octet-stream' + attachfile_req = [] + attachfile_req.append(f"--{boundary}") + attachfile_req.append(f'Content-Disposition: form-data; name="file"; filename="{asset["name"]}"') + attachfile_req.append(f"Content-Type: {attachfile_mime}") + attachfile_req.append("") + attachfile_req.append(f.read().decode('latin-1')) + attachfile_req.append(f"--{boundary}--") + attachfile_req.append("") + attachfile_req = "\r\n".join(attachfile_req).encode('latin-1') + + do_httpreq( + f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases/{release_id}/attach_files?access_token={GITEE_TOKEN}", + method="POST", + headers={'Content-Type': f'multipart/form-data; boundary={boundary}'}, + data=attachfile_req, + ) + logging.info("Asset uploaded: %s", asset["name"]) + + release_info = do_httpreq( + f"https://gitee.com/api/v5/repos/{GITEE_REPO}/releases/{release_id}?access_token={GITEE_TOKEN}", + method="PATCH", + headers={"Content-Type": "application/json"}, + data=json.dumps({ + "tag_name": tag, + "name": name, + "body": f"**此发行版同步自 GitHub,完整变更日志请访问 https://github.com/{GITHUB_REPO}/releases/{tag} 查看。**\n\n**因 Gitee 存储空间容量有限,仅能保留最新一个发行版,如需其余版本请访问 GitHub 获取。**\n\n---\n\n" + body, + "prerelease": prerelease, + }).encode("utf-8"), + ) + logging.info("Gitee release updated") + return release_info + + +def main(): + try: + # 获取 GitHub 最新稳定发行版 + github_release = get_github_stable_release() + if not github_release: + logging.warning("GitHub stable release not found. Foget to release?") + return + else: + logging.info("GitHub stable release found: %s", github_release.get('name')) + + # 提取稳定版的信息 + release_name = github_release.get("name") + release_tag = github_release.get("tag_name") + release_body = github_release.get("body") + release_prerelease = github_release.get("prerelease", False) + release_assets = github_release.get("assets", []) + + # 检查 Gitee 是否已有同名发行版 + gitee_release = get_gitee_release_by_tag(release_tag) + if gitee_release and gitee_release.get("body") == SYNC_MARKER: + logging.warning("Gitee syncing release found, cleaning up...") + delete_gitee_release(gitee_release) + elif gitee_release: + logging.info("Gitee release already exists, exit.") + return + + # 同步发行版 + gitee_release = create_gitee_release(release_name, release_tag, release_body, release_prerelease, release_assets) + if not gitee_release: + logging.warning("Failed to create Gitee release.") + return + + # 清除历史发行版 + gitee_release_list = get_gitee_release_list() + for release in gitee_release_list: + if release.get("tag_name") == release_tag: + continue + else: + delete_gitee_release(release) + + logging.info("Sync release completed.") + + except Exception as e: + logging.fatal(str(e)) + + finally: + if os.path.exists(TEMP_DIR): + shutil.rmtree(TEMP_DIR) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/release_sync_gitee.yml b/.github/workflows/release_sync_gitee.yml new file mode 100644 index 00000000..79df3eef --- /dev/null +++ b/.github/workflows/release_sync_gitee.yml @@ -0,0 +1,27 @@ +name: Release Sync to Gitee + +on: + release: + types: [published, unpublished, deleted] + workflow_dispatch: + +jobs: + sync-to-gitee: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python3 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Run script + env: + GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} + run: | + cd .github/workflows + python ./release_sync_gitee.py From 4d4f75a7c88763a7c14020a3b3d6fb0240489ccc Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 26 Jun 2025 21:47:20 +0800 Subject: [PATCH 07/27] build: release sync to gitee --- .github/workflows/release_sync_gitee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_sync_gitee.py b/.github/workflows/release_sync_gitee.py index e6af715b..eaed6af2 100644 --- a/.github/workflows/release_sync_gitee.py +++ b/.github/workflows/release_sync_gitee.py @@ -179,7 +179,7 @@ def create_gitee_release(name, tag, body, prerelease, gh_assets): download_ts = ts logging.info(f"Downloading {download_url} >>> {pct}%") - # request.urlretrieve(download_url, download_path, _hook) + request.urlretrieve(download_url, download_path, _hook) for asset in gh_assets: logging.info("Tring to upload asset to Gitee: %s", asset["name"]) From 40e9f9626875e08826c4a62c964f8662c4a049ee Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 26 Jun 2025 21:49:44 +0800 Subject: [PATCH 08/27] build: release sync to gitee --- .github/workflows/release_sync_gitee.py | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_sync_gitee.py b/.github/workflows/release_sync_gitee.py index eaed6af2..bacfda72 100644 --- a/.github/workflows/release_sync_gitee.py +++ b/.github/workflows/release_sync_gitee.py @@ -265,6 +265,7 @@ def main(): except Exception as e: logging.fatal(str(e)) + exit(1) finally: if os.path.exists(TEMP_DIR): From 808370fd103d5f8e03f7fb0fdbacc0f07fe7cfa1 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 26 Jun 2025 22:13:54 +0800 Subject: [PATCH 09/27] build: disable auto trigger releasing sync to gitee --- .github/workflows/release_sync_gitee.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_sync_gitee.yml b/.github/workflows/release_sync_gitee.yml index 79df3eef..6c3a1dd6 100644 --- a/.github/workflows/release_sync_gitee.yml +++ b/.github/workflows/release_sync_gitee.yml @@ -1,8 +1,8 @@ name: Release Sync to Gitee on: - release: - types: [published, unpublished, deleted] + # release: + # types: [published, unpublished, deleted] workflow_dispatch: jobs: From f29cdae6488bf69911113b3ba22b00506513dd9d Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 27 Jun 2025 13:13:50 +0800 Subject: [PATCH 10/27] fix: #827 --- .../ssl-deployer/providers/aliyun-alb/aliyun_alb.go | 10 +++++----- .../ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go index 391f4b55..2b165967 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go @@ -109,12 +109,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 根据部署资源类型决定部署方式 switch d.config.ResourceType { case RESOURCE_TYPE_LOADBALANCER: - if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + if err := d.deployToLoadbalancer(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { return nil, err } case RESOURCE_TYPE_LISTENER: - if err := d.deployToListener(ctx, upres.CertId); err != nil { + if err := d.deployToListener(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { return nil, err } @@ -338,13 +338,13 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo continue } - // 监听证书 ID 格式:${证书 ID}-${地域} - certificateId := strings.Split(tea.StringValue(listenerCertificate.CertificateId), "-")[0] - if certificateId == cloudCertId { + if tea.StringValue(listenerCertificate.CertificateId) == cloudCertId { certificateIsAlreadyAssociated = true break } + // 监听证书 ID 格式:${证书 ID}-${地域} + certificateId := strings.Split(tea.StringValue(listenerCertificate.CertificateId), "-")[0] certificateIdAsInt64, err := strconv.ParseInt(certificateId, 10, 64) if err != nil { errs = append(errs, err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go index 6a6a0411..809c7391 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -97,12 +97,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 根据部署资源类型决定部署方式 switch d.config.ResourceType { case RESOURCE_TYPE_LOADBALANCER: - if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + if err := d.deployToLoadbalancer(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { return nil, err } case RESOURCE_TYPE_LISTENER: - if err := d.deployToListener(ctx, upres.CertId); err != nil { + if err := d.deployToListener(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { return nil, err } From d6882cbc4f29c2a38afc351fa6da8286dfcac35e Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 27 Jun 2025 13:30:01 +0800 Subject: [PATCH 11/27] fix: uploading certificates duplicated to aliyun cas --- .../providers/aliyun-cas/aliyun_cas.go | 12 ++- .../providers/aliyun-cas/aliyun_cas_test.go | 77 +++++++++++++++++++ .../baiducloud-cert/baiducloud_cert_test.go | 12 +-- 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas_test.go diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go index 52e480c4..8e875a54 100644 --- a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go @@ -94,10 +94,20 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if listUserCertificateOrderResp.Body.CertificateOrderList != nil { for _, certOrder := range listUserCertificateOrderResp.Body.CertificateOrderList { - if !strings.EqualFold(certX509.SerialNumber.Text(16), *certOrder.SerialNo) { + // 先对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certOrder.CommonName)) { continue } + // 再对比证书序列号 + // 注意阿里云 CAS 会在序列号前补零,需去除后再比较 + oldCertSN := strings.TrimLeft(tea.StringValue(certOrder.SerialNo), "0") + newCertSN := strings.TrimLeft(certX509.SerialNumber.Text(16), "0") + if !strings.EqualFold(newCertSN, oldCertSN) { + continue + } + + // 最后对比证书内容 getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ CertId: certOrder.CertificateId, } diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas_test.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas_test.go new file mode 100644 index 00000000..31955399 --- /dev/null +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas_test.go @@ -0,0 +1,77 @@ +package aliyuncas_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string +) + +func init() { + argsPrefix := "CERTIMATE_SSLMANAGER_ALIYUNCAS_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_cas_test.go -args \ + --CERTIMATE_SSLMANAGER_ALIYUNCAS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLMANAGER_ALIYUNCAS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLMANAGER_ALIYUNCAS_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_SSLMANAGER_ALIYUNCAS_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_SSLMANAGER_ALIYUNCAS_REGION="cn-hangzhou" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + }, "\n")) + + sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := sslmanager.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/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert_test.go b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert_test.go index 80c7d790..9360621c 100644 --- a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert_test.go +++ b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert_test.go @@ -20,7 +20,7 @@ var ( ) func init() { - argsPrefix := "CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_" + argsPrefix := "CERTIMATE_SSLMANAGER_BAIDUCLOUDCERT_" flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") @@ -31,11 +31,11 @@ func init() { /* Shell command to run this test: - go test -v ./baiducloud_cas_test.go -args \ - --CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - --CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_INPUTKEYPATH="/path/to/your-input-key.pem" \ - --CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_ACCESSKEYID="your-access-key-id" \ - --CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_SECRETACCESSKEY="your-access-key-secret" + go test -v ./baiducloud_cert_test.go -args \ + --CERTIMATE_SSLMANAGER_BAIDUCLOUDCERT_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLMANAGER_BAIDUCLOUDCERT_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLMANAGER_BAIDUCLOUDCERT_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_SSLMANAGER_BAIDUCLOUDCERT_SECRETACCESSKEY="your-access-key-secret" */ func TestDeploy(t *testing.T) { flag.Parse() From 3162673b9de3b783d4d7e9a394c106f04b7c51fd Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 29 Jun 2025 21:50:36 +0800 Subject: [PATCH 12/27] fix: #835 --- internal/deployer/providers.go | 2 +- .../tencentcloud_ssl_update.go | 8 ++++---- ...yNodeConfigFormTencentCloudSSLUpdateConfig.tsx | 15 +++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 0f33f57f..0a48d6ca 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -1251,7 +1251,7 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy SecretId: access.SecretId, SecretKey: access.SecretKey, Endpoint: xmaps.GetString(options.ProviderServiceConfig, "endpoint"), - CertificiateId: xmaps.GetString(options.ProviderServiceConfig, "certificiateId"), + CertificateId: xmaps.GetString(options.ProviderServiceConfig, "certificateId"), IsReplaced: xmaps.GetBool(options.ProviderServiceConfig, "isReplaced"), ResourceTypes: xslices.Filter(strings.Split(xmaps.GetString(options.ProviderServiceConfig, "resourceTypes"), ";"), func(s string) bool { return s != "" }), ResourceRegions: xslices.Filter(strings.Split(xmaps.GetString(options.ProviderServiceConfig, "resourceRegions"), ";"), func(s string) bool { return s != "" }), diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go index 2fff2992..753e8be4 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go @@ -24,7 +24,7 @@ type SSLDeployerProviderConfig struct { // 腾讯云接口端点。 Endpoint string `json:"endpoint,omitempty"` // 原证书 ID。 - CertificiateId string `json:"certificateId"` + CertificateId string `json:"certificateId"` // 是否替换原有证书(即保持原证书 ID 不变)。 IsReplaced bool `json:"isReplaced,omitempty"` // 云资源类型数组。 @@ -80,7 +80,7 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.CertificiateId == "" { + if d.config.CertificateId == "" { return nil, errors.New("config `certificateId` is required") } if len(d.config.ResourceTypes) == 0 { @@ -120,7 +120,7 @@ func (d *SSLDeployerProvider) executeUpdateCertificateInstance(ctx context.Conte } updateCertificateInstanceReq := tcssl.NewUpdateCertificateInstanceRequest() - updateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificiateId) + updateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId) updateCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) updateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes) updateCertificateInstanceReq.ResourceTypesRegions = wrapResourceTypeRegions(d.config.ResourceTypes, d.config.ResourceRegions) @@ -198,7 +198,7 @@ func (d *SSLDeployerProvider) executeUploadUpdateCertificateInstance(ctx context } uploadUpdateCertificateInstanceReq := tcssl.NewUploadUpdateCertificateInstanceRequest() - uploadUpdateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificiateId) + uploadUpdateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId) uploadUpdateCertificateInstanceReq.CertificatePublicKey = common.StringPtr(certPEM) uploadUpdateCertificateInstanceReq.CertificatePrivateKey = common.StringPtr(privkeyPEM) uploadUpdateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx index 1a0de22c..00520d70 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx @@ -49,12 +49,15 @@ const DeployNodeConfigFormTencentCloudSSLUpdateConfig = ({ .split(MULTIPLE_INPUT_SEPARATOR) .every((e) => !!e.trim()); }, t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.placeholder")), - resourceRegions: z.string(t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder")).refine((v) => { - if (!v) return false; - return String(v) - .split(MULTIPLE_INPUT_SEPARATOR) - .every((e) => !!e.trim()); - }, t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder")), + resourceRegions: z + .string(t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder")) + .nullish() + .refine((v) => { + if (!v) return true; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => !!e.trim()); + }, t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder")), isReplaced: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); From 0948291a9e20f28091d30d51121c89c7a5cc1322 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 29 Jun 2025 23:14:17 +0800 Subject: [PATCH 13/27] fix: #834 --- .../providers/ctcccloud-ao/ctcccloud_ao.go | 17 +++++++++++++++-- pkg/sdk3rd/ctyun/ao/api_get_domain_config.go | 18 +++++++++--------- pkg/sdk3rd/ctyun/ao/types.go | 6 ++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index 7a78d41e..0e6d9831 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "log/slog" + "strconv" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-ao" ctyunao "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/ao" + xslices "github.com/certimate-go/certimate/pkg/utils/slices" xtypes "github.com/certimate-go/certimate/pkg/utils/types" ) @@ -80,7 +82,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 域名基础及加速配置查询 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13412&data=174&isNormal=1&vid=167 getDomainConfigReq := &ctyunao.GetDomainConfigRequest{ - Domain: xtypes.ToPtr(d.config.Domain), + Domain: xtypes.ToPtr(d.config.Domain), + ProductCode: xtypes.ToPtr("020"), } getDomainConfigResp, err := d.sdkClient.GetDomainConfig(getDomainConfigReq) d.logger.Debug("sdk request 'cdn.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp)) @@ -93,7 +96,17 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke modifyDomainConfigReq := &ctyunao.ModifyDomainConfigRequest{ Domain: xtypes.ToPtr(d.config.Domain), ProductCode: xtypes.ToPtr(getDomainConfigResp.ReturnObj.ProductCode), - Origin: getDomainConfigResp.ReturnObj.Origin, + Origin: xslices.Map(getDomainConfigResp.ReturnObj.Origin, func(item *ctyunao.DomainOriginConfigWithWeight) *ctyunao.DomainOriginConfig { + weight := item.Weight + if weight == 0 { + weight = 1 + } + return &ctyunao.DomainOriginConfig{ + Origin: item.Origin, + Role: item.Role, + Weight: strconv.Itoa(int(weight)), + } + }), HttpsStatus: xtypes.ToPtr("on"), CertName: xtypes.ToPtr(upres.CertName), } diff --git a/pkg/sdk3rd/ctyun/ao/api_get_domain_config.go b/pkg/sdk3rd/ctyun/ao/api_get_domain_config.go index 01c007ab..9d760a45 100644 --- a/pkg/sdk3rd/ctyun/ao/api_get_domain_config.go +++ b/pkg/sdk3rd/ctyun/ao/api_get_domain_config.go @@ -14,15 +14,15 @@ type GetDomainConfigResponse struct { apiResponseBase ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - Origin []*DomainOriginConfig `json:"origin,omitempty"` - HttpsStatus string `json:"https_status"` - HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` - CertName string `json:"cert_name"` + Domain string `json:"domain"` + ProductCode string `json:"product_code"` + Status int32 `json:"status"` + AreaScope int32 `json:"area_scope"` + Cname string `json:"cname"` + Origin []*DomainOriginConfigWithWeight `json:"origin,omitempty"` + HttpsStatus string `json:"https_status"` + HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` + CertName string `json:"cert_name"` } `json:"returnObj,omitempty"` } diff --git a/pkg/sdk3rd/ctyun/ao/types.go b/pkg/sdk3rd/ctyun/ao/types.go index c706afd9..83f703d4 100644 --- a/pkg/sdk3rd/ctyun/ao/types.go +++ b/pkg/sdk3rd/ctyun/ao/types.go @@ -90,6 +90,12 @@ type CertDetail struct { } type DomainOriginConfig struct { + Origin string `json:"origin"` + Role string `json:"role"` + Weight string `json:"weight"` +} + +type DomainOriginConfigWithWeight struct { Origin string `json:"origin"` Role string `json:"role"` Weight int32 `json:"weight"` From 09243c88baf25cb7cc82b644b20847e6cf72fc8b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 30 Jun 2025 10:07:40 +0800 Subject: [PATCH 14/27] feat(ui): improve i18n --- ...eployNodeConfigFormTencentCloudSSLUpdateConfig.tsx | 11 +++++++---- ui/src/i18n/locales/en/nls.access.json | 6 +++--- ui/src/i18n/locales/en/nls.workflow.nodes.json | 5 +++-- ui/src/i18n/locales/zh/nls.access.json | 6 +++--- ui/src/i18n/locales/zh/nls.workflow.nodes.json | 9 +++++---- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx index 00520d70..383f26c0 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLUpdateConfig.tsx @@ -24,9 +24,7 @@ export type DeployNodeConfigFormTencentCloudSSLUpdateConfigProps = { const MULTIPLE_INPUT_SEPARATOR = ";"; const initFormModel = (): DeployNodeConfigFormTencentCloudSSLUpdateConfigFieldValues => { - return { - isReplaced: true, - }; + return {}; }; const DeployNodeConfigFormTencentCloudSSLUpdateConfig = ({ @@ -125,7 +123,12 @@ const DeployNodeConfigFormTencentCloudSSLUpdateConfig = ({ />
- + } + > diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index b5b79af7..47232e4d 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -288,9 +288,9 @@ "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", "access.form.kong_server_url.label": "Kong admin API server URL", "access.form.kong_server_url.placeholder": "Please enter Kong admin API server URL", - "access.form.kong_api_key.label": "Kong admin API token", - "access.form.kong_api_key.placeholder": "Please enter Kong admin API token", - "access.form.kong_api_key.tooltip": "For more information, see https://developer.konghq.com/", + "access.form.kong_api_token.label": "Kong admin API token", + "access.form.kong_api_token.placeholder": "Please enter Kong admin API token", + "access.form.kong_api_token.tooltip": "For more information, see https://developer.konghq.com/admin-api/", "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", "access.form.larkbot_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 0eba1c73..12254221 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -779,15 +779,16 @@ "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.tooltip": "For more information, see https://console.cloud.tencent.com/certoverview", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.label": "Tencent Cloud resource types", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.placeholder": "Please enter Tencent Cloud resource types (separated by semicolons)", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981 or https://www.tencentcloud.com/document/product/1007/70503", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.title": "Change Tencent Cloud resource types", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource type", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.label": "Tencent Cloud resource regions (Optional)", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder": "Please enter Tencent Cloud resource regions (separated by semicolons)", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981 or https://www.tencentcloud.com/document/product/1007/70503", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.title": "Change Tencent Cloud resource regions", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource region", "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.label": "Renewal certificate (certificate ID unchanged)", + "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.tooltip": "When unchecked, it will invoke UpdateCertificateInstance; otherwise, it will invoke UploadUpdateCertificateInstance.", "workflow_node.deploy.form.tencentcloud_vod_endpoint.label": "Tencent Cloud VOD API endpoint (Optional)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.placeholder": "Please enter Tencent Cloud VOD API endpoint (e.g. vod.intl.tencentcloudapi.com)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "
  • vod.intl.tencentcloudapi.com for Tencent Cloud International
  • vod.tencentcloudapi.com for Tencent Cloud in China
", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 8299c3a4..fc344cc0 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -288,9 +288,9 @@ "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

为空时,将使用 Pod 的 ServiceAccount 作为凭证。", "access.form.kong_server_url.label": "Kong Admin API 服务地址", "access.form.kong_server_url.placeholder": "请输入 Kong Admin API 服务地址", - "access.form.kong_api_key.label": "Kong Admin API Token", - "access.form.kong_api_key.placeholder": "请输入 Kong Admin API Token", - "access.form.kong_api_key.tooltip": "这是什么?请参阅 https://developer.konghq.com/", + "access.form.kong_api_token.label": "Kong Admin API Token", + "access.form.kong_api_token.placeholder": "请输入 Kong Admin API Token", + "access.form.kong_api_token.tooltip": "这是什么?请参阅 https://developer.konghq.com/admin-api/", "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index ea9afaad..a3f4278e 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -777,15 +777,16 @@ "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/certoverview", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.label": "腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649https://cloud.tencent.com/document/product/400/119791

注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.title": "修改腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.label": "腾讯云云产品部署地域(可选)", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder": "请输入腾讯云云产品部署地域(多个值请用半角分号隔开)", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.title": "修改腾讯云云产品资源类型", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649https://cloud.tencent.com/document/product/400/119791", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.title": "修改腾讯云云产品部署地域", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.multiple_input_modal.placeholder": "请输入腾讯云云产品部署地域", "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.label": "是否更新原证书(即证书 ID 保持不变)", + "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.tooltip": "不勾选时,将调用腾讯云 OpenAPI UpdateCertificateInstance 接口;否则,将调用腾讯云 OpenAPI UploadUpdateCertificateInstance 接口。", "workflow_node.deploy.form.tencentcloud_vod_endpoint.label": "腾讯云云点播接口端点(可选)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.placeholder": "请输入腾讯云云点播接口端点(例如:vod.tencentcloudapi.com)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/266/31755

国际站用户请填写 vod.intl.tencentcloudapi.com。", From 341373d73be9ee8b43b876523832187ffe236280 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 30 Jun 2025 12:45:12 +0800 Subject: [PATCH 15/27] fix: #838 --- internal/domain/access.go | 2 +- pkg/core/ssl-deployer/providers/kong/kong.go | 64 +++++++++---------- .../access/AccessFormKongConfig.tsx | 4 +- ui/src/i18n/locales/en/nls.access.json | 2 +- ui/src/i18n/locales/zh/nls.access.json | 2 +- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/internal/domain/access.go b/internal/domain/access.go index db34cf8a..dd63ada1 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -229,7 +229,7 @@ type AccessConfigForJDCloud struct { type AccessConfigForKong struct { ServerUrl string `json:"serverUrl"` - ApiToken string `json:"apiToken"` + ApiToken string `json:"apiToken,omitempty"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } diff --git a/pkg/core/ssl-deployer/providers/kong/kong.go b/pkg/core/ssl-deployer/providers/kong/kong.go index bd325240..d2855ccf 100644 --- a/pkg/core/ssl-deployer/providers/kong/kong.go +++ b/pkg/core/ssl-deployer/providers/kong/kong.go @@ -7,8 +7,6 @@ import ( "fmt" "log/slog" "net/http" - "net/url" - "strings" "github.com/kong/go-kong/kong" @@ -21,7 +19,7 @@ type SSLDeployerProviderConfig struct { // Kong 服务地址。 ServerUrl string `json:"serverUrl"` // Kong Admin API Token。 - ApiToken string `json:"apiToken"` + ApiToken string `json:"apiToken,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` // 部署资源类型。 @@ -93,40 +91,25 @@ func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM s return err } - if d.config.Workspace == "" { - // 更新证书 - // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate - updateCertificateReq := &kong.Certificate{ - ID: kong.String(d.config.CertificateId), - Cert: kong.String(certPEM), - Key: kong.String(privkeyPEM), - SNIs: kong.StringSlice(certX509.DNSNames...), - } - updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) - d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) - } - } else { - // 更新证书 - // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate-in-workspace - updateCertificateReq := &kong.Certificate{ - ID: kong.String(d.config.CertificateId), - Cert: kong.String(certPEM), - Key: kong.String(privkeyPEM), - SNIs: kong.StringSlice(certX509.DNSNames...), - } - updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) - d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) - } + // 更新证书 + // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate + // REF: https://developer.konghq.com/api/gateway/admin-ee/3.10/#/operations/upsert-certificate-in-workspace + updateCertificateReq := &kong.Certificate{ + ID: kong.String(d.config.CertificateId), + Cert: kong.String(certPEM), + Key: kong.String(privkeyPEM), + SNIs: kong.StringSlice(certX509.DNSNames...), + } + updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) + d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) } return nil } -func createSDKClient(serverUrl, workspace, apiKey string, skipTlsVerify bool) (*kong.Client, error) { +func createSDKClient(serverUrl, workspace, apiToken string, skipTlsVerify bool) (*kong.Client, error) { httpClient := &http.Client{ Transport: xhttp.NewDefaultTransport(), Timeout: http.DefaultClient.Timeout, @@ -138,12 +121,23 @@ func createSDKClient(serverUrl, workspace, apiKey string, skipTlsVerify bool) (* } transport.TLSClientConfig.InsecureSkipVerify = true httpClient.Transport = transport + } else { + httpClient.Transport = http.DefaultTransport + } + + httpHeaders := http.Header{} + if apiToken != "" { + httpHeaders.Set("Kong-Admin-Token", apiToken) + } + + client, err := kong.NewClient(kong.String(serverUrl), kong.HTTPClientWithHeaders(httpClient, httpHeaders)) + if err != nil { + return nil, err } - baseUrl := strings.TrimRight(serverUrl, "/") if workspace != "" { - baseUrl = fmt.Sprintf("%s/%s", baseUrl, url.PathEscape(workspace)) + client.SetWorkspace(workspace) } - return kong.NewClient(kong.String(baseUrl), httpClient) + return client, nil } diff --git a/ui/src/components/access/AccessFormKongConfig.tsx b/ui/src/components/access/AccessFormKongConfig.tsx index 36669161..6ac1d0d5 100644 --- a/ui/src/components/access/AccessFormKongConfig.tsx +++ b/ui/src/components/access/AccessFormKongConfig.tsx @@ -27,7 +27,7 @@ const AccessFormKongConfig = ({ form: formInst, formName, disabled, initialValue const formSchema = z.object({ serverUrl: z.url(t("common.errmsg.url_invalid")), - apiToken: z.string().nonempty(t("access.form.kong_api_token.placeholder")), + apiToken: z.string().nullish(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -55,7 +55,7 @@ const AccessFormKongConfig = ({ form: formInst, formName, disabled, initialValue rules={[formRule]} tooltip={} > - +
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 47232e4d..0dd233d6 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -288,7 +288,7 @@ "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", "access.form.kong_server_url.label": "Kong admin API server URL", "access.form.kong_server_url.placeholder": "Please enter Kong admin API server URL", - "access.form.kong_api_token.label": "Kong admin API token", + "access.form.kong_api_token.label": "Kong admin API token (Optional)", "access.form.kong_api_token.placeholder": "Please enter Kong admin API token", "access.form.kong_api_token.tooltip": "For more information, see https://developer.konghq.com/admin-api/", "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index fc344cc0..b6a4a0be 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -288,7 +288,7 @@ "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

为空时,将使用 Pod 的 ServiceAccount 作为凭证。", "access.form.kong_server_url.label": "Kong Admin API 服务地址", "access.form.kong_server_url.placeholder": "请输入 Kong Admin API 服务地址", - "access.form.kong_api_token.label": "Kong Admin API Token", + "access.form.kong_api_token.label": "Kong Admin API Token(可选)", "access.form.kong_api_token.placeholder": "请输入 Kong Admin API Token", "access.form.kong_api_token.tooltip": "这是什么?请参阅 https://developer.konghq.com/admin-api/", "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", From 9e4b7691ce3356963e65b5d9d683fbc7bcb09d71 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 1 Jul 2025 09:48:29 +0800 Subject: [PATCH 16/27] test: add test cases --- pkg/utils/ifelse/ifelse.go | 76 ++++++++++--- pkg/utils/ifelse/ifelse_test.go | 196 ++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 13 deletions(-) create mode 100644 pkg/utils/ifelse/ifelse_test.go diff --git a/pkg/utils/ifelse/ifelse.go b/pkg/utils/ifelse/ifelse.go index fce28401..2e1bc388 100644 --- a/pkg/utils/ifelse/ifelse.go +++ b/pkg/utils/ifelse/ifelse.go @@ -1,34 +1,84 @@ package ifelse +type branch[T any] struct { + cond bool + fn func() T +} + type ifExpr[T any] struct { - condition bool + cond bool } type thenExpr[T any] struct { - condition bool - consequent T + branches []branch[T] } -// 示例: +type elseIfExpr[T any] struct { + branches []branch[T] + cond bool +} + +// 用法示例: // -// result := ifelse.If[T](condition).Then(consequent).Else(alternative) +// result := ifelse.If[string](age < 18).Then("child"). +// ElseIf(age < 60).Then("adult"). +// ElseIf(age < 120).Then("senior"). +// Else("invalid") func If[T any](condition bool) *ifExpr[T] { - return &ifExpr[T]{ - condition: condition, - } + return &ifExpr[T]{cond: condition} } func (e *ifExpr[T]) Then(consequent T) *thenExpr[T] { return &thenExpr[T]{ - condition: e.condition, - consequent: consequent, + branches: []branch[T]{ + {cond: e.cond, fn: func() T { return consequent }}, + }, + } +} + +func (e *ifExpr[T]) ThenFunc(consequent func() T) *thenExpr[T] { + return &thenExpr[T]{ + branches: []branch[T]{ + {cond: e.cond, fn: consequent}, + }, + } +} + +func (e *thenExpr[T]) ElseIf(condition bool) *elseIfExpr[T] { + return &elseIfExpr[T]{ + branches: e.branches, + cond: condition, + } +} + +func (e *elseIfExpr[T]) Then(alternative T) *thenExpr[T] { + branch := branch[T]{cond: e.cond, fn: func() T { return alternative }} + return &thenExpr[T]{ + branches: append(e.branches, branch), + } +} + +func (e *elseIfExpr[T]) ThenFunc(alternativeFunc func() T) *thenExpr[T] { + branch := branch[T]{cond: e.cond, fn: alternativeFunc} + return &thenExpr[T]{ + branches: append(e.branches, branch), } } func (e *thenExpr[T]) Else(alternative T) T { - if e.condition { - return e.consequent + for _, b := range e.branches { + if b.cond { + return b.fn() + } } - return alternative } + +func (e *thenExpr[T]) ElseFunc(alternativeFunc func() T) T { + for _, b := range e.branches { + if b.cond { + return b.fn() + } + } + return alternativeFunc() +} diff --git a/pkg/utils/ifelse/ifelse_test.go b/pkg/utils/ifelse/ifelse_test.go new file mode 100644 index 00000000..51656487 --- /dev/null +++ b/pkg/utils/ifelse/ifelse_test.go @@ -0,0 +1,196 @@ +package ifelse_test + +import ( + "testing" + + "github.com/certimate-go/certimate/pkg/utils/ifelse" +) + +func TestIfTrue(t *testing.T) { + result := ifelse.If[string](true). + Then("true branch"). + Else("false branch") + + if result != "true branch" { + t.Errorf("Expected 'true branch', got '%s'", result) + } +} + +func TestIfFalse(t *testing.T) { + result := ifelse.If[string](false). + Then("true branch"). + Else("false branch") + + if result != "false branch" { + t.Errorf("Expected 'false branch', got '%s'", result) + } +} + +func TestElseIfFirstMatch(t *testing.T) { + result := ifelse.If[string](false). + Then("should not run"). + ElseIf(true). + Then("elseif branch"). + Else("should not run") + + if result != "elseif branch" { + t.Errorf("Expected 'elseif branch', got '%s'", result) + } +} + +func TestElseIfSecondMatch(t *testing.T) { + result := ifelse.If[string](false). + Then("should not run"). + ElseIf(false). + Then("should not run"). + ElseIf(true). + Then("second elseif"). + Else("should not run") + + if result != "second elseif" { + t.Errorf("Expected 'second elseif', got '%s'", result) + } +} + +func TestMultipleConditions(t *testing.T) { + result := ifelse.If[string](1 > 2). + Then("impossible"). + ElseIf(2+2 == 5). + Then("false math"). + ElseIf(3*3 == 9). + Then("correct math"). + Else("fallback") + + if result != "correct math" { + t.Errorf("Expected 'correct math', got '%s'", result) + } +} + +func TestAllConditionsFalse(t *testing.T) { + result := ifelse.If[int](false). + Then(1). + ElseIf(false). + Then(2). + ElseIf(false). + Then(3). + Else(99) + + if result != 99 { + t.Errorf("Expected 99, got %d", result) + } +} + +func TestLazyEvaluationThen(t *testing.T) { + called := []string{} + + result := ifelse.If[string](true). + ThenFunc(func() string { + called = append(called, "then") + return "then" + }). + ElseIf(true). + ThenFunc(func() string { + called = append(called, "elseif") + return "elseif" + }). + ElseFunc(func() string { + called = append(called, "else") + return "else" + }) + + // 验证结果和调用情况 + if result != "then" { + t.Errorf("Expected 'then', got '%s'", result) + } + + if len(called) != 1 || called[0] != "then" { + t.Errorf("Expected only 'then' called, got %v", called) + } +} + +func TestLazyEvaluationElseIf(t *testing.T) { + called := []string{} + + result := ifelse.If[string](false). + ThenFunc(func() string { + called = append(called, "then") + return "then" + }). + ElseIf(true). + ThenFunc(func() string { + called = append(called, "elseif") + return "elseif" + }). + ElseFunc(func() string { + called = append(called, "else") + return "else" + }) + + // 验证结果和调用情况 + if result != "elseif" { + t.Errorf("Expected 'elseif', got '%s'", result) + } + + if len(called) != 1 || called[0] != "elseif" { + t.Errorf("Expected only 'elseif' called, got %v", called) + } +} + +func TestLazyEvaluationElse(t *testing.T) { + called := []string{} + + result := ifelse.If[string](false). + ThenFunc(func() string { + called = append(called, "then") + return "then" + }). + ElseIf(false). + ThenFunc(func() string { + called = append(called, "elseif") + return "elseif" + }). + ElseFunc(func() string { + called = append(called, "else") + return "else" + }) + + // 验证结果和调用情况 + if result != "else" { + t.Errorf("Expected 'else', got '%s'", result) + } + + if len(called) != 1 || called[0] != "else" { + t.Errorf("Expected only 'else' called, got %v", called) + } +} + +func TestMixedValueAndFunc(t *testing.T) { + result := ifelse.If[int](false). + Then(0). + ElseIf(false). + ThenFunc(func() int { + return 1 + }). + ElseIf(true). + Then(2). + Else(3) + + if result != 2 { + t.Errorf("Expected 2, got %d", result) + } +} + +func TestComplexNumericLogic(t *testing.T) { + x := 15 + result := ifelse.If[string](x < 10). + Then("single digit"). + ElseIf(x < 20). + Then("teens"). + ElseIf(x < 30). + Then("twenties"). + Else("older") + + if result != "teens" { + t.Errorf("Expected 'teens', got '%s'", result) + } +} From 37e0188db7386404fbec2a50d88569ab8baede09 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 1 Jul 2025 11:35:41 +0800 Subject: [PATCH 17/27] fix: #841 --- pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go | 7 +------ pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go index dd6f4664..5d5448c4 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "log/slog" - "strings" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" alislb "github.com/alibabacloud-go/slb-20140515/v4/client" @@ -13,7 +12,6 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-slb" - "github.com/certimate-go/certimate/pkg/utils/ifelse" ) type SSLDeployerProviderConfig struct { @@ -61,10 +59,7 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, ResourceGroupId: config.ResourceGroupId, - Region: ifelse. - If[string](config.Region == "" || strings.HasPrefix(config.Region, "cn-")). - Then("cn-hangzhou"). - Else("ap-southeast-1"), + Region: config.Region, }) if err != nil { return nil, fmt.Errorf("could not create ssl manager: %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go index 184d978c..10788365 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go @@ -13,7 +13,7 @@ import ( "github.com/alibabacloud-go/tea/tea" "github.com/certimate-go/certimate/pkg/core" - sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-slb" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" "github.com/certimate-go/certimate/pkg/utils/ifelse" ) From 2102e60c89e5626339bec3334e985c6e139c21b1 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 1 Jul 2025 14:33:43 +0800 Subject: [PATCH 18/27] fix: typo --- internal/workflow/node-processor/apply_node.go | 4 ++-- pkg/core/ssl-deployer/providers/apisix/apisix_test.go | 2 +- pkg/core/ssl-deployer/providers/flexcdn/flexcdn_test.go | 2 +- pkg/core/ssl-deployer/providers/goedge/goedge_test.go | 2 +- pkg/core/ssl-deployer/providers/kong/kong_test.go | 2 +- pkg/core/ssl-deployer/providers/lecdn/lecdn_test.go | 2 +- pkg/core/ssl-deployer/providers/safeline/safeline_test.go | 2 +- .../ssl-manager/providers/azure-keyvault/azure_keyvault.go | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index 5f73d308..7f174b15 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -37,7 +37,7 @@ func NewApplyNode(node *domain.WorkflowNode) *applyNode { func (n *applyNode) Process(ctx context.Context) error { nodeCfg := n.node.GetConfigForApply() - n.logger.Info("ready to obtain certificiate ...", slog.Any("config", nodeCfg)) + n.logger.Info("ready to obtain certificate ...", slog.Any("config", nodeCfg)) // 查询上次执行结果 lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id) @@ -67,7 +67,7 @@ func (n *applyNode) Process(ctx context.Context) error { // 申请证书 applyResult, err := applicant.Apply(ctx) if err != nil { - n.logger.Warn("failed to obtain certificiate") + n.logger.Warn("failed to obtain certificate") return err } diff --git a/pkg/core/ssl-deployer/providers/apisix/apisix_test.go b/pkg/core/ssl-deployer/providers/apisix/apisix_test.go index c9b73c27..4fe922ce 100644 --- a/pkg/core/ssl-deployer/providers/apisix/apisix_test.go +++ b/pkg/core/ssl-deployer/providers/apisix/apisix_test.go @@ -37,7 +37,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_APISIX_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_SSLDEPLOYER_APISIX_SERVERURL="http://127.0.0.1:9080" \ --CERTIMATE_SSLDEPLOYER_APISIX_APIKEY="your-api-key" \ - --CERTIMATE_SSLDEPLOYER_APISIX_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_APISIX_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-deployer/providers/flexcdn/flexcdn_test.go b/pkg/core/ssl-deployer/providers/flexcdn/flexcdn_test.go index 32ee801c..0a53acd0 100644 --- a/pkg/core/ssl-deployer/providers/flexcdn/flexcdn_test.go +++ b/pkg/core/ssl-deployer/providers/flexcdn/flexcdn_test.go @@ -40,7 +40,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_FLEXCDN_SERVERURL="http://127.0.0.1:7788" \ --CERTIMATE_SSLDEPLOYER_FLEXCDN_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_SSLDEPLOYER_FLEXCDN_ACCESSKEY="your-access-key" \ - --CERTIMATE_SSLDEPLOYER_FLEXCDN_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_FLEXCDN_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-deployer/providers/goedge/goedge_test.go b/pkg/core/ssl-deployer/providers/goedge/goedge_test.go index 757527cb..ac5cacb5 100644 --- a/pkg/core/ssl-deployer/providers/goedge/goedge_test.go +++ b/pkg/core/ssl-deployer/providers/goedge/goedge_test.go @@ -40,7 +40,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_GOEDGE_SERVERURL="http://127.0.0.1:7788" \ --CERTIMATE_SSLDEPLOYER_GOEDGE_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_SSLDEPLOYER_GOEDGE_ACCESSKEY="your-access-key" \ - --CERTIMATE_SSLDEPLOYER_GOEDGE_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_GOEDGE_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-deployer/providers/kong/kong_test.go b/pkg/core/ssl-deployer/providers/kong/kong_test.go index 4d3c2aff..5a312993 100644 --- a/pkg/core/ssl-deployer/providers/kong/kong_test.go +++ b/pkg/core/ssl-deployer/providers/kong/kong_test.go @@ -37,7 +37,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_KONG_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_SSLDEPLOYER_KONG_SERVERURL="http://127.0.0.1:9080" \ --CERTIMATE_SSLDEPLOYER_KONG_APITOKEN="your-admin-token" \ - --CERTIMATE_SSLDEPLOYER_KONG_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_KONG_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-deployer/providers/lecdn/lecdn_test.go b/pkg/core/ssl-deployer/providers/lecdn/lecdn_test.go index a94db0bd..ef15c27e 100644 --- a/pkg/core/ssl-deployer/providers/lecdn/lecdn_test.go +++ b/pkg/core/ssl-deployer/providers/lecdn/lecdn_test.go @@ -42,7 +42,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_LECDN_SERVERURL="http://127.0.0.1:5090" \ --CERTIMATE_SSLDEPLOYER_LECDN_USERNAME="your-username" \ --CERTIMATE_SSLDEPLOYER_LECDN_PASSWORD="your-password" \ - --CERTIMATE_SSLDEPLOYER_LECDN_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_LECDN_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-deployer/providers/safeline/safeline_test.go b/pkg/core/ssl-deployer/providers/safeline/safeline_test.go index 80b89839..14aaba7f 100644 --- a/pkg/core/ssl-deployer/providers/safeline/safeline_test.go +++ b/pkg/core/ssl-deployer/providers/safeline/safeline_test.go @@ -37,7 +37,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_SAFELINE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_SSLDEPLOYER_SAFELINE_SERVERURL="http://127.0.0.1:9443" \ --CERTIMATE_SSLDEPLOYER_SAFELINE_APITOKEN="your-api-token" \ - --CERTIMATE_SSLDEPLOYER_SAFELINE_CERTIFICATEID="your-cerficiate-id" + --CERTIMATE_SSLDEPLOYER_SAFELINE_CERTIFICATEID="your-certificate-id" */ func TestDeploy(t *testing.T) { flag.Parse() diff --git a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go index 79209710..b03ecafb 100644 --- a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go @@ -141,7 +141,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 生成新证书名(需符合 Azure 命名规则) certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) - // Azure Key Vault 不支持导入带有 Certificiate Chain 的 PEM 证书。 + // Azure Key Vault 不支持导入带有 Certificate Chain 的 PEM 证书。 // Issue Link: https://github.com/Azure/azure-cli/issues/19017 // 暂时的解决方法是,将 PEM 证书转换成 PFX 格式,然后再导入。 certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "") From eee8a38a719e9e3a00208d99fc4c84310816baa5 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 1 Jul 2025 21:53:10 +0800 Subject: [PATCH 19/27] chore: hide unused exec flags --- internal/app/app.go | 14 ++++++++++---- main.go | 1 - 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 11c81d8d..62de2c9a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,15 +9,21 @@ import ( "github.com/pocketbase/pocketbase/core" ) -var instance core.App - -var intanceOnce sync.Once +var ( + instance core.App + intanceOnce sync.Once +) func GetApp() core.App { intanceOnce.Do(func() { - instance = pocketbase.NewWithConfig(pocketbase.Config{ + pb := pocketbase.NewWithConfig(pocketbase.Config{ HideStartBanner: true, }) + + pb.RootCmd.Flags().MarkHidden("encryptionEnv") + pb.RootCmd.Flags().MarkHidden("queryTimeout") + + instance = pb }) return instance diff --git a/main.go b/main.go index 7184d781..8e3477c9 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,6 @@ func main() { app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error { routes.Unregister() - slog.Info("[CERTIMATE] Exit!") return e.Next() }) From b43fcc3b613e0317c7e64d87bc8ac3f625c74e75 Mon Sep 17 00:00:00 2001 From: JiangJam <39955422+JiangJamm@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:56:50 +0800 Subject: [PATCH 20/27] fix: wrong path when uploading certificates from Windows to Unix fix: https://github.com/certimate-go/certimate/issues/805 --- pkg/core/ssl-deployer/providers/ssh/ssh.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/ssl-deployer/providers/ssh/ssh.go b/pkg/core/ssl-deployer/providers/ssh/ssh.go index 94d0b6bf..7cbe7c8f 100644 --- a/pkg/core/ssl-deployer/providers/ssh/ssh.go +++ b/pkg/core/ssl-deployer/providers/ssh/ssh.go @@ -419,7 +419,7 @@ func writeFileWithSFTP(sshCli *ssh.Client, path string, data []byte) error { } defer sftpCli.Close() - if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil { + if err := sftpCli.MkdirAll(filepath.ToSlash(filepath.Dir(path))); err != nil { return fmt.Errorf("failed to create remote directory: %w", err) } From 1e4cd2b9d5fb417abe1daac59b35b711d74dfd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=83=E7=93=9C=E7=9A=84=E6=98=9F=E6=A0=B8=E7=B2=BE?= <129462191+cgxhj@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:22:29 +0800 Subject: [PATCH 21/27] feat: support configuring multiple domains deployment to tencentcloud edgeone --- internal/deployer/providers.go | 2 +- migrations/1742209200_upgrade.go | 10 +- migrations/1751961600_upgrade.go | 112 ++++++++++++++++++ .../tencentcloud-eo/tencentcloud_eo.go | 10 +- .../tencentcloud-eo/tencentcloud_eo_test.go | 10 +- ...ployNodeConfigFormTencentCloudEOConfig.tsx | 29 +++-- .../i18n/locales/en/nls.workflow.nodes.json | 8 +- .../i18n/locales/zh/nls.workflow.nodes.json | 8 +- 8 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 migrations/1751961600_upgrade.go diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 0a48d6ca..7eb58372 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -1202,7 +1202,7 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy SecretKey: access.SecretKey, Endpoint: xmaps.GetString(options.ProviderServiceConfig, "endpoint"), ZoneId: xmaps.GetString(options.ProviderServiceConfig, "zoneId"), - Domain: xmaps.GetString(options.ProviderServiceConfig, "domain"), + Domains: xslices.Filter(strings.Split(xmaps.GetString(options.ProviderServiceConfig, "domains"), ";"), func(s string) bool { return s != "" }), }) return deployer, err diff --git a/migrations/1742209200_upgrade.go b/migrations/1742209200_upgrade.go index 1bc0d482..1cc05cf3 100644 --- a/migrations/1742209200_upgrade.go +++ b/migrations/1742209200_upgrade.go @@ -270,12 +270,12 @@ func init() { Id string `json:"id"` Type string `json:"type"` Name string `json:"name"` - Config map[string]any `json:"config"` - Inputs []map[string]any `json:"inputs"` - Outputs []map[string]any `json:"outputs"` + Config map[string]any `json:"config,omitempty"` + Inputs []map[string]any `json:"inputs,omitempty"` + Outputs []map[string]any `json:"outputs,omitempty"` Next *dWorkflowNode `json:"next,omitempty"` - Branches []dWorkflowNode `json:"branches,omitempty"` - Validated bool `json:"validated"` + Branches []*dWorkflowNode `json:"branches,omitempty"` + Validated bool `json:"validated,omitempty"` } for _, workflowRun := range workflowRuns { diff --git a/migrations/1751961600_upgrade.go b/migrations/1751961600_upgrade.go new file mode 100644 index 00000000..91ac39d2 --- /dev/null +++ b/migrations/1751961600_upgrade.go @@ -0,0 +1,112 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + tracer := NewTracer("(v0.3)1751961600") + tracer.Printf("go ...") + + // migrate data + { + workflows, err := app.FindAllRecords("workflow") + if err != nil { + return err + } + + type dWorkflowNode struct { + Id string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Config map[string]any `json:"config,omitempty"` + Inputs []map[string]any `json:"inputs,omitempty"` + Outputs []map[string]any `json:"outputs,omitempty"` + Next *dWorkflowNode `json:"next,omitempty"` + Branches []*dWorkflowNode `json:"branches,omitempty"` + Validated bool `json:"validated,omitempty"` + } + + deepChangeFn := func(node *dWorkflowNode) bool { + stack := []*dWorkflowNode{node} + + for len(stack) > 0 { + current := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + if current.Type == "deploy" { + configMap := current.Config + if configMap != nil { + if provider, ok := configMap["provider"]; ok { + if provider.(string) == "tencentcloud-eo" { + if providerConfig, ok := configMap["providerConfig"]; ok { + if providerConfigMap, ok := providerConfig.(map[string]any); ok { + if _, ok := providerConfigMap["domain"]; ok { + providerConfigMap["domains"] = providerConfigMap["domain"] + delete(providerConfigMap, "domain") + configMap["providerConfig"] = providerConfigMap + return true + } + } + } + } + } + } + } + + if current.Next != nil { + stack = append(stack, current.Next) + } + + if current.Branches != nil { + for i := len(current.Branches) - 1; i >= 0; i-- { + stack = append(stack, current.Branches[i]) + } + } + } + + return false + } + + for _, workflow := range workflows { + changed := false + + rootNodeContent := &dWorkflowNode{} + if err := workflow.UnmarshalJSONField("content", rootNodeContent); err != nil { + return err + } else { + if deepChangeFn(rootNodeContent) { + workflow.Set("content", rootNodeContent) + changed = true + } + } + + rootNodeDraft := &dWorkflowNode{} + if err := workflow.UnmarshalJSONField("draft", rootNodeDraft); err != nil { + return err + } else { + if deepChangeFn(rootNodeDraft) { + workflow.Set("draft", rootNodeDraft) + changed = true + } + } + + if changed { + err = app.Save(workflow) + if err != nil { + return err + } + + tracer.Printf("record #%s in collection '%s' updated", workflow.Id, workflow.Collection().Name) + } + } + } + + tracer.Printf("done") + return nil + }, func(app core.App) error { + return nil + }) +} diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index 5540c8c8..e2fcaf7d 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -25,8 +25,8 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 站点 ID。 ZoneId string `json:"zoneId"` - // 加速域名(支持泛域名)。 - Domain string `json:"domain"` + // 加速域名列表(支持泛域名)。 + Domains []string `json:"domains"` } type SSLDeployerProvider struct { @@ -82,8 +82,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if d.config.ZoneId == "" { return nil, errors.New("config `zoneId` is required") } - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") + if len(d.config.Domains) == 0 { + return nil, errors.New("config `domains` is required") } // 上传证书 @@ -99,7 +99,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke modifyHostsCertificateReq := tcteo.NewModifyHostsCertificateRequest() modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId) modifyHostsCertificateReq.Mode = common.StringPtr("sslcert") - modifyHostsCertificateReq.Hosts = common.StringPtrs([]string{d.config.Domain}) + modifyHostsCertificateReq.Hosts = common.StringPtrs(d.config.Domains) modifyHostsCertificateReq.ServerCertInfo = []*tcteo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}} modifyHostsCertificateResp, err := d.sdkClient.ModifyHostsCertificate(modifyHostsCertificateReq) d.logger.Debug("sdk request 'teo.ModifyHostsCertificate'", slog.Any("request", modifyHostsCertificateReq), slog.Any("response", modifyHostsCertificateResp)) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go index cbca3fe3..51a440c1 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go @@ -17,7 +17,7 @@ var ( fSecretId string fSecretKey string fZoneId string - fDomain string + fDomains string ) func init() { @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") flag.StringVar(&fZoneId, argsPrefix+"ZONEID", "", "") - flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") + flag.StringVar(&fDomains, argsPrefix+"DOMAINS", "", "") } /* @@ -40,7 +40,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_TENCENTCLOUDEO_SECRETID="your-secret-id" \ --CERTIMATE_SSLDEPLOYER_TENCENTCLOUDEO_SECRETKEY="your-secret-key" \ --CERTIMATE_SSLDEPLOYER_TENCENTCLOUDEO_ZONEID="your-zone-id" \ - --CERTIMATE_SSLDEPLOYER_TENCENTCLOUDEO_DOMAIN="example.com" + --CERTIMATE_SSLDEPLOYER_TENCENTCLOUDEO_DOMAINS="example.com" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -53,14 +53,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("SECRETID: %v", fSecretId), fmt.Sprintf("SECRETKEY: %v", fSecretKey), fmt.Sprintf("ZONEID: %v", fZoneId), - fmt.Sprintf("DOMAIN: %v", fDomain), + fmt.Sprintf("DOMAINS: %v", fDomains), }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ SecretId: fSecretId, SecretKey: fSecretKey, ZoneId: fZoneId, - Domain: fDomain, + Domains: strings.Split(fDomains, ";"), }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudEOConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudEOConfig.tsx index b5daf715..7702c39a 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudEOConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudEOConfig.tsx @@ -4,11 +4,12 @@ import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod/v4"; import { validDomainName } from "@/utils/validators"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; type DeployNodeConfigFormTencentCloudEOConfigFieldValues = Nullish<{ endpoint?: string; zoneId: string; - domain: string; + domains: string; }>; export type DeployNodeConfigFormTencentCloudEOConfigProps = { @@ -23,6 +24,8 @@ const initFormModel = (): DeployNodeConfigFormTencentCloudEOConfigFieldValues => return {}; }; +const MULTIPLE_INPUT_SEPARATOR = ";"; + const DeployNodeConfigFormTencentCloudEOConfig = ({ form: formInst, formName, @@ -37,9 +40,14 @@ const DeployNodeConfigFormTencentCloudEOConfig = ({ zoneId: z .string(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")) .nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")), - domain: z - .string(t("workflow_node.deploy.form.tencentcloud_eo_domain.placeholder")) - .refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + domains: z + .string(t("workflow_node.deploy.form.tencentcloud_eo_domains.placeholder")) + .refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + }, t("common.errmsg.domain_invalid")), }); const formRule = createSchemaFieldRule(formSchema); @@ -75,12 +83,17 @@ const DeployNodeConfigFormTencentCloudEOConfig = ({
} + tooltip={} > - + ); diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 12254221..df7f1915 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -727,9 +727,11 @@ "workflow_node.deploy.form.tencentcloud_eo_zone_id.label": "Tencent Cloud EdgeOne zone ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder": "Please enter Tencent Cloud EdgeOne zone ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", - "workflow_node.deploy.form.tencentcloud_eo_domain.label": "Tencent Cloud EdgeOne domain", - "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "Please enter Tencent Cloud EdgeOne domain name", - "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", + "workflow_node.deploy.form.tencentcloud_eo_domains.label": "Tencent Cloud EdgeOne domains", + "workflow_node.deploy.form.tencentcloud_eo_domains.placeholder": "Please enter Tencent Cloud EdgeOne domain names (separated by semicolons)", + "workflow_node.deploy.form.tencentcloud_eo_domains.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", + "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.title": "Change Tencent Cloud EdgeOne domain", + "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "Please enter Tencent Cloud EdgeOne domain name", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "Tencent Cloud GAAP API endpoint (Optional)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "Please enter Tencent Cloud GAAP API endpoint (e.g. gaap.intl.tencentcloudapi.com)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "
  • gaap.intl.tencentcloudapi.com for Tencent Cloud International
  • gaap.tencentcloudapi.com for Tencent Cloud in China
", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a3f4278e..061189df 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -725,9 +725,11 @@ "workflow_node.deploy.form.tencentcloud_eo_zone_id.label": "腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", - "workflow_node.deploy.form.tencentcloud_eo_domain.label": "腾讯云 EdgeOne 加速域名", - "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "请输入腾讯云 EdgeOne 加速域名(支持泛域名)", - "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", + "workflow_node.deploy.form.tencentcloud_eo_domains.label": "腾讯云 EdgeOne 加速域名", + "workflow_node.deploy.form.tencentcloud_eo_domains.placeholder": "请输入腾讯云 EdgeOne 加速域名(支持泛域名;多个值请用半角分号隔开)", + "workflow_node.deploy.form.tencentcloud_eo_domains.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", + "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.title": "修改腾讯云 EdgeOne 加速域名", + "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "请输入腾讯云 EdgeOne 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "腾讯云 GAAP 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/608/36934

国际站用户请填写 gaap.intl.tencentcloudapi.com。", From b6c59c57ded445c0e8916a7ff5e63f6759c13bf7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 6 Jul 2025 22:47:13 +0800 Subject: [PATCH 22/27] fix: #852 --- pkg/sdk3rd/baishan/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sdk3rd/baishan/client.go b/pkg/sdk3rd/baishan/client.go index fb9a1df7..2978cd33 100644 --- a/pkg/sdk3rd/baishan/client.go +++ b/pkg/sdk3rd/baishan/client.go @@ -22,7 +22,7 @@ func NewClient(apiToken string) (*Client, error) { SetHeader("Accept", "application/json"). SetHeader("Content-Type", "application/json"). SetHeader("User-Agent", "certimate"). - SetHeader("Token", apiToken) + SetQueryParam("token", apiToken) return &Client{client}, nil } From 72cfe921d3004544679c9bdfd2ac497ebcb87204 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 6 Jul 2025 23:07:07 +0800 Subject: [PATCH 23/27] fix: #849 --- pkg/sdk3rd/upyun/console/client.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/sdk3rd/upyun/console/client.go b/pkg/sdk3rd/upyun/console/client.go index 7af3e7ae..0c54ea61 100644 --- a/pkg/sdk3rd/upyun/console/client.go +++ b/pkg/sdk3rd/upyun/console/client.go @@ -103,8 +103,11 @@ func (c *Client) doRequestWithResult(req *resty.Request, res apiResponse) (*rest if err := json.Unmarshal(resp.Body(), &res); err != nil { return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w", err) } else { - if tdata := res.GetData(); tdata == nil { - return resp, fmt.Errorf("sdkerr: empty data") + tresp := &apiResponseBase{} + if err := json.Unmarshal(resp.Body(), &tresp); err != nil { + return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w", err) + } else if tdata := tresp.GetData(); tdata == nil { + return resp, fmt.Errorf("sdkerr: received empty data") } else if terrcode := tdata.GetErrorCode(); terrcode != 0 { return resp, fmt.Errorf("sdkerr: code='%d', message='%s'", terrcode, tdata.GetMessage()) } From 424e2e7710f2c47f6f1dd54ce31a3f0c8655a079 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 7 Jul 2025 10:04:14 +0800 Subject: [PATCH 24/27] build: fix README release path --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed10b5ee..4f8efd8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -189,6 +189,8 @@ jobs: cp ../README.md "$tmpdir/README_zhCN.md" cp ../README_EN.md "$tmpdir/README_enUS.md" cp ../CHANGELOG.md "$tmpdir/CHANGELOG.md" + sed -i 's/README_EN\.md/README_enUS.md/g' "$tmpdir/README_zhCN.md" + sed -i 's/README\.md/README_zhCN.md/g' "$tmpdir/README_enUS.md" if [[ "$bin" == *".exe" ]]; then zip -j "${bin%.exe}.zip" "$tmpdir"/* From 7d272a6c524daf05c770161967fc5c1b4d8099ca Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 7 Jul 2025 17:27:04 +0800 Subject: [PATCH 25/27] fix: missing pfx certificiate chains --- pkg/utils/cert/common.go | 16 +++++++++ pkg/utils/cert/extractor.go | 26 +++++++-------- pkg/utils/cert/transformer.go | 62 +++++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/pkg/utils/cert/common.go b/pkg/utils/cert/common.go index 56703125..bdc86367 100644 --- a/pkg/utils/cert/common.go +++ b/pkg/utils/cert/common.go @@ -2,6 +2,7 @@ package cert import ( "crypto/x509" + "encoding/pem" ) // 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。 @@ -24,3 +25,18 @@ func EqualCertificate(a, b *x509.Certificate) bool { a.Issuer.SerialNumber == b.Issuer.SerialNumber && a.Subject.SerialNumber == b.Subject.SerialNumber } + +func decodePEM(data []byte) []*pem.Block { + blocks := make([]*pem.Block, 0) + for { + block, rest := pem.Decode(data) + if block == nil { + break + } + + blocks = append(blocks, block) + data = rest + } + + return blocks +} diff --git a/pkg/utils/cert/extractor.go b/pkg/utils/cert/extractor.go index 1e116b1f..b9e4607f 100644 --- a/pkg/utils/cert/extractor.go +++ b/pkg/utils/cert/extractor.go @@ -3,6 +3,7 @@ package cert import ( "encoding/pem" "errors" + "fmt" ) // 从 PEM 编码的证书字符串解析并提取服务器证书和中间证书。 @@ -15,32 +16,27 @@ import ( // - intermediaCertPEM: 中间证书的 PEM 内容。 // - err: 错误。 func ExtractCertificatesFromPEM(certPEM string) (_serverCertPEM string, _intermediaCertPEM string, _err error) { - pemBlocks := make([]*pem.Block, 0) - pemData := []byte(certPEM) - for { - block, rest := pem.Decode(pemData) - if block == nil || block.Type != "CERTIFICATE" { - break + blocks := decodePEM([]byte(certPEM)) + for i, block := range blocks { + if block.Type != "CERTIFICATE" { + return "", "", fmt.Errorf("invalid PEM block type at %d, expected 'CERTIFICATE', got '%s'", i, block.Type) } - - pemBlocks = append(pemBlocks, block) - pemData = rest } serverCertPEM := "" intermediaCertPEM := "" - if len(pemBlocks) == 0 { + if len(blocks) == 0 { return "", "", errors.New("failed to decode PEM block") } - if len(pemBlocks) > 0 { - serverCertPEM = string(pem.EncodeToMemory(pemBlocks[0])) + if len(blocks) > 0 { + serverCertPEM = string(pem.EncodeToMemory(blocks[0])) } - if len(pemBlocks) > 1 { - for i := 1; i < len(pemBlocks); i++ { - intermediaCertPEM += string(pem.EncodeToMemory(pemBlocks[i])) + if len(blocks) > 1 { + for i := 1; i < len(blocks); i++ { + intermediaCertPEM += string(pem.EncodeToMemory(blocks[i])) } } diff --git a/pkg/utils/cert/transformer.go b/pkg/utils/cert/transformer.go index bf467efa..690ae19f 100644 --- a/pkg/utils/cert/transformer.go +++ b/pkg/utils/cert/transformer.go @@ -2,8 +2,9 @@ package cert import ( "bytes" - "encoding/pem" + "crypto/x509" "errors" + "fmt" "time" "github.com/pavlo-v-chernykh/keystore-go/v4" @@ -21,9 +22,19 @@ import ( // - data: PFX 格式的证书数据。 // - err: 错误。 func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPassword string) ([]byte, error) { - cert, err := ParseCertificateFromPEM(certPEM) - if err != nil { - return nil, err + blocks := decodePEM([]byte(certPEM)) + + certs := make([]*x509.Certificate, 0, len(blocks)) + for i, block := range blocks { + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("invalid PEM block type at %d, expected 'CERTIFICATE', got '%s'", i, block.Type) + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) } privkey, err := ParsePrivateKeyFromPEM(privkeyPEM) @@ -31,12 +42,16 @@ func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPass return nil, err } - pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword) - if err != nil { - return nil, err + var pfxData []byte + if len(certs) == 0 { + return nil, errors.New("failed to decode certificate PEM") + } else if len(certs) == 1 { + pfxData, err = pkcs12.Legacy.Encode(privkey, certs[0], nil, pfxPassword) + } else { + pfxData, err = pkcs12.Legacy.Encode(privkey, certs[0], certs[1:], pfxPassword) } - return pfxData, nil + return pfxData, err } // 将 PEM 编码的证书字符串转换为 JKS 格式。 @@ -52,28 +67,33 @@ func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPass // - data: JKS 格式的证书数据。 // - err: 错误。 func TransformCertificateFromPEMToJKS(certPEM string, privkeyPEM string, jksAlias string, jksKeypass string, jksStorepass string) ([]byte, error) { - certBlock, _ := pem.Decode([]byte(certPEM)) - if certBlock == nil { + certBlocks := decodePEM([]byte(certPEM)) + if len(certBlocks) == 0 { return nil, errors.New("failed to decode certificate PEM") } - privkeyBlock, _ := pem.Decode([]byte(privkeyPEM)) - if privkeyBlock == nil { + privkeyBlocks := decodePEM([]byte(privkeyPEM)) + if len(privkeyBlocks) == 0 { return nil, errors.New("failed to decode private key PEM") } - ks := keystore.New() entry := keystore.PrivateKeyEntry{ - CreationTime: time.Now(), - PrivateKey: privkeyBlock.Bytes, - CertificateChain: []keystore.Certificate{ - { - Type: "X509", - Content: certBlock.Bytes, - }, - }, + CreationTime: time.Now(), + PrivateKey: privkeyBlocks[0].Bytes, + CertificateChain: make([]keystore.Certificate, len(certBlocks)), + } + for i, certBlock := range certBlocks { + if certBlock.Type != "CERTIFICATE" { + return nil, fmt.Errorf("invalid PEM block type at %d, expected 'CERTIFICATE', got '%s'", i, certBlock.Type) + } + + entry.CertificateChain[i] = keystore.Certificate{ + Type: "X509", + Content: certBlock.Bytes, + } } + ks := keystore.New() if err := ks.SetPrivateKeyEntry(jksAlias, entry, []byte(jksKeypass)); err != nil { return nil, err } From 2f551dd3e3db9b5ff417f6b2ee1ff64f7e36af4a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 8 Jul 2025 10:25:44 +0800 Subject: [PATCH 26/27] fix: #854 --- internal/deployer/providers.go | 11 ++- .../providers/qiniu-kodo/qiniu_kodo.go | 88 +++++++++++++++++++ .../providers/qiniu-kodo/qiniu_kodo_test.go | 75 ++++++++++++++++ .../providers/qiniu-sslcert/qiniu_sslcert.go | 81 +++++++++++++++-- .../qiniu-sslcert/qiniu_sslcert_test.go | 72 +++++++++++++++ pkg/sdk3rd/qiniu/cdn.go | 45 +--------- pkg/sdk3rd/qiniu/kodo.go | 44 ++++++++++ pkg/sdk3rd/qiniu/sslcert.go | 80 +++++++++++++++++ pkg/sdk3rd/qiniu/util.go | 14 +++ .../i18n/locales/en/nls.workflow.nodes.json | 28 +++--- .../i18n/locales/zh/nls.workflow.nodes.json | 8 +- 11 files changed, 481 insertions(+), 65 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go create mode 100644 pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go create mode 100644 pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert_test.go create mode 100644 pkg/sdk3rd/qiniu/kodo.go create mode 100644 pkg/sdk3rd/qiniu/sslcert.go create mode 100644 pkg/sdk3rd/qiniu/util.go diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 7eb58372..61ae8785 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -69,6 +69,7 @@ import ( pNetlifySite "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/netlify-site" pProxmoxVE "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/proxmoxve" pQiniuCDN "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/qiniu-cdn" + pQiniuKodo "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/qiniu-kodo" pQiniuPili "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/qiniu-pili" pRainYunRCDN "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/rainyun-rcdn" pRatPanelConsole "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/ratpanel-console" @@ -1001,7 +1002,7 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy } switch options.Provider { - case domain.DeploymentProviderTypeQiniuCDN, domain.DeploymentProviderTypeQiniuKodo: + case domain.DeploymentProviderTypeQiniuCDN: deployer, err := pQiniuCDN.NewSSLDeployerProvider(&pQiniuCDN.SSLDeployerProviderConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, @@ -1009,6 +1010,14 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy }) return deployer, err + case domain.DeploymentProviderTypeQiniuKodo: + deployer, err := pQiniuKodo.NewSSLDeployerProvider(&pQiniuKodo.SSLDeployerProviderConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: xmaps.GetString(options.ProviderServiceConfig, "domain"), + }) + return deployer, err + case domain.DeploymentProviderTypeQiniuPili: deployer, err := pQiniuPili.NewSSLDeployerProvider(&pQiniuPili.SSLDeployerProviderConfig{ AccessKey: access.AccessKey, diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go new file mode 100644 index 00000000..7dcec172 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go @@ -0,0 +1,88 @@ +package qiniukodo + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/qiniu/go-sdk/v7/auth" + + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" + qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu" +) + +type SSLDeployerProviderConfig struct { + // 七牛云 AccessKey。 + AccessKey string `json:"accessKey"` + // 七牛云 SecretKey。 + SecretKey string `json:"secretKey"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type SSLDeployerProvider struct { + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *qiniusdk.KodoManager + sslManager core.SSLManager +} + +var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) + +func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the ssl deployer provider is nil") + } + + client := qiniusdk.NewKodoManager(auth.New(config.AccessKey, config.SecretKey)) + + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + + return &SSLDeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, + }, nil +} + +func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + + d.sslManager.SetLogger(logger) +} + +func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + if d.config.Domain == "" { + return nil, fmt.Errorf("config `domain` is required") + } + + // 上传证书 + upres, err := d.sslManager.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)) + } + + // 绑定空间域名证书 + bindBucketCertResp, err := d.sdkClient.BindBucketCert(context.TODO(), d.config.Domain, upres.CertId) + d.logger.Debug("sdk request 'kodo.BindCert'", slog.String("request.domain", d.config.Domain), slog.String("request.certId", upres.CertId), slog.Any("response", bindBucketCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'kodo.BindCert': %w", err) + } + + return &core.SSLDeployResult{}, nil +} diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go new file mode 100644 index 00000000..3dfcf456 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go @@ -0,0 +1,75 @@ +package qiniukodo_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/qiniu-kodo" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_SSLDEPLOYER_QINIUKODO_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./qiniu_kodo_test.go -args \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_ACCESSKEY="your-access-key" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_SECRETKEY="your-secret-key" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + 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/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go index 07775b21..dcbe26b0 100644 --- a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go @@ -2,9 +2,12 @@ package qiniusslcert import ( "context" + "crypto/x509" "errors" "fmt" "log/slog" + "slices" + "strings" "time" "github.com/qiniu/go-sdk/v7/auth" @@ -24,7 +27,7 @@ type SSLManagerProviderConfig struct { type SSLManagerProvider struct { config *SSLManagerProviderConfig logger *slog.Logger - sdkClient *qiniusdk.CdnManager + sdkClient *qiniusdk.SslCertManager } var _ core.SSLManager = (*SSLManagerProvider)(nil) @@ -64,12 +67,80 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 生成新证书名(需符合七牛云命名规则) certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + // 遍历查询已有证书,避免重复上传 + getSslCertListMarker := "" + getSslCertListLimit := int32(200) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getSslCertListResp, err := m.sdkClient.GetSslCertList(context.TODO(), getSslCertListMarker, getSslCertListLimit) + m.logger.Debug("sdk request 'sslcert.GetList'", slog.Any("request.marker", getSslCertListMarker), slog.Any("response", getSslCertListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'sslcert.GetList': %w", err) + } + + if getSslCertListResp.Certs != nil { + for _, sslCert := range getSslCertListResp.Certs { + // 先对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, sslCert.CommonName) { + continue + } + + // 再对比证书多域名 + if !slices.Equal(certX509.DNSNames, sslCert.DnsNames) { + continue + } + + // 再对比证书有效期 + if certX509.NotBefore.Unix() != sslCert.NotBefore || certX509.NotAfter.Unix() != sslCert.NotAfter { + continue + } + + // 最后对比证书公钥算法 + switch certX509.PublicKeyAlgorithm { + case x509.RSA: + if !strings.EqualFold(sslCert.Encrypt, "RSA") { + continue + } + case x509.ECDSA: + if !strings.EqualFold(sslCert.Encrypt, "ECDSA") { + continue + } + case x509.Ed25519: + if !strings.EqualFold(sslCert.Encrypt, "ED25519") { + continue + } + default: + // 未知算法,跳过 + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: sslCert.CertID, + CertName: sslCert.Name, + }, nil + } + } + + if len(getSslCertListResp.Certs) < int(getSslCertListLimit) || getSslCertListResp.Marker == "" { + break + } else { + getSslCertListMarker = getSslCertListResp.Marker + } + } + // 上传新证书 // REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate uploadSslCertResp, err := m.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPEM, privkeyPEM) - m.logger.Debug("sdk request 'cdn.UploadSslCert'", slog.Any("response", uploadSslCertResp)) + m.logger.Debug("sdk request 'sslcert.Upload'", slog.Any("response", uploadSslCertResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UploadSslCert': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'sslcert.Upload': %w", err) } return &core.SSLManageUploadResult{ @@ -78,7 +149,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey }, nil } -func createSDKClient(accessKey, secretKey string) (*qiniusdk.CdnManager, error) { +func createSDKClient(accessKey, secretKey string) (*qiniusdk.SslCertManager, error) { if secretKey == "" { return nil, errors.New("invalid qiniu access key") } @@ -88,6 +159,6 @@ func createSDKClient(accessKey, secretKey string) (*qiniusdk.CdnManager, error) } credential := auth.New(accessKey, secretKey) - client := qiniusdk.NewCdnManager(credential) + client := qiniusdk.NewSslCertManager(credential) return client, nil } diff --git a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert_test.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert_test.go new file mode 100644 index 00000000..87e14a08 --- /dev/null +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert_test.go @@ -0,0 +1,72 @@ +package qiniusslcert_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string +) + +func init() { + argsPrefix := "CERTIMATE_SSLMANAGER_QINIUSSLCERT_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./qiniu_sslcert_test.go -args \ + --CERTIMATE_SSLMANAGER_QINIUSSLCERT_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLMANAGER_QINIUSSLCERT_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLMANAGER_QINIUSSLCERT_ACCESSKEY="your-access-key" \ + --CERTIMATE_SSLMANAGER_QINIUSSLCERT_SECRETKEY="your-secret-key" +*/ +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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + }, "\n")) + + sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := sslmanager.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/pkg/sdk3rd/qiniu/cdn.go b/pkg/sdk3rd/qiniu/cdn.go index 54a56517..74745d0b 100644 --- a/pkg/sdk3rd/qiniu/cdn.go +++ b/pkg/sdk3rd/qiniu/cdn.go @@ -2,16 +2,12 @@ package qiniu import ( "context" - "fmt" "net/http" - "strings" "github.com/qiniu/go-sdk/v7/auth" "github.com/qiniu/go-sdk/v7/client" ) -const qiniuHost = "https://api.qiniu.com" - type CdnManager struct { client *client.Client } @@ -21,16 +17,10 @@ func NewCdnManager(mac *auth.Credentials) *CdnManager { mac = auth.Default() } - client := &client.Client{&http.Client{Transport: newTransport(mac, nil)}} + client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}} return &CdnManager{client: client} } -func (m *CdnManager) urlf(pathf string, pathargs ...any) string { - path := fmt.Sprintf(pathf, pathargs...) - path = strings.TrimPrefix(path, "/") - return qiniuHost + "/" + path -} - type GetDomainInfoResponse struct { Code *int `json:"code,omitempty"` Error *string `json:"error,omitempty"` @@ -52,7 +42,7 @@ type GetDomainInfoResponse struct { func (m *CdnManager) GetDomainInfo(ctx context.Context, domain string) (*GetDomainInfoResponse, error) { resp := new(GetDomainInfoResponse) - if err := m.client.Call(ctx, resp, http.MethodGet, m.urlf("domain/%s", domain), nil); err != nil { + if err := m.client.Call(ctx, resp, http.MethodGet, urlf("domain/%s", domain), nil); err != nil { return nil, err } return resp, nil @@ -76,7 +66,7 @@ func (m *CdnManager) ModifyDomainHttpsConf(ctx context.Context, domain string, c Http2Enable: http2Enable, } resp := new(ModifyDomainHttpsConfResponse) - if err := m.client.CallWithJson(ctx, resp, http.MethodPut, m.urlf("domain/%s/httpsconf", domain), nil, req); err != nil { + if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("domain/%s/httpsconf", domain), nil, req); err != nil { return nil, err } return resp, nil @@ -100,34 +90,7 @@ func (m *CdnManager) EnableDomainHttps(ctx context.Context, domain string, certI Http2Enable: http2Enable, } resp := new(EnableDomainHttpsResponse) - if err := m.client.CallWithJson(ctx, resp, http.MethodPut, m.urlf("domain/%s/sslize", domain), nil, req); err != nil { - return nil, err - } - return resp, nil -} - -type UploadSslCertRequest struct { - Name string `json:"name"` - CommonName string `json:"common_name"` - Certificate string `json:"ca"` - PrivateKey string `json:"pri"` -} - -type UploadSslCertResponse struct { - Code *int `json:"code,omitempty"` - Error *string `json:"error,omitempty"` - CertID string `json:"certID"` -} - -func (m *CdnManager) UploadSslCert(ctx context.Context, name string, commonName string, certificate string, privateKey string) (*UploadSslCertResponse, error) { - req := &UploadSslCertRequest{ - Name: name, - CommonName: commonName, - Certificate: certificate, - PrivateKey: privateKey, - } - resp := new(UploadSslCertResponse) - if err := m.client.CallWithJson(ctx, resp, http.MethodPost, m.urlf("sslcert"), nil, req); err != nil { + if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("domain/%s/sslize", domain), nil, req); err != nil { return nil, err } return resp, nil diff --git a/pkg/sdk3rd/qiniu/kodo.go b/pkg/sdk3rd/qiniu/kodo.go new file mode 100644 index 00000000..6a3245a2 --- /dev/null +++ b/pkg/sdk3rd/qiniu/kodo.go @@ -0,0 +1,44 @@ +package qiniu + +import ( + "context" + "net/http" + + "github.com/qiniu/go-sdk/v7/auth" + "github.com/qiniu/go-sdk/v7/client" +) + +type KodoManager struct { + client *client.Client +} + +func NewKodoManager(mac *auth.Credentials) *KodoManager { + if mac == nil { + mac = auth.Default() + } + + client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}} + return &KodoManager{client: client} +} + +type BindBucketCertRequest struct { + CertID string `json:"certid"` + Domain string `json:"domain"` +} + +type BindBucketCertResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` +} + +func (m *KodoManager) BindBucketCert(ctx context.Context, domain string, certId string) (*BindBucketCertResponse, error) { + req := &BindBucketCertRequest{ + CertID: certId, + Domain: domain, + } + resp := new(BindBucketCertResponse) + if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("cert/bind"), nil, req); err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/sdk3rd/qiniu/sslcert.go b/pkg/sdk3rd/qiniu/sslcert.go new file mode 100644 index 00000000..f9784270 --- /dev/null +++ b/pkg/sdk3rd/qiniu/sslcert.go @@ -0,0 +1,80 @@ +package qiniu + +import ( + "context" + "net/http" + "net/url" + + "github.com/qiniu/go-sdk/v7/auth" + "github.com/qiniu/go-sdk/v7/client" +) + +type SslCertManager struct { + client *client.Client +} + +func NewSslCertManager(mac *auth.Credentials) *SslCertManager { + if mac == nil { + mac = auth.Default() + } + + client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}} + return &SslCertManager{client: client} +} + +type GetSslCertListResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + Certs []*struct { + CertID string `json:"certid"` + Name string `json:"name"` + CommonName string `json:"common_name"` + DnsNames []string `json:"dnsnames"` + CreateTime int64 `json:"create_time"` + NotBefore int64 `json:"not_before"` + NotAfter int64 `json:"not_after"` + ProductType string `json:"product_type"` + ProductShortName string `json:"product_short_name,omitempty"` + OrderId string `json:"orderid,omitempty"` + CertType string `json:"cert_type"` + Encrypt string `json:"encrypt"` + EncryptParameter string `json:"encryptParameter,omitempty"` + Enable bool `json:"enable"` + } `json:"certs"` + Marker string `json:"marker"` +} + +func (m *SslCertManager) GetSslCertList(ctx context.Context, marker string, limit int32) (*GetSslCertListResponse, error) { + resp := new(GetSslCertListResponse) + if err := m.client.Call(ctx, resp, http.MethodGet, urlf("sslcert?marker=%s&limit=%d", url.QueryEscape(marker), limit), nil); err != nil { + return nil, err + } + return resp, nil +} + +type UploadSslCertRequest struct { + Name string `json:"name"` + CommonName string `json:"common_name"` + Certificate string `json:"ca"` + PrivateKey string `json:"pri"` +} + +type UploadSslCertResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + CertID string `json:"certID"` +} + +func (m *SslCertManager) UploadSslCert(ctx context.Context, name string, commonName string, certificate string, privateKey string) (*UploadSslCertResponse, error) { + req := &UploadSslCertRequest{ + Name: name, + CommonName: commonName, + Certificate: certificate, + PrivateKey: privateKey, + } + resp := new(UploadSslCertResponse) + if err := m.client.CallWithJson(ctx, resp, http.MethodPost, urlf("sslcert"), nil, req); err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/sdk3rd/qiniu/util.go b/pkg/sdk3rd/qiniu/util.go new file mode 100644 index 00000000..0957a310 --- /dev/null +++ b/pkg/sdk3rd/qiniu/util.go @@ -0,0 +1,14 @@ +package qiniu + +import ( + "fmt" + "strings" +) + +const qiniuHost = "https://api.qiniu.com" + +func urlf(pathf string, pathargs ...any) string { + path := fmt.Sprintf(pathf, pathargs...) + path = strings.TrimPrefix(path, "/") + return qiniuHost + "/" + path +} diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index df7f1915..bc5a16a1 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -268,8 +268,8 @@ "workflow_node.deploy.form.aliyun_oss_bucket.label": "Alibaba Cloud OSS bucket", "workflow_node.deploy.form.aliyun_oss_bucket.placeholder": "Please enter Alibaba Cloud OSS bucket name", "workflow_node.deploy.form.aliyun_oss_bucket.tooltip": "For more information, see https://oss.console.aliyun.com", - "workflow_node.deploy.form.aliyun_oss_domain.label": "Alibaba Cloud OSS domain", - "workflow_node.deploy.form.aliyun_oss_domain.placeholder": "Please enter Alibaba Cloud OSS domain name", + "workflow_node.deploy.form.aliyun_oss_domain.label": "Alibaba Cloud OSS custom domain", + "workflow_node.deploy.form.aliyun_oss_domain.placeholder": "Please enter Alibaba Cloud OSS bucket custom domain name", "workflow_node.deploy.form.aliyun_oss_domain.tooltip": "For more information, see https://oss.console.aliyun.com", "workflow_node.deploy.form.aliyun_vod_region.label": "Alibaba Cloud VOD region", "workflow_node.deploy.form.aliyun_vod_region.placeholder": "Please enter Alibaba Cloud VOD region (e.g. cn-hangzhou)", @@ -601,8 +601,8 @@ "workflow_node.deploy.form.qiniu_cdn_domain.label": "Qiniu CDN domain", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "Please enter Qiniu CDN domain name", "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "For more information, see https://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.label": "Qiniu Kodo custom domain", + "workflow_node.deploy.form.qiniu_kodo_domain.placeholder": "Please enter Qiniu Kodo bucket custom 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", @@ -706,8 +706,8 @@ "workflow_node.deploy.form.tencentcloud_cos_bucket.label": "Tencent Cloud COS bucket", "workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder": "Please enter Tencent Cloud COS bucket name", "workflow_node.deploy.form.tencentcloud_cos_bucket.tooltip": "For more information, see https://console.tencentcloud.com/cos", - "workflow_node.deploy.form.tencentcloud_cos_domain.label": "Tencent Cloud COS domain", - "workflow_node.deploy.form.tencentcloud_cos_domain.placeholder": "Please enter Tencent Cloud COS domain name", + "workflow_node.deploy.form.tencentcloud_cos_domain.label": "Tencent Cloud COS custom domain", + "workflow_node.deploy.form.tencentcloud_cos_domain.placeholder": "Please enter Tencent Cloud COS bucket custom domain name", "workflow_node.deploy.form.tencentcloud_cos_domain.tooltip": "For more information, see https://console.tencentcloud.com/cos", "workflow_node.deploy.form.tencentcloud_css_endpoint.label": "Tencent Cloud CSS API endpoint (Optional)", "workflow_node.deploy.form.tencentcloud_css_endpoint.placeholder": "Please enter Tencent Cloud CSS API endpoint (e.g. live.intl.tencentcloudapi.com)", @@ -824,8 +824,8 @@ "workflow_node.deploy.form.ucloud_us3_bucket.label": "UCloud US3 bucket", "workflow_node.deploy.form.ucloud_us3_bucket.placeholder": "Please enter UCloud US3 bucket name", "workflow_node.deploy.form.ucloud_us3_bucket.tooltip": "For more information, see https://console.ucloud-global.com/ufile", - "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.label": "UCloud US3 custom domain", + "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 bucket custom domain name", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see https://console.ucloud-global.com/ufile", "workflow_node.deploy.form.unicloud_webhost.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the uniCloud, please create a GitHub Issue.", "workflow_node.deploy.form.unicloud_webhost_space_provider.label": "uniCloud space provider", @@ -842,8 +842,8 @@ "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.upyun_file.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", - "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.label": "UPYUN USS custom domain", + "workflow_node.deploy.form.upyun_file_domain.placeholder": "Please enter UPYUN USS bucket custom 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_alb_region.label": "VolcEngine ALB region", "workflow_node.deploy.form.volcengine_alb_region.placeholder": "Please enter VolcEngine ALB region (e.g. cn-beijing)", @@ -888,8 +888,8 @@ "workflow_node.deploy.form.volcengine_imagex_service_id.label": "VolcEngine ImageX service ID", "workflow_node.deploy.form.volcengine_imagex_service_id.placeholder": "Please enter VolcEngine ImageX service ID", "workflow_node.deploy.form.volcengine_imagex_service_id.tooltip": "For more information, see https://console.volcengine.com/imagex", - "workflow_node.deploy.form.volcengine_imagex_domain.label": "VolcEngine ImageX domain", - "workflow_node.deploy.form.volcengine_imagex_domain.placeholder": "Please enter VolcEngine ImageX domain name", + "workflow_node.deploy.form.volcengine_imagex_domain.label": "VolcEngine ImageX custom domain", + "workflow_node.deploy.form.volcengine_imagex_domain.placeholder": "Please enter VolcEngine ImageX custom domain name", "workflow_node.deploy.form.volcengine_imagex_domain.tooltip": "For more information, see https://console.volcengine.com/imagex", "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine Live streaming domain", "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine Live streaming domain name", @@ -900,8 +900,8 @@ "workflow_node.deploy.form.volcengine_tos_bucket.label": "VolcEngine TOS bucket", "workflow_node.deploy.form.volcengine_tos_bucket.placeholder": "Please enter VolcEngine TOS bucket name", "workflow_node.deploy.form.volcengine_tos_bucket.tooltip": "For more information, see https://console.volcengine.com/tos", - "workflow_node.deploy.form.volcengine_tos_domain.label": "VolcEngine TOS domain", - "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "Please enter VolcEngine TOS domain name", + "workflow_node.deploy.form.volcengine_tos_domain.label": "VolcEngine TOS custom domain", + "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "Please enter VolcEngine TOS bucket custom domain name", "workflow_node.deploy.form.volcengine_tos_domain.tooltip": "For more information, see https://console.volcengine.com/tos", "workflow_node.deploy.form.wangsu_cdn_domains.label": "Wangsu Cloud CDN domains", "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "Please enter Wangsu Cloud CDN domain names (separated by semicolons)", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 061189df..09fc27e9 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -599,8 +599,8 @@ "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.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": "请输入七牛云视频直播空间名", @@ -840,8 +840,8 @@ "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.guide": "小贴士:由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", - "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储加速域名", - "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储加速域名", + "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_alb_resource_type.label": "证书部署方式", "workflow_node.deploy.form.volcengine_alb_resource_type.placeholder": "请选择证书部署方式", From 07d5c2051cceb65e38f931de98f1fb123561f8ac Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 8 Jul 2025 10:37:44 +0800 Subject: [PATCH 27/27] fix: missing valid from field --- .../workflow/node/DeployNodeConfigFormSSHConfig.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index ebeb1cff..132de43f 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -152,9 +152,11 @@ chmod 755 "$fnCertPath" chmod 755 "$fnKeyPath" # 更新数据库 +NEW_EFFECT_DATE=$(openssl x509 -startdate -noout -in "$fnCertPath" | sed "s/^.*=\\(.*\\)$/\\1/") +NEW_EFFECT_TIMESTAMP=$(date -d "$NEW_EFFECT_DATE" +%s%3N) NEW_EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$fnCertPath" | sed "s/^.*=\\(.*\\)$/\\1/") NEW_EXPIRY_TIMESTAMP=$(date -d "$NEW_EXPIRY_DATE" +%s%3N) -psql -U postgres -d trim_connect -c "UPDATE cert SET valid_to=$NEW_EXPIRY_TIMESTAMP WHERE domain='$domain'" +psql -U postgres -d trim_connect -c "UPDATE cert SET valid_from=$NEW_EFFECT_TIMESTAMP, valid_to=$NEW_EXPIRY_TIMESTAMP WHERE domain='$domain'" # 重启服务 systemctl restart webdav.service