From f0ec7a12ea9cef771ddb5ea63135ca0d33ece54f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 27 Aug 2026 17:14:31 +0800 Subject: [PATCH] feat(provider): new purge provider: jdcloud ssl --- internal/certmgmt/purgers/sp_jdcloud_ssl.go | 25 +++ internal/domain/provider.go | 1 + .../providers/ucloud-ualb/ucloud_ualb.go | 2 +- .../jdcloud-ssl/jdcloud_ssl_test.go | 44 ++++++ .../providers/jdcloud-ssl/jdcloud_ssl.go | 147 ++++++++++++++++++ .../services/ssl/client/SslClient.go | 19 +++ ui/src/domain/provider.ts | 2 + ui/src/i18n/resources/en/nls.provider.json | 1 + ui/src/i18n/resources/zh/nls.provider.json | 1 + 9 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 internal/certmgmt/purgers/sp_jdcloud_ssl.go create mode 100644 pkg/core/purger/providers-tester/jdcloud-ssl/jdcloud_ssl_test.go create mode 100644 pkg/core/purger/providers/jdcloud-ssl/jdcloud_ssl.go diff --git a/internal/certmgmt/purgers/sp_jdcloud_ssl.go b/internal/certmgmt/purgers/sp_jdcloud_ssl.go new file mode 100644 index 000000000..2da8bc501 --- /dev/null +++ b/internal/certmgmt/purgers/sp_jdcloud_ssl.go @@ -0,0 +1,25 @@ +package purgers + +import ( + "fmt" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core" + pgrimpl "github.com/certimate-go/certimate/pkg/core/purger/providers/jdcloud-ssl" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + Registries.MustRegister(domain.PurgeProviderTypeJDCloudSSL, func(options *ProviderFactoryOptions) (core.Purger, error) { + credentials := domain.AccessConfigForJDCloud{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := pgrimpl.NewPurger(&pgrimpl.PurgerConfig{ + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + }) + return provider, err + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index ab05abf09..c71cc510d 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -499,6 +499,7 @@ const ( PurgeProviderTypeHuaweiCloudELB = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-elb") PurgeProviderTypeHuaweiCloudSCM = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-scm") PurgeProviderTypeHuaweiCloudWAF = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-waf") + PurgeProviderTypeJDCloudSSL = PurgeProviderType(AccessProviderTypeJDCloud + "-ssl") PurgeProviderTypeTencentCloudGAAP = PurgeProviderType(AccessProviderTypeTencentCloud + "-gaap") PurgeProviderTypeTencentCloudSSL = PurgeProviderType(AccessProviderTypeTencentCloud + "-ssl") PurgeProviderTypeTencentCloudTSE = PurgeProviderType(AccessProviderTypeTencentCloud + "-tse") diff --git a/pkg/core/deployer/providers/ucloud-ualb/ucloud_ualb.go b/pkg/core/deployer/providers/ucloud-ualb/ucloud_ualb.go index 6c9bfa0c1..15f7d3408 100644 --- a/pkg/core/deployer/providers/ucloud-ualb/ucloud_ualb.go +++ b/pkg/core/deployer/providers/ucloud-ualb/ucloud_ualb.go @@ -280,7 +280,7 @@ func (d *Deployer) updateListenerSniCertificate(ctx context.Context, cloudLoadba sslItem := describeSSLV2Resp.DataSet[0] if sslItem.Domains == d.config.Domain { - sslIdsToDelete = append(sslIdsToDelete, sslItem.SSLId) // 同域名证书需要删除 + sslIdsToDelete = append(sslIdsToDelete, sslItem.SSLId) continue } } diff --git a/pkg/core/purger/providers-tester/jdcloud-ssl/jdcloud_ssl_test.go b/pkg/core/purger/providers-tester/jdcloud-ssl/jdcloud_ssl_test.go new file mode 100644 index 000000000..08957651c --- /dev/null +++ b/pkg/core/purger/providers-tester/jdcloud-ssl/jdcloud_ssl_test.go @@ -0,0 +1,44 @@ +//go:build tester + +package jdcloudssl_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + tester "github.com/certimate-go/certimate/pkg/core/purger/providers-tester" + impl "github.com/certimate-go/certimate/pkg/core/purger/providers/jdcloud-ssl" +) + +var ( + fp = tester.InitArgs("JDCLOUDSSL_") + fAccessKeyId string + fAccessKeySecret string +) + +func init() { + fp.DefineString(&fAccessKeyId, "ACCESSKEYID") + fp.DefineString(&fAccessKeySecret, "ACCESSKEYSECRET") +} + +/* +Shell command to run this test: + + go test -tags=tester -v ./jdcloud_ssl_test.go -args \ + --JDCLOUDSSL_ACCESSKEYID="your-access-key-id" \ + --JDCLOUDSSL_ACCESSKEYSECRET="your-access-key-secret" +*/ +func TestProvider(t *testing.T) { + fp.Parse() + + t.Run("Purge", func(t *testing.T) { + provider, err := impl.NewPurger(&impl.PurgerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + }) + require.NoError(t, err) + + tester.Purge(t, provider, tester.PurgeInput{}) + }) +} diff --git a/pkg/core/purger/providers/jdcloud-ssl/jdcloud_ssl.go b/pkg/core/purger/providers/jdcloud-ssl/jdcloud_ssl.go new file mode 100644 index 000000000..1da6a2b69 --- /dev/null +++ b/pkg/core/purger/providers/jdcloud-ssl/jdcloud_ssl.go @@ -0,0 +1,147 @@ +package jdcloudssl + +import ( + "context" + "fmt" + "log/slog" + "time" + + jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" + jdsslapis "github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/apis" + + jdssl "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client" + + "github.com/certimate-go/certimate/pkg/core" + xloop "github.com/certimate-go/certimate/pkg/utils/loop" + xwait "github.com/certimate-go/certimate/pkg/utils/wait" +) + +type ( + Provider = core.Purger + PurgeResult = core.PurgerPurgeResult +) + +type PurgerConfig struct { + // 京东云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 京东云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` +} + +type Purger struct { + config *PurgerConfig + logger *slog.Logger + sdkClient *jdssl.SslClient +} + +var _ Provider = (*Purger)(nil) + +func NewPurger(config *PurgerConfig) (*Purger, error) { + if config == nil { + return nil, fmt.Errorf("the configuration of the purger provider is nil") + } + + client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("could not create client: %w", err) + } + + return &Purger{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (p *Purger) SetLogger(logger *slog.Logger) { + if logger == nil { + p.logger = slog.New(slog.DiscardHandler) + } else { + p.logger = logger + } +} + +func (p *Purger) Purge(ctx context.Context, expiry time.Duration) (*PurgeResult, error) { + purgingCertIds := make([]string, 0) + + // 查看证书列表 + // REF: https://docs.jdcloud.com/cn/ssl-certificate/api/describecerts + describeCertsPageNumber := 1 + describeCertsPageSize := 10 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeCertsReq := jdsslapis.NewDescribeCertsRequestWithoutParam() + describeCertsReq.SetPageNumber(describeCertsPageNumber) + describeCertsReq.SetPageSize(describeCertsPageSize) + describeCertsResp, err := p.sdkClient.DescribeCerts(describeCertsReq) + p.logger.Debug("sdk request 'ssl.DescribeCerts'", slog.Any("request", describeCertsReq), slog.Any("response", describeCertsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCerts': %w", err) + } + + for _, certItem := range describeCertsResp.Result.CertListDetails { + certNotAfter, err := time.Parse(time.RFC3339, certItem.EndTime) + if err != nil { + continue + } + + if time.Since(certNotAfter) >= expiry { + purgingCertIds = append(purgingCertIds, certItem.CertId) + } + } + + if len(describeCertsResp.Result.CertListDetails) < int(describeCertsPageSize) { + break + } + + describeCertsPageNumber++ + } + + // 删除证书 + // REF: https://docs.jdcloud.com/cn/ssl-certificate/api/deletecerts + purgedCount := 0 + if len(purgingCertIds) > 0 { + p.logger.Info("found certificates to purge", slog.Any("certIds", purgingCertIds)) + + if err := xloop.ForRangeAllWithContext(ctx, purgingCertIds, func(ctx context.Context, certId string, i int) error { + if i > 0 { + if err := xwait.DelayWithContext(ctx, 1*time.Second); err != nil { + return err + } + } + + deleteCertsReq := jdsslapis.NewDeleteCertsRequestWithoutParam() + deleteCertsReq.SetCertId(certId) + deleteCertResp, err := p.sdkClient.DeleteCerts(deleteCertsReq) + p.logger.Debug("sdk request 'ssl.DeleteCerts'", slog.Any("request", deleteCertsReq), slog.Any("response", deleteCertResp)) + if err != nil { + if deleteCertResp != nil && deleteCertResp.Error.Code == 404 { + return nil + } + + return fmt.Errorf("failed to execute sdk request 'ssl.DeleteCerts': %w", err) + } + + purgedCount++ + return nil + }); err != nil { + return nil, err + } + } + + return &PurgeResult{ + Count: purgedCount, + }, nil +} + +func createSDKClient(accessKeyId, accessKeySecret string) (*jdssl.SslClient, error) { + clientCredentials := jdcore.NewCredentials(accessKeyId, accessKeySecret) + client := jdssl.NewSslClient(clientCredentials) + client.DisableLogger() + return client, nil +} diff --git a/pkg/sdk3rd-trimmed/github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client/SslClient.go b/pkg/sdk3rd-trimmed/github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client/SslClient.go index 3b1a734dd..8a6338364 100644 --- a/pkg/sdk3rd-trimmed/github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client/SslClient.go +++ b/pkg/sdk3rd-trimmed/github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client/SslClient.go @@ -35,6 +35,25 @@ func (c *SslClient) DisableLogger() { c.Logger = core.NewDummyLogger() } +func (c *SslClient) DeleteCerts(request *ssl.DeleteCertsRequest) (*ssl.DeleteCertsResponse, error) { + if request == nil { + return nil, errors.New("Request object is nil.") + } + resp, err := c.Send(request, c.ServiceName) + if err != nil { + return nil, err + } + + jdResp := &ssl.DeleteCertsResponse{} + err = json.Unmarshal(resp, jdResp) + if err != nil { + c.Logger.Log(core.LogError, "Unmarshal json failed, resp: %s", string(resp)) + return nil, err + } + + return jdResp, err +} + func (c *SslClient) DescribeCerts(request *ssl.DescribeCertsRequest) (*ssl.DescribeCertsResponse, error) { if request == nil { return nil, errors.New("Request object is nil.") diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 3aee417a3..5dcfecfb6 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -993,6 +993,7 @@ export const PURGE_PROVIDERS = Object.freeze( HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`, HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`, HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`, + JDCLOUD_SSL: `${ACCESS_PROVIDERS.JDCLOUD}-ssl`, TENCENTCLOUD_GAAP: `${ACCESS_PROVIDERS.TENCENTCLOUD}-gaap`, TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`, TENCENTCLOUD_TSE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-tse`, @@ -1021,6 +1022,7 @@ export const purgeProvidersMap: Map ).map(([type, name]) => [ diff --git a/ui/src/i18n/resources/en/nls.provider.json b/ui/src/i18n/resources/en/nls.provider.json index f50deabd3..f0782b955 100644 --- a/ui/src/i18n/resources/en/nls.provider.json +++ b/ui/src/i18n/resources/en/nls.provider.json @@ -183,6 +183,7 @@ "jdcloud_dns": "JD Cloud - DNS", "jdcloud_live": "JD Cloud - Live Video", "jdcloud_vod": "JD Cloud - VOD (Video on Demand)", + "jdcloud_ssl": "JD Cloud - SSL Certificate", "jdcloud_ssl_upload": "JD Cloud - Upload to SSL Certificate", "jdcloud_waf": "JD Cloud - WAF (Web Application Firewall)", "kong": "Kong", diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json index e1af7b562..0bb712da5 100644 --- a/ui/src/i18n/resources/zh/nls.provider.json +++ b/ui/src/i18n/resources/zh/nls.provider.json @@ -183,6 +183,7 @@ "jdcloud_dns": "京东云 - 云解析 DNS", "jdcloud_live": "京东云 - 视频直播 Live", "jdcloud_vod": "京东云 - 视频点播 VOD", + "jdcloud_ssl": "京东云 - SSL 数字证书", "jdcloud_ssl_upload": "京东云 - 上传到 SSL 数字证书", "jdcloud_waf": "京东云 - Web 应用防火墙 WAF", "kong": "Kong",