diff --git a/internal/certmgmt/deployers/sp_byteplus_apig.go b/internal/certmgmt/deployers/sp_byteplus_apig.go new file mode 100644 index 000000000..ad7624709 --- /dev/null +++ b/internal/certmgmt/deployers/sp_byteplus_apig.go @@ -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 + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 7853d9589..a215fa430 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -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") diff --git a/pkg/core/deployer/providers/byteplus-apig/byteplus_apig.go b/pkg/core/deployer/providers/byteplus-apig/byteplus_apig.go new file mode 100644 index 000000000..e47820da9 --- /dev/null +++ b/pkg/core/deployer/providers/byteplus-apig/byteplus_apig.go @@ -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 +} diff --git a/pkg/core/deployer/providers/byteplus-apig/byteplus_apig_test.go b/pkg/core/deployer/providers/byteplus-apig/byteplus_apig_test.go new file mode 100644 index 000000000..f4752bb2b --- /dev/null +++ b/pkg/core/deployer/providers/byteplus-apig/byteplus_apig_test.go @@ -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}) + }) +} diff --git a/pkg/core/deployer/providers/byteplus-apig/consts.go b/pkg/core/deployer/providers/byteplus-apig/consts.go new file mode 100644 index 000000000..5269b73c8 --- /dev/null +++ b/pkg/core/deployer/providers/byteplus-apig/consts.go @@ -0,0 +1,10 @@ +package byteplusapig + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_get_custom_domain.go b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_get_custom_domain.go new file mode 100644 index 000000000..12d8b6476 --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_get_custom_domain.go @@ -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 diff --git a/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_list_custom_domains.go b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_list_custom_domains.go new file mode 100644 index 000000000..424299ed3 --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_list_custom_domains.go @@ -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 diff --git a/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_update_custom_domain.go b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_update_custom_domain.go new file mode 100644 index 000000000..5b726b07c --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/api_update_custom_domain.go @@ -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 diff --git a/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/service_apig.go b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/service_apig.go new file mode 100644 index 000000000..3fff27caf --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/byteplus-sdk/byteplus-go-sdk-v2/service/apig/service_apig.go @@ -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 +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx index e28f32455..fa453aecf 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx @@ -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 { + 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 ( + <> + } + > + + + + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { + 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; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusTOS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusTOS.tsx new file mode 100644 index 000000000..82bbaf3d4 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusTOS.tsx @@ -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 ( + <> + } + > + + + + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + bucket: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { + 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; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 94b10d377..68020fe64 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -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: Maphttps://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones" + }, + "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" diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json index 7b4d8a3f2..3eabb6949 100644 --- a/ui/src/i18n/resources/zh/nls.provider.json +++ b/ui/src/i18n/resources/zh/nls.provider.json @@ -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", diff --git a/ui/src/i18n/resources/zh/nls.workflow.nodes.json b/ui/src/i18n/resources/zh/nls.workflow.nodes.json index 4429f1404..3f6c562fb 100644 --- a/ui/src/i18n/resources/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/resources/zh/nls.workflow.nodes.json @@ -1238,6 +1238,15 @@ "placeholder": "请输入 BytePlus ALB 扩展域名", "help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。" }, + "byteplus_apig_region": { + "label": "BytePlus 服务地域", + "placeholder": "请输入 BytePlus API 网关服务地域(例如:ap-southeast-1)", + "tooltip": "这是什么?请参阅 https://docs.byteplus.com/en/docs/apig/Regions_and_availability_zones" + }, + "byteplus_apig_domain": { + "label": "BytePlus API 网关自定义域名", + "placeholder": "请输入 BytePlus API 网关自定义域名" + }, "byteplus_cdn_domain": { "label": "BytePlus CDN 域名", "placeholder": "请输入 BytePlus CDN 域名"