feat(provider): new deployment provider: tencentcloud tse

This commit is contained in:
Fu Diwei
2026-06-11 20:13:46 +08:00
committed by RHQYZ
parent 4c475a87ed
commit fe05eefa45
19 changed files with 822 additions and 3 deletions
@@ -0,0 +1,6 @@
package tencentcloudtse
const (
// 服务类型:云原生网关。
SERVICE_TYPE_CLOUDNATIVE = "cloudnative"
)
@@ -0,0 +1,239 @@
package tencentcloudtse
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/samber/lo"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcssl "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
tctse "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207"
"github.com/certimate-go/certimate/pkg/core"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
type (
Provider = core.Certmgr
UploadResult = core.CertmgrUploadResult
ReplaceResult = core.CertmgrReplaceResult
)
type CertmgrConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
// 腾讯云项目 ID。
ProjectId int64 `json:"projectId,omitempty"`
// 腾讯云接口端点。
Endpoint string `json:"endpoint,omitempty"`
// 腾讯云地域。
Region string `json:"region"`
// 服务类型。
ServiceType string `json:"serviceType"`
// 云原生网关 ID。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。
GatewayId string `json:"gatewayId,omitempty"`
// 云原生网关绑定的域名。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。
// 零值时根据证书内容自动识别。
Domains []string `json:"domains,omitempty"`
}
type Certmgr struct {
config *CertmgrConfig
logger *slog.Logger
sdkClient *wSDKClients
}
var _ Provider = (*Certmgr)(nil)
type wSDKClients struct {
SSL *tcssl.Client
TSE *tctse.Client
}
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the certmgr provider is nil")
}
client, err := createSDKClients(config.SecretId, config.SecretKey, config.Endpoint, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Certmgr{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (c *Certmgr) SetLogger(logger *slog.Logger) {
if logger == nil {
c.logger = slog.New(slog.DiscardHandler)
} else {
c.logger = logger
}
}
func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
switch c.config.ServiceType {
case SERVICE_TYPE_CLOUDNATIVE:
return c.uploadToCloudNative(ctx, certPEM, privkeyPEM)
default:
return nil, fmt.Errorf("unsupported service type '%s'", c.config.ServiceType)
}
}
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
switch c.config.ServiceType {
case SERVICE_TYPE_CLOUDNATIVE:
return c.replaceToCloudNative(ctx, certIdOrName, certPEM, privkeyPEM)
default:
return nil, fmt.Errorf("unsupported service type '%s'", c.config.ServiceType)
}
}
func (c *Certmgr) uploadToCloudNative(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 先上传证书到 SSL
// REF: https://cloud.tencent.com/document/api/400/41665
uploadCertificateReq := tcssl.NewUploadCertificateRequest()
uploadCertificateReq.ProjectId = lo.EmptyableToPtr(uint64(c.config.ProjectId))
uploadCertificateReq.CertificatePublicKey = common.StringPtr(certPEM)
uploadCertificateReq.CertificatePrivateKey = common.StringPtr(privkeyPEM)
uploadCertificateReq.Repeatable = common.BoolPtr(false)
uploadCertificateResp, err := c.sdkClient.SSL.UploadCertificateWithContext(ctx, uploadCertificateReq)
c.logger.Debug("sdk request 'ssl.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err)
}
// 查询云原生网关证书列表,避免重复上传
// REF: https://cloud.tencent.com/document/api/1364/98588
describeCloudNativeAPIGatewayCertificatesOffset := 0
describeCloudNativeAPIGatewayCertificatesLimit := 100
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeCloudNativeAPIGatewayCertificatesReq := tctse.NewDescribeCloudNativeAPIGatewayCertificatesRequest()
describeCloudNativeAPIGatewayCertificatesReq.GatewayId = common.StringPtr(c.config.GatewayId)
describeCloudNativeAPIGatewayCertificatesReq.Offset = common.Int64Ptr(int64(describeCloudNativeAPIGatewayCertificatesOffset))
describeCloudNativeAPIGatewayCertificatesReq.Limit = common.Int64Ptr(int64(describeCloudNativeAPIGatewayCertificatesLimit))
describeCloudNativeAPIGatewayCertificatesResp, err := c.sdkClient.TSE.DescribeCloudNativeAPIGatewayCertificatesWithContext(ctx, describeCloudNativeAPIGatewayCertificatesReq)
c.logger.Debug("sdk request 'tse.DescribeCloudNativeAPIGatewayCertificates'", slog.Any("request", describeCloudNativeAPIGatewayCertificatesReq), slog.Any("response", describeCloudNativeAPIGatewayCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'tse.DescribeCloudNativeAPIGatewayCertificates': %w", err)
}
for _, certItem := range describeCloudNativeAPIGatewayCertificatesResp.Response.Result.CertificatesList {
if lo.FromPtr(uploadCertificateResp.Response.CertificateId) == lo.FromPtr(certItem.CertId) ||
xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(certItem.Crt)) {
// 如果已存在相同证书,直接返回
c.logger.Info("ssl certificate already exists")
return &UploadResult{
CertId: lo.FromPtr(certItem.CertId),
CertName: lo.FromPtr(certItem.Name),
}, nil
}
}
if len(describeCloudNativeAPIGatewayCertificatesResp.Response.Result.CertificatesList) < describeCloudNativeAPIGatewayCertificatesLimit {
break
}
describeCloudNativeAPIGatewayCertificatesOffset += describeCloudNativeAPIGatewayCertificatesLimit
}
// 生成新证书名(需符合腾讯云命名规则)
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 创建云原生网关证书
// REF: https://cloud.tencent.com/document/api/1364/98591
createCloudNativeAPIGatewayCertificateReq := tctse.NewCreateCloudNativeAPIGatewayCertificateRequest()
createCloudNativeAPIGatewayCertificateReq.GatewayId = common.StringPtr(c.config.GatewayId)
createCloudNativeAPIGatewayCertificateReq.Name = common.StringPtr(certName)
createCloudNativeAPIGatewayCertificateReq.CertId = uploadCertificateResp.Response.CertificateId
createCloudNativeAPIGatewayCertificateReq.BindDomains = common.StringPtrs(lo.Ternary(len(c.config.Domains) != 0, c.config.Domains, certX509.DNSNames))
createCloudNativeAPIGatewayCertificateResp, err := c.sdkClient.TSE.CreateCloudNativeAPIGatewayCertificateWithContext(ctx, createCloudNativeAPIGatewayCertificateReq)
c.logger.Debug("sdk request 'tse.CreateCloudNativeAPIGatewayCertificate'", slog.Any("request", createCloudNativeAPIGatewayCertificateReq), slog.Any("response", createCloudNativeAPIGatewayCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'tse.CreateCloudNativeAPIGatewayCertificate': %w", err)
}
return &UploadResult{
CertId: lo.FromPtr(createCloudNativeAPIGatewayCertificateResp.Response.Result.Id),
CertName: certName,
}, nil
}
func (c *Certmgr) replaceToCloudNative(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
// 更新云原生网关证书
// REF: https://cloud.tencent.com/document/api/1364/100199
modifyCloudNativeAPIGatewayCertificateReq := tctse.NewModifyCloudNativeAPIGatewayCertificateRequest()
modifyCloudNativeAPIGatewayCertificateReq.GatewayId = common.StringPtr(c.config.GatewayId)
modifyCloudNativeAPIGatewayCertificateReq.Id = common.StringPtr(certIdOrName)
modifyCloudNativeAPIGatewayCertificateReq.Crt = common.StringPtr(certPEM)
modifyCloudNativeAPIGatewayCertificateReq.Key = common.StringPtr(privkeyPEM)
modifyCloudNativeAPIGatewayCertificateReq.CertSource = common.StringPtr("native")
modifyCloudNativeAPIGatewayCertificateResp, err := c.sdkClient.TSE.ModifyCloudNativeAPIGatewayCertificateWithContext(ctx, modifyCloudNativeAPIGatewayCertificateReq)
c.logger.Debug("sdk request 'tse.ModifyCloudNativeAPIGatewayCertificate'", slog.Any("request", modifyCloudNativeAPIGatewayCertificateReq), slog.Any("response", modifyCloudNativeAPIGatewayCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'tse.ModifyCloudNativeAPIGatewayCertificate': %w", err)
}
return &ReplaceResult{}, nil
}
func createSDKClients(secretId, secretKey, endpoint, region string) (*wSDKClients, error) {
wsdk := &wSDKClients{}
{
credential := common.NewCredential(secretId, secretKey)
cpf := profile.NewClientProfile()
client, err := tcssl.NewClient(credential, "", cpf)
if err != nil {
return nil, err
}
wsdk.SSL = client
}
{
credential := common.NewCredential(secretId, secretKey)
cpf := profile.NewClientProfile()
if endpoint != "" {
cpf.HttpProfile.Endpoint = endpoint
}
client, err := tctse.NewClient(credential, region, cpf)
if err != nil {
return nil, err
}
wsdk.TSE = client
}
return wsdk, nil
}
@@ -0,0 +1,61 @@
package tencentcloudtse_test
import (
"testing"
"github.com/certimate-go/certimate/pkg/core/certmgr/internal/tester"
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse"
)
var (
fp = tester.Args("TENCENTCLOUDTSE_")
fTestCertPath string
fTestKeyPath string
fSecretId string
fSecretKey string
fRegion string
fServiceType string
fGatewayId string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fSecretId, "SECRETID")
fp.DefineString(&fSecretKey, "SECRETKEY")
fp.DefineString(&fRegion, "REGION")
fp.DefineString(&fServiceType, "SERVICETYPE")
fp.DefineString(&fGatewayId, "GATEWAYID")
}
/*
Shell command to run this test:
go test -v ./tencentcloud_tse_test.go -args \
--TENCENTCLOUDTSE_TESTCERTPATH="/path/to/your-test-cert.pem" \
--TENCENTCLOUDTSE_TESTKEYPATH="/path/to/your-test-key.pem" \
--TENCENTCLOUDTSE_SECRETID="your-secret-id" \
--TENCENTCLOUDTSE_SECRETKEY="your-secret-key" \
--TENCENTCLOUDTSE_REGION="ap-guangzhou" \
--TENCENTCLOUDTSE_SERVICETYPE="cloudnative" \
--TENCENTCLOUDTSE_GATEWAYID="your-gateway-id"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Upload", func(t *testing.T) {
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
SecretId: fSecretId,
SecretKey: fSecretKey,
Region: fRegion,
ServiceType: fServiceType,
GatewayId: fGatewayId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
tester.TestUpload(t, provider, tester.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -0,0 +1,10 @@
package tencentcloudtse
import (
tse "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse"
)
const (
// 服务类型:云原生网关。
SERVICE_TYPE_CLOUDNATIVE = tse.SERVICE_TYPE_CLOUDNATIVE
)
@@ -0,0 +1,107 @@
package tencentcloudtse
import (
"context"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/pkg/core"
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
// 腾讯云项目 ID。
ProjectId int64 `json:"projectId,omitempty"`
// 腾讯云接口端点。
Endpoint string `json:"endpoint,omitempty"`
// 腾讯云地域。
Region string `json:"region"`
// 服务类型。
ServiceType string `json:"serviceType"`
// 云原生网关 ID。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。
GatewayId string `json:"gatewayId,omitempty"`
// 云原生网关绑定的域名。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。
// 零值时根据证书内容自动识别。
Domains []string `json:"domains,omitempty"`
// 云原生网关证书 ID。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。
// 零值时表示新建证书;否则表示更新证书。
CertificateId string `json:"certificateId,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkCertmgr core.Certmgr
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
ProjectId: config.ProjectId,
Endpoint: config.Endpoint,
Region: config.Region,
ServiceType: config.ServiceType,
GatewayId: config.GatewayId,
Domains: config.Domains,
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkCertmgr: pcertmgr,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
d.sdkCertmgr.SetLogger(logger)
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
if d.config.CertificateId == "" {
// 上传证书
upres, err := d.sdkCertmgr.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))
}
} else {
// 替换证书
rplres, err := d.sdkCertmgr.Replace(ctx, d.config.CertificateId, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to replace certificate file: %w", err)
} else {
d.logger.Info("ssl certificate replaced", slog.Any("result", rplres))
}
}
return &DeployResult{}, nil
}
@@ -0,0 +1,99 @@
package v20201207
import (
"context"
"errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207"
)
const APIVersion = tse.APIVersion
type Client struct {
common.Client
}
func NewClient(credential common.CredentialIface, region string, clientProfile *profile.ClientProfile) (client *Client, err error) {
client = &Client{}
client.Init(region).
WithCredential(credential).
WithProfile(clientProfile)
return
}
func NewCreateCloudNativeAPIGatewayCertificateRequest() (request *CreateCloudNativeAPIGatewayCertificateRequest) {
return tse.NewCreateCloudNativeAPIGatewayCertificateRequest()
}
func NewCreateCloudNativeAPIGatewayCertificateResponse() (response *CreateCloudNativeAPIGatewayCertificateResponse) {
return tse.NewCreateCloudNativeAPIGatewayCertificateResponse()
}
func (c *Client) CreateCloudNativeAPIGatewayCertificateWithContext(ctx context.Context, request *CreateCloudNativeAPIGatewayCertificateRequest) (response *CreateCloudNativeAPIGatewayCertificateResponse, err error) {
if request == nil {
request = NewCreateCloudNativeAPIGatewayCertificateRequest()
}
c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "CreateCloudNativeAPIGatewayCertificate")
if c.GetCredential() == nil {
return nil, errors.New("CreateCloudNativeAPIGatewayCertificate require credential")
}
request.SetContext(ctx)
response = NewCreateCloudNativeAPIGatewayCertificateResponse()
err = c.Send(request, response)
return
}
func NewDescribeCloudNativeAPIGatewayCertificatesRequest() (request *DescribeCloudNativeAPIGatewayCertificatesRequest) {
return tse.NewDescribeCloudNativeAPIGatewayCertificatesRequest()
}
func NewDescribeCloudNativeAPIGatewayCertificatesResponse() (response *DescribeCloudNativeAPIGatewayCertificatesResponse) {
return tse.NewDescribeCloudNativeAPIGatewayCertificatesResponse()
}
func (c *Client) DescribeCloudNativeAPIGatewayCertificatesWithContext(ctx context.Context, request *DescribeCloudNativeAPIGatewayCertificatesRequest) (response *DescribeCloudNativeAPIGatewayCertificatesResponse, err error) {
if request == nil {
request = NewDescribeCloudNativeAPIGatewayCertificatesRequest()
}
c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "DescribeCloudNativeAPIGatewayCertificates")
if c.GetCredential() == nil {
return nil, errors.New("DescribeCloudNativeAPIGatewayCertificates require credential")
}
request.SetContext(ctx)
response = NewDescribeCloudNativeAPIGatewayCertificatesResponse()
err = c.Send(request, response)
return
}
func NewModifyCloudNativeAPIGatewayCertificateRequest() (request *ModifyCloudNativeAPIGatewayCertificateRequest) {
return tse.NewModifyCloudNativeAPIGatewayCertificateRequest()
}
func NewModifyCloudNativeAPIGatewayCertificateResponse() (response *ModifyCloudNativeAPIGatewayCertificateResponse) {
return tse.NewModifyCloudNativeAPIGatewayCertificateResponse()
}
func (c *Client) ModifyCloudNativeAPIGatewayCertificateWithContext(ctx context.Context, request *ModifyCloudNativeAPIGatewayCertificateRequest) (response *ModifyCloudNativeAPIGatewayCertificateResponse, err error) {
if request == nil {
request = NewModifyCloudNativeAPIGatewayCertificateRequest()
}
c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "ModifyCloudNativeAPIGatewayCertificate")
if c.GetCredential() == nil {
return nil, errors.New("ModifyCloudNativeAPIGatewayCertificate require credential")
}
request.SetContext(ctx)
response = NewModifyCloudNativeAPIGatewayCertificateResponse()
err = c.Send(request, response)
return
}
@@ -0,0 +1,17 @@
package v20201207
import (
tse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207"
)
type CreateCloudNativeAPIGatewayCertificateRequest = tse.CreateCloudNativeAPIGatewayCertificateRequest
type CreateCloudNativeAPIGatewayCertificateResponse = tse.CreateCloudNativeAPIGatewayCertificateResponse
type DescribeCloudNativeAPIGatewayCertificatesRequest = tse.DescribeCloudNativeAPIGatewayCertificatesRequest
type DescribeCloudNativeAPIGatewayCertificatesResponse = tse.DescribeCloudNativeAPIGatewayCertificatesResponse
type ModifyCloudNativeAPIGatewayCertificateRequest = tse.ModifyCloudNativeAPIGatewayCertificateRequest
type ModifyCloudNativeAPIGatewayCertificateResponse = tse.ModifyCloudNativeAPIGatewayCertificateResponse