feat(provider): new deployment provider: byteplus apig

This commit is contained in:
Fu Diwei
2026-06-04 01:14:21 +08:00
committed by RHQYZ
parent a4a0ed57e4
commit 24e9d62471
17 changed files with 776 additions and 0 deletions
@@ -0,0 +1,29 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/volcengine-apig"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeBytePlusAPIG, func(options *ProviderFactoryOptions) (core.Deployer, error) {
credentials := domain.AccessConfigForBytePlus{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
AccessKeyId: credentials.AccessKeyId,
SecretAccessKey: credentials.SecretAccessKey,
ProjectName: credentials.ProjectName,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err
})
}
+1
View File
@@ -339,6 +339,7 @@ const (
DeploymentProviderTypeBaotaWAFConsole = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-console")
DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn")
DeploymentProviderTypeBytePlusALB = DeploymentProviderType(AccessProviderTypeBytePlus + "-alb")
DeploymentProviderTypeBytePlusAPIG = DeploymentProviderType(AccessProviderTypeBytePlus + "-apig")
DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn")
DeploymentProviderTypeBytePlusCertCenter = DeploymentProviderType(AccessProviderTypeBytePlus + "-certcenter")
DeploymentProviderTypeBytePlusCLB = DeploymentProviderType(AccessProviderTypeBytePlus + "-clb")
@@ -0,0 +1,277 @@
package byteplusapig
import (
"context"
"errors"
"fmt"
"log/slog"
bp "github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus"
bpsession "github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/session"
"github.com/samber/lo"
bpapig "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig"
"github.com/certimate-go/certimate/pkg/core"
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/byteplus-certcenter"
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// BytePlus AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// BytePlus SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// BytePlus 项目名称。
ProjectName string `json:"projectName,omitempty"`
// BytePlus 地域。
Region string `json:"region"`
// 域名匹配模式。
// 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *bpapig.APIG
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")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
ProjectName: config.ProjectName,
Region: "ap-singapore-1",
})
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) (*DeployResult, error) {
// 上传证书
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))
}
// 获取待部署的域名列表
domainIds := make([]string, 0)
switch d.config.DomainMatchPattern {
case "", DOMAIN_MATCH_PATTERN_EXACT:
{
if d.config.Domain == "" {
return nil, fmt.Errorf("config `domain` is required")
}
domainCandidates, err := d.getAllDomains(ctx)
if err != nil {
return nil, err
}
domains := lo.Filter(domainCandidates, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) bool {
return lo.FromPtr(domainItem.Domain) == d.config.Domain
})
if len(domains) == 0 {
return nil, fmt.Errorf("could not find domain")
}
domainIds = lo.Map(domains, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) string {
return lo.FromPtr(domainItem.Id)
})
}
case DOMAIN_MATCH_PATTERN_WILDCARD:
{
if d.config.Domain == "" {
return nil, fmt.Errorf("config `domain` is required")
}
domainCandidates, err := d.getAllDomains(ctx)
if err != nil {
return nil, err
}
domains := lo.Filter(domainCandidates, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) bool {
return xcerthostname.IsMatch(d.config.Domain, lo.FromPtr(domainItem.Domain))
})
if len(domains) == 0 {
return nil, fmt.Errorf("could not find any domains matched by wildcard")
}
domainIds = lo.Map(domains, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) string {
return lo.FromPtr(domainItem.Id)
})
}
case DOMAIN_MATCH_PATTERN_CERTSAN:
{
domainCandidates, err := d.getAllDomains(ctx)
if err != nil {
return nil, err
}
domains := lo.Filter(domainCandidates, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) bool {
return xcerthostname.IsMatchByCertificatePEM(certPEM, lo.FromPtr(domainItem.Domain))
})
if len(domains) == 0 {
return nil, fmt.Errorf("could not find any domains matched by certificate")
}
domainIds = lo.Map(domains, func(domainItem *bpapig.ItemForListCustomDomainsOutput, _ int) string {
return lo.FromPtr(domainItem.Id)
})
}
default:
return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern)
}
// 遍历更新域名证书
if len(domainIds) == 0 {
d.logger.Info("no apig domains to deploy")
} else {
d.logger.Info("found apig domains to deploy", slog.Any("domainIds", domainIds))
var errs []error
for _, domainId := range domainIds {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
if err := d.updateDomainCertificate(ctx, domainId, upres.CertId); err != nil {
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
}
return &DeployResult{}, nil
}
func (d *Deployer) getAllDomains(ctx context.Context) ([]*bpapig.ItemForListCustomDomainsOutput, error) {
domains := make([]*bpapig.ItemForListCustomDomainsOutput, 0)
// 查询自定义域名列表
listCustomDomainsPageNumber := 1
listCustomDomainsPageSize := 100
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCustomDomainsReq := &bpapig.ListCustomDomainsInput{
PageNumber: bp.Int64(int64(listCustomDomainsPageNumber)),
PageSize: bp.Int64(int64(listCustomDomainsPageSize)),
}
listCustomDomainsResp, err := d.sdkClient.ListCustomDomainsWithContext(ctx, listCustomDomainsReq)
d.logger.Debug("sdk request 'apig.ListCustomDomains'", slog.Any("request", listCustomDomainsReq), slog.Any("response", listCustomDomainsResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'apig.ListCustomDomains': %w", err)
}
ignoredStatuses := []string{"Creating", "CreationFailed", "Deleting", "DeletionFailed"}
for _, domainItem := range listCustomDomainsResp.Items {
if lo.Contains(ignoredStatuses, *domainItem.Status) {
continue
}
domains = append(domains, domainItem)
}
if len(listCustomDomainsResp.Items) < listCustomDomainsPageSize {
break
}
listCustomDomainsPageNumber++
}
return domains, nil
}
func (d *Deployer) updateDomainCertificate(ctx context.Context, cloudDomainId string, cloudCertId string) error {
// 查询自定义域名
getCustomDomainReq := &bpapig.GetCustomDomainInput{
Id: bp.String(cloudDomainId),
}
getCustomDomainResp, err := d.sdkClient.GetCustomDomainWithContext(ctx, getCustomDomainReq)
d.logger.Debug("sdk request 'apig.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'apig.GetCustomDomain': %w", err)
}
// 更新自定义域名
updateCustomDomainReq := &bpapig.UpdateCustomDomainInput{
Id: bp.String(cloudDomainId),
Protocol: getCustomDomainResp.CustomDomain.Protocol,
CertificateId: bp.String(cloudCertId),
}
if !lo.Contains(bp.StringValueSlice(updateCustomDomainReq.Protocol), "HTTPS") {
updateCustomDomainReq.Protocol = append(updateCustomDomainReq.Protocol, bp.String("HTTPS"))
}
updateCustomDomainResp, err := d.sdkClient.UpdateCustomDomainWithContext(ctx, updateCustomDomainReq)
d.logger.Debug("sdk request 'apig.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'apig.UpdateCustomDomain': %w", err)
}
return nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*bpapig.APIG, error) {
config := bp.NewConfig().
WithAkSk(accessKeyId, secretAccessKey).
WithRegion(region)
session, err := bpsession.NewSession(config)
if err != nil {
return nil, err
}
client := bpapig.New(session)
return client, nil
}
@@ -0,0 +1,58 @@
package byteplusapig_test
import (
"testing"
"github.com/certimate-go/certimate/pkg/core/deployer/internal/tester"
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/byteplus-apig"
)
var (
fp = tester.Args("BYTEPLUSAPIG_")
fTestCertPath string
fTestKeyPath string
fAccessKeyId string
fSecretAccessKey string
fRegion string
fDomain string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
fp.DefineString(&fRegion, "REGION")
fp.DefineString(&fDomain, "DOMAIN")
}
/*
Shell command to run this test:
go test -v ./byteplus_apig_test.go -args \
--BYTEPLUSAPIG_TESTCERTPATH="/path/to/your-test-cert.pem" \
--BYTEPLUSAPIG_TESTKEYPATH="/path/to/your-test-key.pem" \
--BYTEPLUSAPIG_ACCESSKEYID="your-access-key-id" \
--BYTEPLUSAPIG_SECRETACCESSKEY="your-secret-access-key" \
--BYTEPLUSAPIG_REGION="ap-southeast-1" \
--BYTEPLUSAPIG_DOMAIN="example.com"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Region: fRegion,
DomainMatchPattern: impl.DOMAIN_MATCH_PATTERN_EXACT,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -0,0 +1,10 @@
package byteplusapig
const (
// 匹配模式:精确匹配。
DOMAIN_MATCH_PATTERN_EXACT = "exact"
// 匹配模式:通配符匹配。
DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard"
// 匹配模式:证书 SAN 匹配。
DOMAIN_MATCH_PATTERN_CERTSAN = "certsan"
)
@@ -0,0 +1,39 @@
package apig
import (
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/request"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig"
)
const opGetCustomDomain = "GetCustomDomain"
func (c *APIG) GetCustomDomainRequest(input *GetCustomDomainInput) (req *request.Request, output *GetCustomDomainOutput) {
op := &request.Operation{
Name: opGetCustomDomain,
HTTPMethod: "POST",
HTTPPath: "/",
}
if input == nil {
input = &GetCustomDomainInput{}
}
output = &GetCustomDomainOutput{}
req = c.newRequest(op, input, output)
req.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8")
return
}
func (c *APIG) GetCustomDomainWithContext(ctx byteplus.Context, input *GetCustomDomainInput, opts ...request.Option) (*GetCustomDomainOutput, error) {
req, out := c.GetCustomDomainRequest(input)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return out, req.Send()
}
type GetCustomDomainInput = apig.GetCustomDomainInput
type GetCustomDomainOutput = apig.GetCustomDomainOutput
@@ -0,0 +1,41 @@
package apig
import (
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/request"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig"
)
const opListCustomDomains = "ListCustomDomains"
func (c *APIG) ListCustomDomainsRequest(input *ListCustomDomainsInput) (req *request.Request, output *ListCustomDomainsOutput) {
op := &request.Operation{
Name: opListCustomDomains,
HTTPMethod: "POST",
HTTPPath: "/",
}
if input == nil {
input = &ListCustomDomainsInput{}
}
output = &ListCustomDomainsOutput{}
req = c.newRequest(op, input, output)
req.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8")
return
}
func (c *APIG) ListCustomDomainsWithContext(ctx byteplus.Context, input *ListCustomDomainsInput, opts ...request.Option) (*ListCustomDomainsOutput, error) {
req, out := c.ListCustomDomainsRequest(input)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return out, req.Send()
}
type ListCustomDomainsInput = apig.ListCustomDomainsInput
type ListCustomDomainsOutput = apig.ListCustomDomainsOutput
type ItemForListCustomDomainsOutput = apig.ItemForListCustomDomainsOutput
@@ -0,0 +1,39 @@
package apig
import (
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/request"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig"
)
const opUpdateCustomDomain = "UpdateCustomDomain"
func (c *APIG) UpdateCustomDomainRequest(input *UpdateCustomDomainInput) (req *request.Request, output *UpdateCustomDomainOutput) {
op := &request.Operation{
Name: opUpdateCustomDomain,
HTTPMethod: "POST",
HTTPPath: "/",
}
if input == nil {
input = &UpdateCustomDomainInput{}
}
output = &UpdateCustomDomainOutput{}
req = c.newRequest(op, input, output)
req.HTTPRequest.Header.Set("Content-Type", "application/json; charset=utf-8")
return
}
func (c *APIG) UpdateCustomDomainWithContext(ctx byteplus.Context, input *UpdateCustomDomainInput, opts ...request.Option) (*UpdateCustomDomainOutput, error) {
req, out := c.UpdateCustomDomainRequest(input)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return out, req.Send()
}
type UpdateCustomDomainInput = apig.UpdateCustomDomainInput
type UpdateCustomDomainOutput = apig.UpdateCustomDomainOutput
@@ -0,0 +1,60 @@
package apig
import (
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/byteplusquery"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/client"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/client/metadata"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/corehandlers"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/request"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/byteplus/signer/byteplussign"
"github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig"
)
type APIG struct {
*client.Client
}
const (
ServiceName = apig.ServiceName
EndpointsID = apig.EndpointsID
ServiceID = apig.ServiceID
)
func New(p client.ConfigProvider, cfgs ...*byteplus.Config) *APIG {
c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName)
}
func newClient(cfg byteplus.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *APIG {
svc := &APIG{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
ServiceID: ServiceID,
SigningName: signingName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2021-03-03",
},
handlers,
),
}
svc.Handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler)
svc.Handlers.Build.PushBackNamed(corehandlers.AddHostExecEnvUserAgentHandler)
svc.Handlers.Sign.PushBackNamed(byteplussign.SignRequestHandler)
svc.Handlers.Build.PushBackNamed(byteplusquery.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed(byteplusquery.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed(byteplusquery.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed(byteplusquery.UnmarshalErrorHandler)
return svc
}
func (c *APIG) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
return req
}
@@ -38,10 +38,12 @@ import BizDeployNodeConfigFieldsProviderBaotaWAF from "./BizDeployNodeConfigFiel
import BizDeployNodeConfigFieldsProviderBaotaWAFConsole from "./BizDeployNodeConfigFieldsProviderBaotaWAFConsole";
import BizDeployNodeConfigFieldsProviderBunnyCDN from "./BizDeployNodeConfigFieldsProviderBunnyCDN";
import BizDeployNodeConfigFieldsProviderBytePlusALB from "./BizDeployNodeConfigFieldsProviderBytePlusALB";
import BizDeployNodeConfigFieldsProviderBytePlusAPIG from "./BizDeployNodeConfigFieldsProviderBytePlusAPIG";
import BizDeployNodeConfigFieldsProviderBytePlusCDN from "./BizDeployNodeConfigFieldsProviderBytePlusCDN";
import BizDeployNodeConfigFieldsProviderBytePlusCertCenter from "./BizDeployNodeConfigFieldsProviderBytePlusCertCenter";
import BizDeployNodeConfigFieldsProviderBytePlusCLB from "./BizDeployNodeConfigFieldsProviderBytePlusCLB";
import BizDeployNodeConfigFieldsProviderBytePlusMediaLive from "./BizDeployNodeConfigFieldsProviderBytePlusMediaLive";
import BizDeployNodeConfigFieldsProviderBytePlusTOS from "./BizDeployNodeConfigFieldsProviderBytePlusTOS";
import BizDeployNodeConfigFieldsProviderCdnfly from "./BizDeployNodeConfigFieldsProviderCdnfly";
import BizDeployNodeConfigFieldsProviderCPanel from "./BizDeployNodeConfigFieldsProviderCPanel";
import BizDeployNodeConfigFieldsProviderCTCCCloudAO from "./BizDeployNodeConfigFieldsProviderCTCCCloudAO";
@@ -170,10 +172,12 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.BAOTAWAF_CONSOLE]: BizDeployNodeConfigFieldsProviderBaotaWAFConsole,
[DEPLOYMENT_PROVIDERS.BUNNY_CDN]: BizDeployNodeConfigFieldsProviderBunnyCDN,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_ALB]: BizDeployNodeConfigFieldsProviderBytePlusALB,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_APIG]: BizDeployNodeConfigFieldsProviderBytePlusAPIG,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN]: BizDeployNodeConfigFieldsProviderBytePlusCDN,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CERTCENTER]: BizDeployNodeConfigFieldsProviderBytePlusCertCenter,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CLB]: BizDeployNodeConfigFieldsProviderBytePlusCLB,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_MEDIALIVE]: BizDeployNodeConfigFieldsProviderBytePlusMediaLive,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_TOS]: BizDeployNodeConfigFieldsProviderBytePlusTOS,
[DEPLOYMENT_PROVIDERS.CDNFLY]: BizDeployNodeConfigFieldsProviderCdnfly,
[DEPLOYMENT_PROVIDERS.CPANEL]: BizDeployNodeConfigFieldsProviderCPanel,
[DEPLOYMENT_PROVIDERS.CTCCCLOUD_AO]: BizDeployNodeConfigFieldsProviderCTCCCloudAO,
@@ -0,0 +1,120 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Radio } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import Show from "@/components/Show";
import { isDomain } from "@/utils/validator";
import { useFormNestedFieldsContext } from "./_context";
const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const;
const DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" as const;
const DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" as const;
const BizDeployNodeConfigFieldsProviderBytePlusAPIG = () => {
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 fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true });
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.byteplus_apig_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.byteplus_apig_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.byteplus_apig_region.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "domainMatchPattern"]}
initialValue={initialValues.domainMatchPattern}
label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")}
extra={
fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? (
<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_domain_match_pattern.option.exact.help.wildcard") }}></span>
) : (
void 0
)
}
rules={[formRule]}
>
<Radio.Group
options={[DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN].map((s) => ({
key: s,
label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`),
value: s,
}))}
/>
</Form.Item>
<Show when={fieldDomainMatchPattern !== DOMAIN_MATCH_PATTERN_CERTSAN}>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
label={t("workflow_node.deploy.form.byteplus_apig_domain.label")}
rules={[formRule]}
>
<Input placeholder={t("workflow_node.deploy.form.byteplus_apig_domain.placeholder")} />
</Form.Item>
</Show>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT,
domain: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z
.object({
region: z.string().nonempty(),
domainMatchPattern: z.string().nonempty().default(DOMAIN_MATCH_PATTERN_EXACT),
domain: z.string().nullish(),
})
.superRefine((values, ctx) => {
if (values.domainMatchPattern) {
switch (values.domainMatchPattern) {
case DOMAIN_MATCH_PATTERN_EXACT:
case DOMAIN_MATCH_PATTERN_WILDCARD:
{
const scDomain = z.string().refine((v) => isDomain(v, { allowWildcard: true }), t("common.errmsg.domain_invalid"));
const spDomain = scDomain.safeParse(values.domain);
if (!spDomain.success) {
ctx.addIssue({
code: "custom",
message: z.treeifyError(spDomain.error).errors.join(),
path: ["domain"],
});
}
}
break;
}
}
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderBytePlusAPIG, {
getInitialValues,
getSchema,
});
export default _default;
@@ -0,0 +1,76 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { isDomain } from "@/utils/validator";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderBytePlusTOS = () => {
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, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.byteplus_tos_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.byteplus_tos_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.byteplus_tos_region.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "bucket"]}
initialValue={initialValues.bucket}
label={t("workflow_node.deploy.form.byteplus_tos_bucket.label")}
rules={[formRule]}
>
<Input placeholder={t("workflow_node.deploy.form.byteplus_tos_bucket.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
label={t("workflow_node.deploy.form.byteplus_tos_domain.label")}
rules={[formRule]}
>
<Input placeholder={t("workflow_node.deploy.form.byteplus_tos_domain.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
bucket: "",
domain: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
region: z.string().nonempty(),
bucket: z.string().nonempty(),
domain: z.string().refine((v) => isDomain(v), t("common.errmsg.domain_invalid")),
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderBytePlusTOS, {
getInitialValues,
getSchema,
});
export default _default;
+2
View File
@@ -622,6 +622,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
BAOTAWAF_CONSOLE: `${ACCESS_PROVIDERS.BAOTAWAF}-console`,
BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`,
BYTEPLUS_ALB: `${ACCESS_PROVIDERS.BYTEPLUS}-alb`,
BYTEPLUS_APIG: `${ACCESS_PROVIDERS.BYTEPLUS}-apig`,
BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`,
BYTEPLUS_CERTCENTER: `${ACCESS_PROVIDERS.BYTEPLUS}-certcenter`,
BYTEPLUS_CLB: `${ACCESS_PROVIDERS.BYTEPLUS}-clb`,
@@ -834,6 +835,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CLB, "provider.byteplus_clb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
[DEPLOYMENT_PROVIDERS.BYTEPLUS_ALB, "provider.byteplus_alb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
[DEPLOYMENT_PROVIDERS.BYTEPLUS_MEDIALIVE, "provider.byteplus_medialive", DEPLOYMENT_CATEGORIES.AV],
[DEPLOYMENT_PROVIDERS.BYTEPLUS_APIG, "provider.byteplus_apig", DEPLOYMENT_CATEGORIES.APIGATEWAY],
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CERTCENTER, "provider.byteplus_certcenter_upload", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.UCLOUD_US3, "provider.ucloud_us3", DEPLOYMENT_CATEGORIES.STORAGE],
[DEPLOYMENT_PROVIDERS.UCLOUD_UCDN, "provider.ucloud_ucdn", DEPLOYMENT_CATEGORIES.CDN],
@@ -90,6 +90,7 @@
"bunny_cdn": "Bunny - CDN (Content Delivery Network)",
"byteplus": "BytePlus",
"byteplus_alb": "BytePlus - ALB (Application Load Balancer)",
"byteplus_apig": "BytePlus - API Gateway",
"byteplus_cdn": "BytePlus - CDN (Content Delivery Network)",
"byteplus_certcenter_upload": "BytePlus - Upload to Certificate Center",
"byteplus_clb": "BytePlus - CLB (Classic Load Balancer)",
@@ -1239,6 +1239,15 @@
"placeholder": "Please enter BytePlus ALB SNI domain name",
"help": "Notes: Leave it blank to set the default certificate; otherwise, to set the extension one for SNI."
},
"byteplus_apig_region": {
"label": "BytePlus region",
"placeholder": "Please enter BytePlus API gateway region (e.g. ap-southeast-1)",
"tooltip": "For more information, see <a href=\"https://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones\" target=\"_blank\">https://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones</a>"
},
"byteplus_apig_domain": {
"label": "BytePlus API gateway custom domain",
"placeholder": "Please enter BytePlus API gateway custom domain name"
},
"byteplus_cdn_domain": {
"label": "BytePlus CDN domain",
"placeholder": "Please enter BytePlus CDN domain name"
@@ -90,6 +90,7 @@
"bunny_cdn": "Bunny - 内容分发网络 CDN",
"byteplus": "BytePlus",
"byteplus_alb": "BytePlus - 应用型负载均衡 ALB",
"byteplus_apig": "BytePlus - API 网关",
"byteplus_cdn": "BytePlus - 内容分发网络 CDN",
"byteplus_certcenter_upload": "BytePlus - 上传到证书中心",
"byteplus_clb": "BytePlus - 传统型负载均衡 CLB",
@@ -1238,6 +1238,15 @@
"placeholder": "请输入 BytePlus ALB 扩展域名",
"help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。"
},
"byteplus_apig_region": {
"label": "BytePlus 服务地域",
"placeholder": "请输入 BytePlus API 网关服务地域(例如:ap-southeast-1)",
"tooltip": "这是什么?请参阅 <a href=\"https://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones\" target=\"_blank\">https://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones</a>"
},
"byteplus_apig_domain": {
"label": "BytePlus API 网关自定义域名",
"placeholder": "请输入 BytePlus API 网关自定义域名"
},
"byteplus_cdn_domain": {
"label": "BytePlus CDN 域名",
"placeholder": "请输入 BytePlus CDN 域名"