mirror of
https://github.com/certimate-go/certimate.git
synced 2026-10-16 07:05:59 +08:00
feat(provider): new purge provider: tencentcloud gaap
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package purgers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
prgimpl "github.com/certimate-go/certimate/pkg/core/purger/providers/tencentcloud-gaap"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.PurgeProviderTypeTencentCloudGAAP, func(options *ProviderFactoryOptions) (core.Purger, error) {
|
||||
credentials := domain.AccessConfigForTencentCloud{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := prgimpl.NewPurger(&prgimpl.PurgerConfig{
|
||||
SecretId: credentials.SecretId,
|
||||
SecretKey: credentials.SecretKey,
|
||||
ProjectId: credentials.ProjectId,
|
||||
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -492,10 +492,11 @@ func (t PurgeProviderType) String() string {
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
const (
|
||||
PurgeProviderTypeAliyunCAS = PurgeProviderType(AccessProviderTypeAliyun + "-cas")
|
||||
PurgeProviderTypeAliyunCLB = PurgeProviderType(AccessProviderTypeAliyun + "-clb")
|
||||
PurgeProviderTypeAliyunESA = PurgeProviderType(AccessProviderTypeAliyun + "-esa")
|
||||
PurgeProviderTypeTencentCloudSSL = PurgeProviderType(AccessProviderTypeTencentCloud + "-ssl")
|
||||
PurgeProviderTypeAliyunCAS = PurgeProviderType(AccessProviderTypeAliyun + "-cas")
|
||||
PurgeProviderTypeAliyunCLB = PurgeProviderType(AccessProviderTypeAliyun + "-clb")
|
||||
PurgeProviderTypeAliyunESA = PurgeProviderType(AccessProviderTypeAliyun + "-esa")
|
||||
PurgeProviderTypeTencentCloudGAAP = PurgeProviderType(AccessProviderTypeTencentCloud + "-gaap")
|
||||
PurgeProviderTypeTencentCloudSSL = PurgeProviderType(AccessProviderTypeTencentCloud + "-ssl")
|
||||
)
|
||||
|
||||
type NotificationProviderType string
|
||||
|
||||
@@ -76,7 +76,7 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
|
||||
}
|
||||
|
||||
// 查询服务器证书列表,避免重复上传
|
||||
// REF: https://cloud.tencent.com/document/api/1364/98588
|
||||
// REF: https://cloud.tencent.com/document/api/608/36977
|
||||
// REF: https://cloud.tencent.com/document/api/608/36978
|
||||
describeCertificatesOffset := 0
|
||||
describeCertificatesLimit := 100
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
//go:build tester
|
||||
|
||||
package tencentcloudgaap_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/tencentcloud-gaap"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.InitArgs("TENCENTCLOUDGAAP_")
|
||||
fSecretId string
|
||||
fSecretKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fSecretId, "SECRETID")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -tags=tester -v ./tencentcloud_gaap_test.go -args \
|
||||
--TENCENTCLOUDGAAP_SECRETID="your-secret-id" \
|
||||
--TENCENTCLOUDGAAP_SECRETKEY="your-secret-key"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Purge", func(t *testing.T) {
|
||||
provider, err := impl.NewPurger(&impl.PurgerConfig{
|
||||
SecretId: fSecretId,
|
||||
SecretKey: fSecretKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tester.Purge(t, provider, tester.PurgeInput{})
|
||||
})
|
||||
}
|
||||
@@ -97,10 +97,6 @@ func (p *Purger) Purge(ctx context.Context, expiry time.Duration) (*PurgeResult,
|
||||
|
||||
for _, certItem := range listCertificatesResp.Body.CertificateList {
|
||||
certNotAfter := time.UnixMilli(tea.Int64Value(certItem.NotAfter))
|
||||
if certNotAfter.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if time.Since(certNotAfter) >= expiry {
|
||||
purgingCertIds = append(purgingCertIds, tea.StringValue(certItem.CertificateId))
|
||||
}
|
||||
|
||||
@@ -91,10 +91,6 @@ func (p *Purger) Purge(ctx context.Context, expiry time.Duration) (*PurgeResult,
|
||||
|
||||
for _, certItem := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate {
|
||||
certNotAfter := time.Unix(tea.Int64Value(certItem.ExpireTimeStamp), 0)
|
||||
if certNotAfter.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if time.Since(certNotAfter) >= expiry {
|
||||
purgingCertIds = append(purgingCertIds, tea.StringValue(certItem.ServerCertificateId))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package tencentcloudgaap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
tcerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
|
||||
tcgaap "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap/v20180529"
|
||||
|
||||
"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 {
|
||||
// 腾讯云 SecretId。
|
||||
SecretId string `json:"secretId"`
|
||||
// 腾讯云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
// 腾讯云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
// 腾讯云接口端点。
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
}
|
||||
|
||||
type Purger struct {
|
||||
config *PurgerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *tcgaap.Client
|
||||
}
|
||||
|
||||
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.SecretId, config.SecretKey, config.Endpoint)
|
||||
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://cloud.tencent.com/document/api/608/36977
|
||||
describeCertificatesOffset := 0
|
||||
describeCertificatesLimit := 100
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
describeCertificatesReq := tcgaap.NewDescribeCertificatesRequest()
|
||||
describeCertificatesReq.CertificateType = common.Int64Ptr(2)
|
||||
describeCertificatesReq.Offset = common.Uint64Ptr(uint64(describeCertificatesOffset))
|
||||
describeCertificatesReq.Limit = common.Uint64Ptr(uint64(describeCertificatesLimit))
|
||||
describeCertificatesResp, err := p.sdkClient.DescribeCertificatesWithContext(ctx, describeCertificatesReq)
|
||||
p.logger.Debug("sdk request 'gaap.DescribeCertificates'", slog.Any("request", describeCertificatesReq), slog.Any("response", describeCertificatesResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'gaap.DescribeCertificates': %w", err)
|
||||
}
|
||||
|
||||
for _, certItem := range describeCertificatesResp.Response.CertificateSet {
|
||||
certNotAfter := time.Unix(int64(lo.FromPtr(certItem.EndTime)), 0)
|
||||
if time.Since(certNotAfter) >= expiry {
|
||||
purgingCertIds = append(purgingCertIds, lo.FromPtr(certItem.CertificateId))
|
||||
}
|
||||
}
|
||||
|
||||
if len(describeCertificatesResp.Response.CertificateSet) < describeCertificatesLimit {
|
||||
break
|
||||
}
|
||||
|
||||
describeCertificatesOffset += describeCertificatesLimit
|
||||
}
|
||||
|
||||
// 删除证书
|
||||
// REF: https://cloud.tencent.com/document/api/608/36979
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
deleteCertificateReq := tcgaap.NewDeleteCertificateRequest()
|
||||
deleteCertificateReq.CertificateId = common.StringPtr(certId)
|
||||
deleteCertificateResp, err := p.sdkClient.DeleteCertificateWithContext(ctx, deleteCertificateReq)
|
||||
p.logger.Debug("sdk request 'gaap.DeleteCertificate'", slog.Any("request", deleteCertificateReq), slog.Any("response", deleteCertificateResp))
|
||||
if err != nil {
|
||||
if sdkErr, ok := err.(*tcerrors.TencentCloudSDKError); ok {
|
||||
if sdkErrCode := sdkErr.Code; sdkErrCode == "ResourceNotFound" || sdkErrCode == "FailedOperation.CertificateIsUsing" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to execute sdk request 'gaap.DeleteCertificate': %w", err)
|
||||
}
|
||||
|
||||
purgedCount++
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &PurgeResult{
|
||||
Count: purgedCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSDKClient(secretId, secretKey, endpoint string) (*tcgaap.Client, error) {
|
||||
credential := common.NewCredential(secretId, secretKey)
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
if endpoint != "" {
|
||||
cpf.HttpProfile.Endpoint = endpoint
|
||||
}
|
||||
|
||||
client, err := tcgaap.NewClient(credential, "", cpf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
+25
@@ -48,6 +48,31 @@ func (c *Client) CreateCertificateWithContext(ctx context.Context, request *Crea
|
||||
return
|
||||
}
|
||||
|
||||
func NewDeleteCertificateRequest() (request *DeleteCertificateRequest) {
|
||||
return gaap.NewDeleteCertificateRequest()
|
||||
}
|
||||
|
||||
func NewDeleteCertificateResponse() (response *DeleteCertificateResponse) {
|
||||
return gaap.NewDeleteCertificateResponse()
|
||||
}
|
||||
|
||||
func (c *Client) DeleteCertificateWithContext(ctx context.Context, request *DeleteCertificateRequest) (response *DeleteCertificateResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewDeleteCertificateRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "gaap", APIVersion, "DeleteCertificate")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("DeleteCertificate require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewDeleteCertificateResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
func NewDescribeCertificatesRequest() (request *DescribeCertificatesRequest) {
|
||||
return gaap.NewDescribeCertificatesRequest()
|
||||
}
|
||||
|
||||
+4
@@ -8,6 +8,10 @@ type CreateCertificateRequest = gaap.CreateCertificateRequest
|
||||
|
||||
type CreateCertificateResponse = gaap.CreateCertificateResponse
|
||||
|
||||
type DeleteCertificateRequest = gaap.DeleteCertificateRequest
|
||||
|
||||
type DeleteCertificateResponse = gaap.DeleteCertificateResponse
|
||||
|
||||
type DescribeCertificatesRequest = gaap.DescribeCertificatesRequest
|
||||
|
||||
type DescribeCertificatesResponse = gaap.DescribeCertificatesResponse
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PURGE_PROVIDERS, type PurgeProviderType } from "@/domain/provider";
|
||||
import BizPurgeNodeConfigFieldsProviderAliyunCAS from "./BizPurgeNodeConfigFieldsProviderAliyunCAS";
|
||||
import BizPurgeNodeConfigFieldsProviderAliyunCLB from "./BizPurgeNodeConfigFieldsProviderAliyunCLB";
|
||||
import BizPurgeNodeConfigFieldsProviderAliyunESA from "./BizPurgeNodeConfigFieldsProviderAliyunESA";
|
||||
import BizPurgeNodeConfigFieldsProviderTencentCloudGAAP from "./BizPurgeNodeConfigFieldsProviderTencentCloudGAAP";
|
||||
import BizPurgeNodeConfigFieldsProviderTencentCloudSSL from "./BizPurgeNodeConfigFieldsProviderTencentCloudSSL";
|
||||
|
||||
const providerComponentMap: Partial<Record<PurgeProviderType, React.ComponentType<any>>> = {
|
||||
@@ -15,6 +16,7 @@ const providerComponentMap: Partial<Record<PurgeProviderType, React.ComponentTyp
|
||||
[PURGE_PROVIDERS.ALIYUN_CAS]: BizPurgeNodeConfigFieldsProviderAliyunCAS,
|
||||
[PURGE_PROVIDERS.ALIYUN_CLB]: BizPurgeNodeConfigFieldsProviderAliyunCLB,
|
||||
[PURGE_PROVIDERS.ALIYUN_ESA]: BizPurgeNodeConfigFieldsProviderAliyunESA,
|
||||
[PURGE_PROVIDERS.TENCENTCLOUD_GAAP]: BizPurgeNodeConfigFieldsProviderTencentCloudGAAP,
|
||||
[PURGE_PROVIDERS.TENCENTCLOUD_SSL]: BizPurgeNodeConfigFieldsProviderTencentCloudSSL,
|
||||
};
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const BizPurgeNodeConfigFieldsProviderTencentCloudGAAP = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "endpoint"]}
|
||||
initialValue={initialValues.endpoint}
|
||||
label={t("workflow_node.purge.form.tencentcloud_gaap_endpoint.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.purge.form.tencentcloud_gaap_endpoint.tooltip") }}></span>}
|
||||
>
|
||||
<Input allowClear placeholder={t("workflow_node.purge.form.tencentcloud_gaap_endpoint.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
|
||||
return z.object({
|
||||
endpoint: z.string().nullish(),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(BizPurgeNodeConfigFieldsProviderTencentCloudGAAP, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -989,6 +989,7 @@ export const PURGE_PROVIDERS = Object.freeze(
|
||||
ALIYUN_CAS: `${ACCESS_PROVIDERS.ALIYUN}-cas`,
|
||||
ALIYUN_CLB: `${ACCESS_PROVIDERS.ALIYUN}-clb`,
|
||||
ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`,
|
||||
TENCENTCLOUD_GAAP: `${ACCESS_PROVIDERS.TENCENTCLOUD}-gaap`,
|
||||
TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`,
|
||||
} as const
|
||||
);
|
||||
@@ -1007,6 +1008,7 @@ export const purgeProvidersMap: Map<PurgeProvider["type"] | string, PurgeProvide
|
||||
[PURGE_PROVIDERS.ALIYUN_CAS, "provider.aliyun_cas"],
|
||||
[PURGE_PROVIDERS.ALIYUN_CLB, "provider.aliyun_clb"],
|
||||
[PURGE_PROVIDERS.ALIYUN_ESA, "provider.aliyun_esa"],
|
||||
[PURGE_PROVIDERS.TENCENTCLOUD_GAAP, "provider.tencentcloud_gaap"],
|
||||
[PURGE_PROVIDERS.TENCENTCLOUD_SSL, "provider.tencentcloud_ssl"],
|
||||
] satisfies Array<[PurgeProviderType, string, "builtin"] | [PurgeProviderType, string]>
|
||||
).map(([type, name]) => [
|
||||
|
||||
@@ -3052,6 +3052,11 @@
|
||||
"placeholder": "Please enter Alibaba Cloud ESA site ID",
|
||||
"tooltip": "For more information, see <a href=\"https://esa.console.aliyun.com/siteManage/list\" target=\"_blank\">https://esa.console.aliyun.com/siteManage/list</a>"
|
||||
},
|
||||
"tencentcloud_gaap_endpoint": {
|
||||
"label": "Tencent Cloud API endpoint (Optional)",
|
||||
"placeholder": "Please enter Tencent Cloud GAAP API endpoint (e.g. gaap.intl.tencentcloudapi.com)",
|
||||
"tooltip": "<ul style=\"list-style: disc;\"><li><strong>gaap.intl.tencentcloudapi.com</strong> for Tencent Cloud International</li><li><strong>gaap.tencentcloudapi.com</strong> for Tencent Cloud in China (<em>Default</em>)</li></ul>"
|
||||
},
|
||||
"tencentcloud_ssl_endpoint": {
|
||||
"label": "Tencent Cloud API endpoint (Optional)",
|
||||
"placeholder": "Please enter Tencent Cloud SSL API endpoint (e.g. ssl.intl.tencentcloudapi.com)",
|
||||
|
||||
@@ -3051,6 +3051,11 @@
|
||||
"placeholder": "请输入阿里云 ESA 站点 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://esa.console.aliyun.com/siteManage/list\" target=\"_blank\">https://esa.console.aliyun.com/siteManage/list</a>"
|
||||
},
|
||||
"tencentcloud_gaap_endpoint": {
|
||||
"label": "腾讯云接口端点(可选)",
|
||||
"placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/608/36934\" target=\"_blank\">https://cloud.tencent.com/document/product/608/36934</a><br>国际站用户请填写 <em>gaap.intl.tencentcloudapi.com</em>。"
|
||||
},
|
||||
"tencentcloud_ssl_endpoint": {
|
||||
"label": "腾讯云接口端点(可选)",
|
||||
"placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)",
|
||||
|
||||
Reference in New Issue
Block a user