feat(provider): new deployment provider: zenlayer zga

This commit is contained in:
Fu Diwei
2026-05-15 13:15:07 +08:00
committed by RHQYZ
parent 568bf28f27
commit 66710f6ca2
23 changed files with 889 additions and 0 deletions
+1
View File
@@ -256,6 +256,7 @@ require (
github.com/tinylib/msgp v1.6.4 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
github.com/zenlayer/zenlayercloud-sdk-go v0.2.33 // indirect
golang.org/x/image v0.40.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.54.0 // indirect
+2
View File
@@ -982,6 +982,8 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
github.com/zenlayer/zenlayercloud-sdk-go v0.2.33 h1:lfzoPBtKyCFktOk69IfvpMI3n3o6YHSV/NVp3qQQ1mU=
github.com/zenlayer/zenlayercloud-sdk-go v0.2.33/go.mod h1:iOR3Pzb2t3Z0+UvXhdUqi7nhwZM5Ux2oH4ixKH+qe88=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -0,0 +1,29 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core/deployer"
zenlayerga "github.com/certimate-go/certimate/pkg/core/deployer/providers/zenlayer-ga"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeZenlayerGA, func(options *ProviderFactoryOptions) (deployer.Provider, error) {
credentials := domain.AccessConfigForZenlayer{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := zenlayerga.NewDeployer(&zenlayerga.DeployerConfig{
AccessKeyId: credentials.AccessKeyId,
AccessKeyPassword: credentials.AccessKeyPassword,
ResourceGroupId: credentials.ResourceGroupId,
ResourceType: xmaps.GetString(options.ProviderExtendedConfig, "resourceType"),
AcceleratorId: xmaps.GetString(options.ProviderExtendedConfig, "acceleratorId"),
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
})
return provider, err
})
}
+6
View File
@@ -621,6 +621,12 @@ type AccessConfigForXinnet struct {
ApiPassword string `json:"apiPassword"`
}
type AccessConfigForZenlayer struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeyPassword string `json:"accessKeyPassword"`
ResourceGroupId string `json:"resourceGroupId,omitempty"`
}
type AccessConfigForZeroSSL struct {
AccessConfigForACMEExternalAccountBinding
}
+2
View File
@@ -123,6 +123,7 @@ const (
AccessProviderTypeWeComBot = AccessProviderType("wecombot")
AccessProviderTypeWestcn = AccessProviderType("westcn")
AccessProviderTypeXinnet = AccessProviderType("xinnet")
AccessProviderTypeZenlayer = AccessProviderType("zenlayer")
AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
)
@@ -391,6 +392,7 @@ const (
DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro")
DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate")
DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook)
DeploymentProviderTypeZenlayerGA = DeploymentProviderType(AccessProviderTypeZenlayer + "-ga")
)
type NotificationProviderType string
@@ -0,0 +1,46 @@
package internal
import (
"github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/common"
zga "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/zga20230706"
)
// This is a partial copy of https://github.com/zenlayer/zenlayercloud-sdk-go/blob/main/zenlayercloud/zga20230706/client.go
// to lightweight the vendor packages in the built binary.
type ZgaClient struct {
*common.Client
}
func NewZgaClient(config *common.Config, secretKeyId, secretKeyPassword string) (client *ZgaClient, err error) {
client = &ZgaClient{}
err = client.InitWithCredential(common.NewCredential(secretKeyId, secretKeyPassword))
if err != nil {
return nil, err
}
err = client.WithConfig(config)
if err != nil {
return nil, err
}
return client, nil
}
func (c *ZgaClient) CreateCertificate(request *zga.CreateCertificateRequest) (response *zga.CreateCertificateResponse, err error) {
response = zga.NewCreateCertificateResponse()
err = c.ApiCall(request, response)
return
}
func (c *ZgaClient) DescribeCertificates(request *zga.DescribeCertificatesRequest) (response *zga.DescribeCertificatesResponse, err error) {
response = zga.NewDescribeCertificatesResponse()
err = c.ApiCall(request, response)
return
}
func (c *ZgaClient) ModifyCertificate(request *zga.ModifyCertificateRequest) (response *zga.ModifyCertificateResponse, err error) {
response = zga.NewModifyCertificateResponse()
err = c.ApiCall(request, response)
return
}
@@ -0,0 +1,202 @@
package zenlayerga
import (
"context"
"crypto/md5"
"crypto/sha1"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"time"
zcommon "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/common"
zga "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/zga20230706"
"github.com/certimate-go/certimate/pkg/core/certmgr"
"github.com/certimate-go/certimate/pkg/core/certmgr/providers/zenlayer-ga/internal"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
type CertmgrConfig struct {
// Zenlayer AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// Zenlayer AccessKeyPassword。
AccessKeyPassword string `json:"accessKeyPassword"`
// Zenlayer 资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
}
type Certmgr struct {
config *CertmgrConfig
logger *slog.Logger
sdkClient *internal.ZgaClient
}
var _ certmgr.Provider = (*Certmgr)(nil)
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
if config == nil {
return nil, errors.New("the configuration of the certmgr provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeyPassword)
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) (*certmgr.UploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/certificate/describecertificates
describeCertificatesPageNum := 1
describeCertificatesPageSize := 100
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeCertificatesReq := zga.NewDescribeCertificatesRequest()
describeCertificatesReq.ResourceGroupId = c.config.ResourceGroupId
describeCertificatesReq.PageNum = describeCertificatesPageNum
describeCertificatesReq.PageSize = describeCertificatesPageSize
describeCertificatesResp, err := c.sdkClient.DescribeCertificates(describeCertificatesReq)
c.logger.Debug("sdk request 'zga.DescribeCertificates'", slog.Any("request", describeCertificatesReq), slog.Any("response", describeCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'zga.DescribeCertificates': %w", err)
}
for _, certItem := range describeCertificatesResp.Response.DataSet {
// 对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certItem.Common) {
continue
}
// 对比证书扩展名称
if !slices.Equal(certX509.DNSNames, certItem.Sans) {
continue
}
// 对比证书有效期
oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certItem.StartTime)
oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certItem.EndTime)
if !certX509.NotBefore.Equal(oldCertNotBefore) {
continue
} else if !certX509.NotAfter.Equal(oldCertNotAfter) {
continue
}
// 对比证书指纹
//
// 注意,虽然文档中描述为 MD5 摘要,但示例给出的是 SHA-1 摘要,因此这里都尝试对比一下
fingerprintMd5 := md5.Sum(certX509.Raw)
fingerprintMd5Hex := hex.EncodeToString(fingerprintMd5[:])
fingerprintSha1 := sha1.Sum(certX509.Raw)
fingerprintSha1Hex := hex.EncodeToString(fingerprintSha1[:])
if !strings.EqualFold(fingerprintMd5Hex, certItem.Fingerprint) && !strings.EqualFold(fingerprintSha1Hex, certItem.Fingerprint) {
continue
}
// 对比证书公钥算法
switch certX509.PublicKeyAlgorithm {
case x509.RSA:
if !strings.EqualFold(certItem.Algorithm, "RSA") {
continue
}
case x509.ECDSA:
if !strings.EqualFold(certItem.Algorithm, "EC") && !strings.EqualFold(certItem.Algorithm, "ECDSA") {
continue
}
default:
// 未知算法,跳过
continue
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
c.logger.Info("ssl certificate already exists")
return &certmgr.UploadResult{
CertId: certItem.CertificateId,
CertName: certItem.CertificateLabel,
}, nil
}
if len(describeCertificatesResp.Response.DataSet) < describeCertificatesPageSize {
break
}
describeCertificatesPageNum++
}
// 生成新证书名(需符合 Zenlayer 命名规则)
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 创建证书
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/certificate/createcertificate
createCertificateReq := zga.NewCreateCertificateRequest()
createCertificateReq.ResourceGroupId = c.config.ResourceGroupId
createCertificateReq.CertificateLabel = certName
createCertificateReq.CertificateContent = certPEM
createCertificateReq.CertificateKey = privkeyPEM
createCertificateResp, err := c.sdkClient.CreateCertificate(createCertificateReq)
c.logger.Debug("sdk request 'zga.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'zga.CreateCertificate': %w", err)
}
return &certmgr.UploadResult{
CertId: createCertificateResp.Response.CertificateId,
CertName: certName,
}, nil
}
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*certmgr.OperateResult, error) {
// 更新证书
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/certificate/modifycertificate
modifyCertificateReq := zga.NewModifyCertificateRequest()
modifyCertificateReq.CertificateId = certIdOrName
modifyCertificateReq.CertificateContent = certPEM
modifyCertificateReq.CertificateKey = privkeyPEM
modifyCertificateResp, err := c.sdkClient.ModifyCertificate(modifyCertificateReq)
c.logger.Debug("sdk request 'zga.ModifyCertificate'", slog.Any("request", modifyCertificateReq), slog.Any("response", modifyCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'zga.ModifyCertificate': %w", err)
}
return &certmgr.OperateResult{}, nil
}
func createSDKClient(accessKeyId, accessKeyPassword string) (*internal.ZgaClient, error) {
config := zcommon.NewConfig()
client, err := internal.NewZgaClient(config, accessKeyId, accessKeyPassword)
if err != nil {
return nil, err
}
return client, nil
}
@@ -0,0 +1,8 @@
package zenlayerga
const (
// 资源类型:部署到指定全球网络加速器。
RESOURCE_TYPE_ACCELERATOR = "accelerator"
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = "certificate"
)
@@ -0,0 +1,40 @@
package internal
import (
"github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/common"
zga "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/zga20230706"
)
// This is a partial copy of https://github.com/zenlayer/zenlayercloud-sdk-go/blob/main/zenlayercloud/zga20230706/client.go
// to lightweight the vendor packages in the built binary.
type ZgaClient struct {
*common.Client
}
func NewZgaClient(config *common.Config, secretKeyId, secretKeyPassword string) (client *ZgaClient, err error) {
client = &ZgaClient{}
err = client.InitWithCredential(common.NewCredential(secretKeyId, secretKeyPassword))
if err != nil {
return nil, err
}
err = client.WithConfig(config)
if err != nil {
return nil, err
}
return client, nil
}
func (c *ZgaClient) DescribeAccelerators(request *zga.DescribeAcceleratorsRequest) (response *zga.DescribeAcceleratorsResponse, err error) {
response = zga.NewDescribeAcceleratorsResponse()
err = c.ApiCall(request, response)
return
}
func (c *ZgaClient) ModifyAcceleratorCertificate(request *zga.ModifyAcceleratorCertificateRequest) (response *zga.ModifyAcceleratorCertificateResponse, err error) {
response = zga.NewModifyAcceleratorCertificateResponse()
err = c.ApiCall(request, response)
return
}
@@ -0,0 +1,200 @@
package zenlayerga
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
zcommon "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/common"
zga "github.com/zenlayer/zenlayercloud-sdk-go/zenlayercloud/zga20230706"
"github.com/certimate-go/certimate/pkg/core/certmgr"
mcertmgr "github.com/certimate-go/certimate/pkg/core/certmgr/providers/zenlayer-ga"
"github.com/certimate-go/certimate/pkg/core/deployer"
"github.com/certimate-go/certimate/pkg/core/deployer/providers/zenlayer-ga/internal"
xwait "github.com/certimate-go/certimate/pkg/utils/wait"
)
type DeployerConfig struct {
// Zenlayer AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// Zenlayer AccessKeyPassword。
AccessKeyPassword string `json:"accessKeyPassword"`
// Zenlayer 资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 部署资源类型。
ResourceType string `json:"resourceType"`
// 加速器 ID。
// 部署资源类型为 [RESOURCE_TYPE_ACCELERATOR] 时必填。
AcceleratorId string `json:"acceleratorId,omitempty"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId string `json:"certificateId,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *internal.ZgaClient
sdkCertmgr certmgr.Provider
}
var _ deployer.Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, errors.New("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeyPassword)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
pcertmgr, err := mcertmgr.NewCertmgr(&mcertmgr.CertmgrConfig{
AccessKeyId: config.AccessKeyId,
AccessKeyPassword: config.AccessKeyPassword,
ResourceGroupId: config.ResourceGroupId,
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
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) (*deployer.DeployResult, error) {
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_ACCELERATOR:
if err := d.deployToAccelerator(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
}
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 &deployer.DeployResult{}, nil
}
func (d *Deployer) deployToAccelerator(ctx context.Context, certPEM, privkeyPEM string) error {
if d.config.AcceleratorId == "" {
return errors.New("config `acceleratorId` is required")
}
// 上传证书
upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 查询加速器信息
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/accelerator/describeaccelerators
describeAcceleratorsReq := zga.NewDescribeAcceleratorsRequest()
describeAcceleratorsReq.AcceleratorIds = []string{d.config.AcceleratorId}
describeAcceleratorsReq.PageNum = 1
describeAcceleratorsReq.PageSize = 1
describeAcceleratorsResp, err := d.sdkClient.DescribeAccelerators(describeAcceleratorsReq)
d.logger.Debug("sdk request 'zga.DescribeAccelerators'", slog.Any("request", describeAcceleratorsReq), slog.Any("response", describeAcceleratorsResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'zga.DescribeAccelerators': %w", err)
} else if len(describeAcceleratorsResp.Response.DataSet) == 0 {
return fmt.Errorf("could not found accelerator '%s'", d.config.AcceleratorId)
}
// 修改加速器证书
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/accelerator/modifyacceleratorcertificate
acceleratorInfo := describeAcceleratorsResp.Response.DataSet[0]
if acceleratorInfo.Certificate == nil || acceleratorInfo.Certificate.CertificateId != upres.CertId {
modifyAcceleratorCertificateReq := zga.NewModifyAcceleratorCertificateRequest()
modifyAcceleratorCertificateReq.AcceleratorId = acceleratorInfo.AcceleratorId
modifyAcceleratorCertificateReq.CertificateId = upres.CertId
modifyAcceleratorCertificateResp, err := d.sdkClient.ModifyAcceleratorCertificate(modifyAcceleratorCertificateReq)
d.logger.Debug("sdk request 'zga.ModifyAcceleratorCertificate'", slog.Any("request", modifyAcceleratorCertificateReq), slog.Any("response", modifyAcceleratorCertificateResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'zga.ModifyAcceleratorCertificate': %w", err)
}
}
// 查询加速器状态,等待部署状态变更
// REF: https://docs.console.zenlayer.com/api-reference/cn/networking/zga/accelerator/describeaccelerators
if _, err := xwait.UntilWithContext(ctx, func(_ context.Context, _ int) (bool, error) {
describeAcceleratorsReq := zga.NewDescribeAcceleratorsRequest()
describeAcceleratorsReq.AcceleratorIds = []string{acceleratorInfo.AcceleratorId}
describeAcceleratorsReq.PageNum = 1
describeAcceleratorsReq.PageSize = 1
describeAcceleratorsResp, err := d.sdkClient.DescribeAccelerators(describeAcceleratorsReq)
d.logger.Debug("sdk request 'zga.DescribeAccelerators'", slog.Any("request", describeAcceleratorsReq), slog.Any("response", describeAcceleratorsResp))
if err != nil {
return false, fmt.Errorf("failed to execute sdk request 'zga.DescribeAccelerators': %w", err)
} else if len(describeAcceleratorsResp.Response.DataSet) == 0 {
return false, fmt.Errorf("could not found accelerator '%s'", d.config.AcceleratorId)
}
switch describeAcceleratorsResp.Response.DataSet[0].AcceleratorStatus {
case "Accelerating":
return true, nil
case "NotAccelerate", "StopAccelerate", "AccelerateFailure":
return false, fmt.Errorf("unexpected zenlayer accelerator status")
}
d.logger.Info("waiting for zenlayer accelerator deploying completion ...")
return false, nil
}, time.Second*5); err != nil {
return err
}
return nil
}
func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM string) error {
if d.config.CertificateId == "" {
return errors.New("config `certificateId` is required")
}
// 替换证书
upres, err := d.sdkCertmgr.Replace(ctx, d.config.CertificateId, certPEM, privkeyPEM)
if err != nil {
return fmt.Errorf("failed to replace certificate file: %w", err)
} else {
d.logger.Info("ssl certificate replaced", slog.Any("result", upres))
}
return nil
}
func createSDKClient(accessKeyId, accessKeyPassword string) (*internal.ZgaClient, error) {
config := zcommon.NewConfig()
client, err := internal.NewZgaClient(config, accessKeyId, accessKeyPassword)
if err != nil {
return nil, err
}
return client, nil
}
@@ -0,0 +1,111 @@
package zenlayerga_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/certimate-go/certimate/pkg/core/deployer/providers/zenlayer-ga"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeyPassword string
fAcceleratorId string
fCertificateId string
)
func init() {
argsPrefix := "ZENLAYERGA_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeyPassword, argsPrefix+"ACCESSKEYPASSWORD", "", "")
flag.StringVar(&fAcceleratorId, argsPrefix+"ACCELERATORID", "", "")
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
}
/*
Shell command to run this test:
go test -v ./zenlayer_ga_test.go -args \
--ZENLAYERGA_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--ZENLAYERGA_INPUTKEYPATH="/path/to/your-input-key.pem" \
--ZENLAYERGA_ACCESSKEYID="your-access-key-id" \
--ZENLAYERGA_ACCESSKEYPASSWORD="your-access-key-password" \
--ZENLAYERGA_ACCELERATORID="your-ga-accelerator-id" \
--ZENLAYERGA_CERTIFICATEID="your-ga-certificate-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy_ToAccelerator", 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("ACCESSKEYPASSWORD: %v", fAccessKeyPassword),
fmt.Sprintf("ACCELERATORID: %v", fAcceleratorId),
}, "\n"))
provider, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeyPassword: fAccessKeyPassword,
ResourceType: provider.RESOURCE_TYPE_ACCELERATOR,
AcceleratorId: fAcceleratorId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := provider.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
t.Run("Deploy_ToCertificate", 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("ACCESSKEYPASSWORD: %v", fAccessKeyPassword),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
provider, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeyPassword: fAccessKeyPassword,
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 := provider.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 33.9 31.9" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 32)" gradientUnits="userSpaceOnUse" x1="1.67" x2="28.86" y1="19.912" y2="8.982"><stop offset="0" stop-color="#1dbfff"/><stop offset="1" stop-color="#1ea0f3"/></linearGradient><linearGradient id="b" gradientTransform="matrix(1 0 0 -1 0 32)" gradientUnits="userSpaceOnUse" x1="1.025" x2="23.988" y1="21.133" y2="12.316"><stop offset="0" stop-color="#2bcdfd"/><stop offset="1" stop-color="#87e6ff"/></linearGradient><linearGradient id="c" gradientTransform="matrix(1 0 0 -1 0 32)" gradientUnits="userSpaceOnUse" x1="5.873" x2="32.043" y1="27.229" y2="9.249"><stop offset="0" stop-color="#32b4f8"/><stop offset="1" stop-color="#1073d8"/></linearGradient><path d="M19.3 3.9c-1-.4-2.1-.7-3.3-.7H15.4C9.8 3.2 5.2 7.8 5.2 13.4c0 1.2.2 2.5.7 3.6v-.5c0-5.2 4.2-9.4 9.4-9.4s9.4 4.2 9.4 9.4c0 .5 0 1.1-.1 1.6 0 .3-.1.6-.2.9-.3 1-.7 2-1.3 2.9-3.6 5.8-11.3 7.6-17.1 4-3.7-2.2-5.9-6.1-6-10.4v.4c0 8.8 7.2 16 16 16 2 0 3.9-.4 5.8-1.1 7.1-4 9.6-13 5.6-20.1-1.8-3.2-4.7-5.6-8.1-6.8z" fill="url(#a)"/><path d="M15.3 25.9c-5 0-9.1-3.9-9.4-8.9-.4-1.1-.7-2.4-.7-3.6 0-5.6 4.6-10.2 10.2-10.2H16h-1.7C7.9 3.2 2.7 6.8.4 12.4c0 0-.1.2-.1.4-.2.8-.3 1.6-.3 2.4v.3c.1 6.9 5.8 12.4 12.7 12.3 4.3-.1 8.2-2.3 10.4-5.9-1.7 2.5-4.8 4-7.8 4z" fill="url(#b)"/><path d="M16 0C9.3 0 3.3 4.1 1 10.4c-.3.8-.6 1.6-.7 2.4 0-.1.1-.4.1-.4 2.3-5.6 7.8-9.3 13.9-9.2H16c1.1.1 2.2.3 3.2.7 7.7 2.6 11.9 11 9.3 18.7-1.2 3.4-3.6 6.3-6.7 8.1C30 27.5 33.9 18.2 30.6 10 28.2 4 22.4.1 16 0z" fill="url(#c)"/></svg>
@@ -111,6 +111,7 @@ import AccessConfigFieldsProviderWebhook from "./AccessConfigFieldsProviderWebho
import AccessConfigFieldsProviderWeComBot from "./AccessConfigFieldsProviderWeComBot";
import AccessConfigFieldsProviderWestcn from "./AccessConfigFieldsProviderWestcn";
import AccessConfigFieldsProviderXinnet from "./AccessConfigFieldsProviderXinnet";
import AccessConfigFieldsProviderZenlayer from "./AccessConfigFieldsProviderZenlayer";
import AccessConfigFieldsProviderZeroSSL from "./AccessConfigFieldsProviderZeroSSL";
const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentType<any>>> = {
@@ -227,6 +228,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.WECOMBOT]: AccessConfigFieldsProviderWeComBot,
[ACCESS_PROVIDERS.WESTCN]: AccessConfigFieldsProviderWestcn,
[ACCESS_PROVIDERS.XINNET]: AccessConfigFieldsProviderXinnet,
[ACCESS_PROVIDERS.ZENLAYER]: AccessConfigFieldsProviderZenlayer,
[ACCESS_PROVIDERS.ZEROSSL]: AccessConfigFieldsProviderZeroSSL,
};
@@ -0,0 +1,75 @@
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 AccessConfigFormFieldsProviderZenlayer = () => {
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, "accessKeyId"]}
initialValue={initialValues.accessKeyId}
label={t("access.form.zenlayer_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zenlayer_access_key_id.tooltip") }}></span>}
>
<Input autoComplete="new-password" placeholder={t("access.form.zenlayer_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "accessKeyPassword"]}
initialValue={initialValues.accessKeyPassword}
label={t("access.form.zenlayer_access_key_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zenlayer_access_key_password.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.zenlayer_access_key_password.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "resourceGroupId"]}
initialValue={initialValues.resourceGroupId}
label={t("access.form.zenlayer_resource_group_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zenlayer_resource_group_id.tooltip") }}></span>}
>
<Input allowClear autoComplete="new-password" placeholder={t("access.form.zenlayer_resource_group_id.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
accessKeyId: "",
accessKeyPassword: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
accessKeyId: z.string().nonempty(t("access.form.zenlayer_access_key_id.placeholder")),
accessKeyPassword: z.string().nonempty(t("access.form.zenlayer_access_key_password.placeholder")),
resourceGroupId: z.string().nullish(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderZenlayer, {
getInitialValues,
getSchema,
});
export default _default;
@@ -122,6 +122,7 @@ import BizDeployNodeConfigFieldsProviderWangsuCDN from "./BizDeployNodeConfigFie
import BizDeployNodeConfigFieldsProviderWangsuCDNPro from "./BizDeployNodeConfigFieldsProviderWangsuCDNPro";
import BizDeployNodeConfigFieldsProviderWangsuCertificate from "./BizDeployNodeConfigFieldsProviderWangsuCertificate";
import BizDeployNodeConfigFieldsProviderWebhook from "./BizDeployNodeConfigFieldsProviderWebhook";
import BizDeployNodeConfigFieldsProviderZenlayerGA from "./BizDeployNodeConfigFieldsProviderZenlayerGA";
const providerComponentMap: Partial<Record<DeploymentProviderType, React.ComponentType<any>>> = {
/*
@@ -248,6 +249,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO]: BizDeployNodeConfigFieldsProviderWangsuCDNPro,
[DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE]: BizDeployNodeConfigFieldsProviderWangsuCertificate,
[DEPLOYMENT_PROVIDERS.WEBHOOK]: BizDeployNodeConfigFieldsProviderWebhook,
[DEPLOYMENT_PROVIDERS.ZENLAYER_GA]: BizDeployNodeConfigFieldsProviderZenlayerGA,
};
const useComponent = (provider: string, { initProps, deps = [] }: { initProps?: (provider: string) => any; deps?: unknown[] }) => {
@@ -0,0 +1,120 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import Show from "@/components/Show";
import { useFormNestedFieldsContext } from "./_context";
const RESOURCE_TYPE_ACCELERATOR = "accelerator" as const;
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
const BizDeployNodeConfigFieldsProviderZenlayerGA = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const formInst = Form.useFormInstance();
const initialValues = getInitialValues();
const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst);
return (
<>
<Form.Item
name={[parentNamePath, "resourceType"]}
initialValue={initialValues.resourceType}
label={t("workflow_node.deploy.form.shared_resource_type.label")}
rules={[formRule]}
>
<Select
options={[RESOURCE_TYPE_ACCELERATOR, RESOURCE_TYPE_CERTIFICATE].map((s) => ({
value: s,
label: t(`workflow_node.deploy.form.zenlayer_ga_resource_type.option.${s}.label`),
}))}
placeholder={t("workflow_node.deploy.form.shared_resource_type.placeholder")}
/>
</Form.Item>
<Show when={fieldResourceType === RESOURCE_TYPE_ACCELERATOR}>
<Form.Item
name={[parentNamePath, "acceleratorId"]}
initialValue={initialValues.acceleratorId}
label={t("workflow_node.deploy.form.zenlayer_ga_accelerator_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.zenlayer_ga_accelerator_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.zenlayer_ga_accelerator_id.placeholder")} />
</Form.Item>
</Show>
<Show when={fieldResourceType === RESOURCE_TYPE_CERTIFICATE}>
<Form.Item
name={[parentNamePath, "certificateId"]}
initialValue={initialValues.certificateId}
label={t("workflow_node.deploy.form.zenlayer_ga_certificate_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.zenlayer_ga_certificate_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.zenlayer_ga_certificate_id.placeholder")} />
</Form.Item>
</Show>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
resourceType: RESOURCE_TYPE_ACCELERATOR,
acceleratorId: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z
.object({
resourceType: z.literal([RESOURCE_TYPE_ACCELERATOR, RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.shared_resource_type.placeholder")),
acceleratorId: z.string().nullish(),
certificateId: z.string().nullish(),
})
.superRefine((values, ctx) => {
switch (values.resourceType) {
case RESOURCE_TYPE_ACCELERATOR:
{
if (!values.acceleratorId?.trim()) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.zenlayer_ga_accelerator_id.placeholder"),
path: ["acceleratorId"],
});
}
}
break;
case RESOURCE_TYPE_CERTIFICATE:
{
if (!values.certificateId?.trim()) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.zenlayer_ga_certificate_id.placeholder"),
path: ["certificateId"],
});
}
}
break;
}
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderZenlayerGA, {
getInitialValues,
getSchema,
});
export default _default;
+4
View File
@@ -127,6 +127,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
WECOMBOT: "wecombot",
WESTCN: "westcn",
XINNET: "xinnet",
ZENLAYER: "zenlayer",
ZEROSSL: "zerossl",
} as const);
@@ -186,6 +187,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.CACHEFLY, "provider.cachefly", "/imgs/providers/cachefly.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.MOHUA, "provider.mohua", "/imgs/providers/mohua.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.UNICLOUD, "provider.unicloud", "/imgs/providers/unicloud.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.ZENLAYER, "provider.zenlayer", "/imgs/providers/zenlayer.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS["1PANEL"], "provider.1panel", "/imgs/providers/1panel.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.APISIX, "provider.apisix", "/imgs/providers/apisix.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.HOSTING]],
@@ -689,6 +691,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`,
WANGSU_CERTIFICATE: `${ACCESS_PROVIDERS.WANGSU}-certificate`,
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
ZENLAYER_GA: `${ACCESS_PROVIDERS.ZENLAYER}-ga`,
} as const);
export type DeploymentProviderType = (typeof DEPLOYMENT_PROVIDERS)[keyof typeof DEPLOYMENT_PROVIDERS];
@@ -826,6 +829,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.FLYIO, "provider.flyio", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.NETLIFY, "provider.netlify", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.VERCEL, "provider.vercel", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.ZENLAYER_GA, "provider.zenlayer_ga", DEPLOYMENT_CATEGORIES.ACCELERATOR],
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.FLEXCDN, "provider.flexcdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.GOEDGE, "provider.goedge", DEPLOYMENT_CATEGORIES.CDN],
+9
View File
@@ -748,5 +748,14 @@
"access.form.xinnet_api_password.label": "Xinnet.com agent API password",
"access.form.xinnet_api_password.placeholder": "Please enter Xinnet.com agent API password",
"access.form.xinnet_agent.guide": "Xinnet.com API only supports calls from agents. Learn more about this: <br><a href=\"https://apidoc.xin.cn/doc-7283837\" target=\"_blank\">https://apidoc.xin.cn/doc-7283837</a>",
"access.form.zenlayer_access_key_id.label": "Zenlayer AccessKeyID",
"access.form.zenlayer_access_key_id.placeholder": "Please enter Zenlayer AccessKeyID",
"access.form.zenlayer_access_key_id.tooltip": "For more information, see <a href=\"https://docs.console.zenlayer.com/welcome/platform/team-management/generate-an-api-access-key\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/platform/team-management/generate-an-api-access-key</a>",
"access.form.zenlayer_access_key_password.label": "Zenlayer AccessKeyPassword",
"access.form.zenlayer_access_key_password.placeholder": "Please enter Zenlayer AccessKeyPassword",
"access.form.zenlayer_access_key_password.tooltip": "For more information, see <a href=\"https://docs.console.zenlayer.com/welcome/platform/team-management/generate-an-api-access-key\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/platform/team-management/generate-an-api-access-key</a>",
"access.form.zenlayer_resource_group_id.label": "Zenlayer resource group ID (Optional)",
"access.form.zenlayer_resource_group_id.placeholder": "Please enter Zenlayer resource group ID",
"access.form.zenlayer_resource_group_id.tooltip": "For more information, see <a href=\"https://docs.console.zenlayer.com/welcome/platform/team-management/create-a-resource-group\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/platform/team-management/create-a-resource-group</a>",
"access.form.zerossl_eab.guide": "Learn more about using EAB key in ZeroSSL: <br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
}
+2
View File
@@ -235,6 +235,8 @@
"provider.westcn": "West.cn",
"provider.xinnet": "Xinnet.com",
"provider.zerossl": "ZeroSSL",
"provider.zenlayer": "Zenlayer",
"provider.zenlayer_ga": "Zenlayer - ZGA (Global Accelerator)",
"provider.category.all": "All",
"provider.category.cdn": "CDN",
@@ -1265,6 +1265,14 @@
"workflow_node.deploy.form.webhook_timeout.label": "Webhook timeout (Optional)",
"workflow_node.deploy.form.webhook_timeout.placeholder": "Please enter Webhook timeout",
"workflow_node.deploy.form.webhook_timeout.unit": "seconds",
"workflow_node.deploy.form.zenlayer_ga_resource_type.option.accelerator.label": "Accelerator",
"workflow_node.deploy.form.zenlayer_ga_resource_type.option.certificate.label": "Certificate",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.label": "Zenlayer ZGA accelerator ID",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.placeholder": "Please enter Zenlayer ZGA accelerator ID",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.tooltip": "For more information, <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.label": "Zenlayer ZGA certificate ID",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.placeholder": "Please enter Zenlayer ZGA certificate ID",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.tooltip": "For more information, <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>",
"workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment",
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ",
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": " re-deployment.",
+9
View File
@@ -747,5 +747,14 @@
"access.form.xinnet_api_password.label": "新网数码代理商 API 密码",
"access.form.xinnet_api_password.placeholder": "请输入新网数码代理商 API 密码",
"access.form.xinnet_agent.guide": "新网数码 API 仅支持代理商调用。点击下方链接了解更多:<br><a href=\"https://apidoc.xin.cn/doc-7283837\" target=\"_blank\">https://apidoc.xin.cn/doc-7283837</a>",
"access.form.zenlayer_access_key_id.label": "Zenlayer AccessKeyID",
"access.form.zenlayer_access_key_id.placeholder": "请输入 Zenlayer AccessKeyID",
"access.form.zenlayer_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://docs.console.zenlayer.com/welcome/cn/platform/team-management/generate-an-api-access-key\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/cn/platform/team-management/generate-an-api-access-key</a>",
"access.form.zenlayer_access_key_password.label": "Zenlayer AccessKeyPassword",
"access.form.zenlayer_access_key_password.placeholder": "请输入 Zenlayer AccessKeyPassword",
"access.form.zenlayer_access_key_password.tooltip": "这是什么?请参阅 <a href=\"https://docs.console.zenlayer.com/welcome/cn/platform/team-management/generate-an-api-access-key\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/cn/platform/team-management/generate-an-api-access-key</a>",
"access.form.zenlayer_resource_group_id.label": "Zenlayer 资源组 ID(可选)",
"access.form.zenlayer_resource_group_id.placeholder": "请输入 Zenlayer 资源组 ID",
"access.form.zenlayer_resource_group_id.tooltip": "这是什么?请参阅 <a href=\"https://docs.console.zenlayer.com/welcome/cn/platform/team-management/create-a-resource-group\" target=\"_blank\">https://docs.console.zenlayer.com/welcome/cn/platform/team-management/create-a-resource-group</a>",
"access.form.zerossl_eab.guide": "点击下方链接了解如何获取 ZeroSSL EAB:<br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
}
+2
View File
@@ -235,6 +235,8 @@
"provider.westcn": "西部数码",
"provider.xinnet": "新网数码",
"provider.zerossl": "ZeroSSL",
"provider.zenlayer": "Zenlayer",
"provider.zenlayer_ga": "Zenlayer - 全球网络加速器 ZGA",
"provider.category.all": "全部",
"provider.category.cdn": "CDN",
@@ -1263,6 +1263,14 @@
"workflow_node.deploy.form.webhook_timeout.label": "Webhook 超时时间(可选)",
"workflow_node.deploy.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间",
"workflow_node.deploy.form.webhook_timeout.unit": "秒",
"workflow_node.deploy.form.zenlayer_ga_resource_type.option.accelerator.label": "部署到指定全球网络加速器",
"workflow_node.deploy.form.zenlayer_ga_resource_type.option.certificate.label": "替换指定证书",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.label": "Zenlayer ZGA 加速器 ID",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.placeholder": "请输入 Zenlayer ZGA 加速器 ID",
"workflow_node.deploy.form.zenlayer_ga_accelerator_id.tooltip": "这是什么?请参阅 <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.label": "Zenlayer ZGA 证书 ID",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.placeholder": "请输入 Zenlayer ZGA 证书 ID",
"workflow_node.deploy.form.zenlayer_ga_certificate_id.tooltip": "这是什么?请参阅 <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>",
"workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署",
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功后,再次运行工作流时",
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": "此部署节点。",