feat(provider): supoprt cloud product access type in deployment to aliyun waf

This commit is contained in:
Fu Diwei 2025-11-14 18:54:40 +08:00
parent 664d8b9434
commit ec91575fef
17 changed files with 848 additions and 153 deletions

View File

@ -22,7 +22,11 @@ func init() {
ResourceGroupId: credentials.ResourceGroupId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"),
ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"),
InstanceId: xmaps.GetString(options.ProviderExtendedConfig, "instanceId"),
ResourceProduct: xmaps.GetString(options.ProviderExtendedConfig, "resourceProduct"),
ResourceId: xmaps.GetString(options.ProviderExtendedConfig, "resourceId"),
ResourcePort: xmaps.GetOrDefaultInt32(options.ProviderExtendedConfig, "resourcePort", 443),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err

View File

@ -20,12 +20,12 @@ func init() {
}
provider, err := tencentcloudssldeploy.NewSSLDeployerProvider(&tencentcloudssldeploy.SSLDeployerProviderConfig{
SecretId: credentials.SecretId,
SecretKey: credentials.SecretKey,
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
ResourceType: xmaps.GetString(options.ProviderExtendedConfig, "resourceType"),
ResourceIds: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string, _ int) bool { return s != "" }),
SecretId: credentials.SecretId,
SecretKey: credentials.SecretKey,
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
ResourceProduct: xmaps.GetString(options.ProviderExtendedConfig, "resourceProduct"),
ResourceIds: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string, _ int) bool { return s != "" }),
})
return provider, err
})

View File

@ -20,13 +20,13 @@ func init() {
}
provider, err := tencentcloudsslupdate.NewSSLDeployerProvider(&tencentcloudsslupdate.SSLDeployerProviderConfig{
SecretId: credentials.SecretId,
SecretKey: credentials.SecretKey,
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
IsReplaced: xmaps.GetBool(options.ProviderExtendedConfig, "isReplaced"),
ResourceTypes: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceTypes"), ";"), func(s string, _ int) bool { return s != "" }),
ResourceRegions: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceRegions"), ";"), func(s string, _ int) bool { return s != "" }),
SecretId: credentials.SecretId,
SecretKey: credentials.SecretKey,
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
IsReplaced: xmaps.GetBool(options.ProviderExtendedConfig, "isReplaced"),
ResourceProducts: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceProducts"), ";"), func(s string, _ int) bool { return s != "" }),
ResourceRegions: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceRegions"), ";"), func(s string, _ int) bool { return s != "" }),
})
return provider, err
})

View File

@ -1,37 +0,0 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
tracer := NewTracer("v0.4.5")
tracer.Printf("go ...")
// adapt to new workflow data structure
{
if _, err := app.DB().NewQuery("UPDATE workflow SET graphDraft = REPLACE(graphDraft, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow SET graphContent = REPLACE(graphContent, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_run SET graph = REPLACE(graph, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
}
tracer.Printf("done")
return nil
}, func(app core.App) error {
return nil
})
}

View File

@ -0,0 +1,218 @@
package migrations
import (
"github.com/go-viper/mapstructure/v2"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
tracer := NewTracer("v0.4.5")
tracer.Printf("go ...")
// adapt to new workflow data structure
{
type dWorkflowNode struct {
Id string `json:"id"`
Type string `json:"type"`
Data map[string]any `json:"data"`
Blocks []*dWorkflowNode `json:"blocks,omitempty,omitzero"`
}
var deepMigrateNode func(node *dWorkflowNode) (_node *dWorkflowNode, _migrated bool)
var deepMigrateNodes func(nodes []*dWorkflowNode) (_nodes []*dWorkflowNode, _migrated bool)
deepMigrateNode = func(node *dWorkflowNode) (*dWorkflowNode, bool) {
migrated := false
if node.Type == "bizDeploy" {
if node.Data != nil {
if _, ok := node.Data["config"]; ok {
nodeCfg := node.Data["config"].(map[string]any)
if nodeCfg["provider"] == "tencentcloud-ssldeploy" {
if nodeCfg["providerConfig"] != nil {
providerCfg := nodeCfg["providerConfig"].(map[string]any)
providerCfg["resourceProduct"] = providerCfg["resourceType"]
delete(providerCfg, "resourceType")
nodeCfg["providerConfig"] = providerCfg
node.Data["config"] = nodeCfg
migrated = true
}
} else if nodeCfg["provider"] == "tencentcloud-sslupdate" {
if nodeCfg["providerConfig"] != nil {
providerCfg := nodeCfg["providerConfig"].(map[string]any)
providerCfg["resourceProducts"] = providerCfg["resourceTypes"]
delete(providerCfg, "resourceTypes")
nodeCfg["providerConfig"] = providerCfg
node.Data["config"] = nodeCfg
migrated = true
}
} else if nodeCfg["provider"] == "aliyun-waf" {
if nodeCfg["providerConfig"] != nil {
providerCfg := nodeCfg["providerConfig"].(map[string]any)
providerCfg["serviceType"] = "cname"
nodeCfg["providerConfig"] = providerCfg
node.Data["config"] = nodeCfg
migrated = true
}
}
}
}
}
if len(node.Blocks) > 0 {
if newBlocks, changed := deepMigrateNodes(node.Blocks); changed {
node.Blocks = newBlocks
migrated = true
}
}
return node, migrated
}
deepMigrateNodes = func(nodes []*dWorkflowNode) ([]*dWorkflowNode, bool) {
migrated := false
for i, node := range nodes {
if newNode, changed := deepMigrateNode(node); changed {
nodes[i] = newNode
migrated = true
}
}
return nodes, migrated
}
// update collection `workflow`
// - migrate field `graphDraft` / `graphContent`
{
collection, err := app.FindCollectionByNameOrId("tovyif5ax6j62ur")
if err != nil {
return err
}
records, err := app.FindAllRecords(collection)
if err != nil {
return err
}
for _, record := range records {
changed := false
graphDraft := make(map[string]any)
if err := record.UnmarshalJSONField("graphDraft", &graphDraft); err == nil {
if _, ok := graphDraft["nodes"]; ok {
nodes := make([]*dWorkflowNode, 0)
if err := mapstructure.Decode(graphDraft["nodes"], &nodes); err != nil {
return err
}
if newNodes, migrated := deepMigrateNodes(nodes); migrated {
graphDraft["nodes"] = newNodes
record.Set("graphDraft", graphDraft)
changed = true
}
}
}
graphContent := make(map[string]any)
if err := record.UnmarshalJSONField("graphContent", &graphContent); err == nil {
if _, ok := graphContent["nodes"]; ok {
nodes := make([]*dWorkflowNode, 0)
if err := mapstructure.Decode(graphContent["nodes"], &nodes); err != nil {
return err
}
if newNodes, migrated := deepMigrateNodes(nodes); migrated {
graphContent["nodes"] = newNodes
record.Set("graphContent", graphContent)
changed = true
}
}
}
if changed {
if err := app.Save(record); err != nil {
return err
}
tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name)
}
}
if _, err := app.DB().NewQuery("UPDATE workflow SET graphDraft = REPLACE(graphDraft, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow SET graphContent = REPLACE(graphContent, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
}
// update collection `workflow_run`
// - migrate field `graph`
{
collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz")
if err != nil {
return err
}
records, err := app.FindAllRecords(collection)
if err != nil {
return err
}
for _, record := range records {
changed := false
graph := make(map[string]any)
if err := record.UnmarshalJSONField("graph", &graph); err == nil {
if _, ok := graph["nodes"]; ok {
nodes := make([]*dWorkflowNode, 0)
if err := mapstructure.Decode(graph["nodes"], &nodes); err != nil {
return err
}
if newNodes, migrated := deepMigrateNodes(nodes); migrated {
graph["nodes"] = newNodes
record.Set("graph", graph)
changed = true
}
}
}
if changed {
if err := app.Save(record); err != nil {
return err
}
tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name)
}
}
if _, err := app.DB().NewQuery("UPDATE workflow_run SET graph = REPLACE(graph, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
}
// update collection `workflow_output`
// - migrate field `nodeConfig`
{
if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"resourceType\"', '\"resourceProduct\"') WHERE nodeConfig LIKE '%\"provider\":\"tencentcloud-ssldeploy\"%' OR nodeConfig LIKE '%\"provider\":\"tencentcloud-sslupdate\"%'").Execute(); err != nil {
return err
}
}
}
tracer.Printf("done")
return nil
}, func(app core.App) error {
return nil
})
}

View File

@ -46,9 +46,9 @@ Shell command to run this test:
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_REGION="cn-hangzhou" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_GATEWAYID="your-api-gateway-id" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_GROUPID="your-api-group-id" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \
--CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
@ -62,9 +62,9 @@ func TestDeploy(t *testing.T) {
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("REGION: %v", fRegion),
fmt.Sprintf("SERVICETYPE: %v", fServiceType),
fmt.Sprintf("GATEWAYID: %v", fGatewayId),
fmt.Sprintf("GROUPID: %v", fGroupId),
fmt.Sprintf("SERVICETYPE: %v", fServiceType),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))

View File

@ -6,6 +6,7 @@ import (
"fmt"
"log/slog"
"strings"
"time"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/dara"
@ -28,10 +29,22 @@ type SSLDeployerProviderConfig struct {
// 阿里云地域。
Region string `json:"region"`
// 服务版本。
// 可取值 "3.0"。
ServiceVersion string `json:"serviceVersion"`
// 服务类型。
ServiceType string `json:"serviceType"`
// WAF 实例 ID。
InstanceId string `json:"instanceId"`
// 接入域名(支持泛域名)。
// 云产品类型。
// 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。
ResourceProduct string `json:"resourceProduct,omitempty"`
// 云产品资源 ID。
// 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。
ResourceId string `json:"resourceId,omitempty"`
// 云产品资源端口。
// 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。
ResourcePort int32 `json:"resourcePort,omitempty"`
// 扩展域名(支持泛域名)。
Domain string `json:"domain,omitempty"`
}
@ -85,10 +98,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
}
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
if d.config.InstanceId == "" {
return nil, errors.New("config `instanceId` is required")
}
switch d.config.ServiceVersion {
case "3", "3.0":
if err := d.deployToWAF3(ctx, certPEM, privkeyPEM); err != nil {
@ -103,6 +112,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
}
func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, privkeyPEM string) error {
if d.config.InstanceId == "" {
return errors.New("config `instanceId` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
@ -111,8 +124,180 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string,
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 根据接入方式决定部署方式
switch d.config.ServiceType {
case SERVICE_TYPE_CLOUDRESOURCE:
certId := upres.ExtendedData["CertIdentifier"].(string)
if err := d.deployToWAF3WithCloudResource(ctx, certId); err != nil {
return err
}
case SERVICE_TYPE_CNAME:
certId := upres.ExtendedData["CertIdentifier"].(string)
if err := d.deployToWAF3WithCNAME(ctx, certId); err != nil {
return err
}
default:
return fmt.Errorf("unsupported service version '%s'", d.config.ServiceVersion)
}
return nil
}
func (d *SSLDeployerProvider) deployToWAF3WithCloudResource(ctx context.Context, cloudCertId string) error {
if d.config.ResourceProduct == "" {
return errors.New("config `resourceProduct` is required")
}
if d.config.ResourceId == "" {
return errors.New("config `resourceId` is required")
}
if d.config.ResourcePort == 0 {
d.config.ResourcePort = 443
}
// 查询已同步的云产品资产
// REF: https://www.alibabacloud.com/help/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describeproductinstances
var resourceInstance *aliwaf.DescribeProductInstancesResponseBodyProductInstances
var resourceInstancePort *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePorts
describeProductInstancesReq := &aliwaf.DescribeProductInstancesRequest{
ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId),
RegionId: tea.String(d.config.Region),
InstanceId: tea.String(d.config.InstanceId),
ResourceProduct: tea.String(d.config.ResourceProduct),
ResourceInstanceId: tea.String(d.config.ResourceId),
}
describeProductInstancesResp, err := d.sdkClient.DescribeProductInstancesWithContext(ctx, describeProductInstancesReq, &dara.RuntimeOptions{})
d.logger.Debug("sdk request 'waf.DescribeProductInstances'", slog.Any("request", describeProductInstancesReq), slog.Any("response", describeProductInstancesResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'waf.DescribeProductInstances': %w", err)
} else if len(describeProductInstancesResp.Body.ProductInstances) == 0 {
return fmt.Errorf("cloud not find waf '%s' cloud resource '%s %s'", d.config.InstanceId, d.config.ResourceProduct, d.config.ResourceId)
} else {
resourceInstance = describeProductInstancesResp.Body.ProductInstances[0]
resourceInstancePort, _ = lo.Find(resourceInstance.ResourcePorts, func(p *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePorts) bool {
return tea.Int32Value(p.Port) == d.config.ResourcePort
})
if resourceInstancePort == nil {
return fmt.Errorf("could not find waf '%s' cloud resource '%s %s:%d'", d.config.InstanceId, d.config.ResourceProduct, d.config.ResourceId, d.config.ResourcePort)
}
}
// 查询云产品实例的证书列表
var resourceInstanceCertificates []*aliwaf.DescribeResourceInstanceCertsResponseBodyCerts = make([]*aliwaf.DescribeResourceInstanceCertsResponseBodyCerts, 0)
describeResourceInstanceCertsPageNumber := 1
describeResourceInstanceCertsPageSize := 10
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
describeResourceInstanceCertsReq := &aliwaf.DescribeResourceInstanceCertsRequest{
ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId),
InstanceId: tea.String(d.config.InstanceId),
ResourceInstanceId: tea.String(d.config.ResourceId),
PageNumber: tea.Int64(int64(describeResourceInstanceCertsPageNumber)),
PageSize: tea.Int64(int64(describeResourceInstanceCertsPageSize)),
}
describeResourceInstanceCertsResp, err := d.sdkClient.DescribeResourceInstanceCertsWithContext(ctx, describeResourceInstanceCertsReq, &dara.RuntimeOptions{})
d.logger.Debug("sdk request 'waf.DescribeResourceInstanceCerts'", slog.Any("request", describeResourceInstanceCertsReq), slog.Any("response", describeResourceInstanceCertsResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'wafw.DescribeResourceInstanceCerts': %w", err)
}
if describeResourceInstanceCertsResp.Body == nil {
break
}
resourceInstanceCertificates = append(resourceInstanceCertificates, describeResourceInstanceCertsResp.Body.Certs...)
if len(describeResourceInstanceCertsResp.Body.Certs) < describeResourceInstanceCertsPageSize {
break
}
describeResourceInstanceCertsPageNumber++
}
// 生成请求参数
modifyCloudResourceReq := &aliwaf.ModifyCloudResourceRequest{
ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId),
RegionId: tea.String(d.config.Region),
Listen: &aliwaf.ModifyCloudResourceRequestListen{
ResourceProduct: resourceInstance.ResourceProduct,
ResourceInstanceId: resourceInstance.ResourceInstanceId,
Protocol: tea.String("https"),
Port: resourceInstancePort.Port,
Certificates: lo.Map(resourceInstancePort.Certificates, func(c *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePortsCertificates, _ int) *aliwaf.ModifyCloudResourceRequestListenCertificates {
return &aliwaf.ModifyCloudResourceRequestListenCertificates{
CertificateId: c.CertificateId,
AppliedType: c.AppliedType,
}
}),
},
}
if d.config.Domain == "" {
// 未指定接入域名,只需替换默认证书即可
// 未指定扩展域名,只需替换默认证书
const certAppliedTypeDefault = "default"
for _, certItem := range modifyCloudResourceReq.Listen.Certificates {
if tea.StringValue(certItem.AppliedType) == certAppliedTypeDefault &&
tea.StringValue(certItem.CertificateId) == cloudCertId {
return nil
}
}
modifyCloudResourceReq.Listen.Certificates = lo.Filter(modifyCloudResourceReq.Listen.Certificates, func(c *aliwaf.ModifyCloudResourceRequestListenCertificates, _ int) bool {
return tea.StringValue(c.AppliedType) != certAppliedTypeDefault
})
modifyCloudResourceReq.Listen.Certificates = append(modifyCloudResourceReq.Listen.Certificates, &aliwaf.ModifyCloudResourceRequestListenCertificates{
CertificateId: tea.String(cloudCertId),
AppliedType: tea.String(certAppliedTypeDefault),
})
} else {
// 指定扩展域名,需替换扩展证书
const certAppliedTypeExtension = "extension"
modifyCloudResourceReq.Listen.Certificates = append(modifyCloudResourceReq.Listen.Certificates, &aliwaf.ModifyCloudResourceRequestListenCertificates{
CertificateId: tea.String(cloudCertId),
AppliedType: tea.String(certAppliedTypeExtension),
})
}
// 过滤掉不存在或已过期的证书,防止接口报错
modifyCloudResourceReq.Listen.Certificates = lo.Filter(modifyCloudResourceReq.Listen.Certificates, func(c *aliwaf.ModifyCloudResourceRequestListenCertificates, _ int) bool {
if tea.StringValue(c.CertificateId) == cloudCertId {
return true
}
resourceInstanceCert, _ := lo.Find(resourceInstanceCertificates, func(r *aliwaf.DescribeResourceInstanceCertsResponseBodyCerts) bool {
cId := tea.StringValue(c.CertificateId)
rId := tea.StringValue(r.CertIdentifier)
return cId == rId || strings.Split(cId, "-")[0] == strings.Split(rId, "-")[0]
})
if resourceInstanceCert != nil {
certNotAfter := time.Unix(tea.Int64Value(resourceInstanceCert.AfterDate)/1000, 0)
return certNotAfter.After(time.Now())
}
return false
})
// 修改云产品接入的配置
// REF: https://www.alibabacloud.com/help/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifycloudresource
modifyCloudResourceResp, err := d.sdkClient.ModifyCloudResourceWithContext(ctx, modifyCloudResourceReq, &dara.RuntimeOptions{})
d.logger.Debug("sdk request 'waf.ModifyCloudResource'", slog.Any("request", modifyCloudResourceReq), slog.Any("response", modifyCloudResourceResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'waf.ModifyCloudResource': %w", err)
}
return nil
}
func (d *SSLDeployerProvider) deployToWAF3WithCNAME(ctx context.Context, cloudCertId string) error {
if d.config.Domain == "" {
// 未指定扩展域名,只需替换默认证书
// 查询默认 SSL/TLS 设置
// REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps
@ -133,7 +318,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string,
ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId),
RegionId: tea.String(d.config.Region),
InstanceId: tea.String(d.config.InstanceId),
CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)),
CertId: tea.String(cloudCertId),
TLSVersion: tea.String("tlsv1"),
EnableTLSv3: tea.Bool(true),
}
@ -151,7 +336,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string,
return fmt.Errorf("failed to execute sdk request 'waf.ModifyDefaultHttps': %w", err)
}
} else {
// 指定接入域名
// 指定扩展域名,需替换扩展证书
// 查询 CNAME 接入详情
// REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedomaindetail
@ -172,7 +357,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string,
RegionId: tea.String(d.config.Region),
InstanceId: tea.String(d.config.InstanceId),
Domain: tea.String(d.config.Domain),
Listen: &aliwaf.ModifyDomainRequestListen{CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string))},
Listen: &aliwaf.ModifyDomainRequestListen{CertId: tea.String(cloudCertId)},
Redirect: &aliwaf.ModifyDomainRequestRedirect{Loadbalance: tea.String("iphash")},
}
modifyDomainReq = _assign(modifyDomainReq, describeDomainDetailResp.Body)

View File

@ -0,0 +1,8 @@
package aliyunwaf
const (
// 服务类型:云产品接入。
SERVICE_TYPE_CLOUDRESOURCE = "cloudresource"
// 服务类型CNAME 接入。
SERVICE_TYPE_CNAME = "cname"
)

View File

@ -123,6 +123,208 @@ func (client *WafClient) DescribeDomainDetailWithContext(ctx context.Context, re
return _result, _err
}
func (client *WafClient) DescribeProductInstancesWithContext(ctx context.Context, request *aliwaf.DescribeProductInstancesRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.DescribeProductInstancesResponse, _err error) {
_err = request.Validate()
if _err != nil {
return _result, _err
}
query := map[string]interface{}{}
if !dara.IsNil(request.InstanceId) {
query["InstanceId"] = request.InstanceId
}
if !dara.IsNil(request.OwnerUserId) {
query["OwnerUserId"] = request.OwnerUserId
}
if !dara.IsNil(request.PageNumber) {
query["PageNumber"] = request.PageNumber
}
if !dara.IsNil(request.PageSize) {
query["PageSize"] = request.PageSize
}
if !dara.IsNil(request.RegionId) {
query["RegionId"] = request.RegionId
}
if !dara.IsNil(request.ResourceInstanceAccessStatus) {
query["ResourceInstanceAccessStatus"] = request.ResourceInstanceAccessStatus
}
if !dara.IsNil(request.ResourceInstanceId) {
query["ResourceInstanceId"] = request.ResourceInstanceId
}
if !dara.IsNil(request.ResourceInstanceIp) {
query["ResourceInstanceIp"] = request.ResourceInstanceIp
}
if !dara.IsNil(request.ResourceInstanceName) {
query["ResourceInstanceName"] = request.ResourceInstanceName
}
if !dara.IsNil(request.ResourceIp) {
query["ResourceIp"] = request.ResourceIp
}
if !dara.IsNil(request.ResourceManagerResourceGroupId) {
query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId
}
if !dara.IsNil(request.ResourceName) {
query["ResourceName"] = request.ResourceName
}
if !dara.IsNil(request.ResourceProduct) {
query["ResourceProduct"] = request.ResourceProduct
}
if !dara.IsNil(request.ResourceRegionId) {
query["ResourceRegionId"] = request.ResourceRegionId
}
req := &openapiutil.OpenApiRequest{
Query: openapiutil.Query(query),
}
params := &openapiutil.Params{
Action: dara.String("DescribeProductInstances"),
Version: dara.String("2021-10-01"),
Protocol: dara.String("HTTPS"),
Pathname: dara.String("/"),
Method: dara.String("POST"),
AuthType: dara.String("AK"),
Style: dara.String("RPC"),
ReqBodyType: dara.String("formData"),
BodyType: dara.String("json"),
}
_result = &aliwaf.DescribeProductInstancesResponse{}
_body, _err := client.CallApiWithCtx(ctx, params, req, runtime)
if _err != nil {
return _result, _err
}
_err = dara.Convert(_body, &_result)
return _result, _err
}
func (client *WafClient) DescribeResourceInstanceCertsWithContext(ctx context.Context, request *aliwaf.DescribeResourceInstanceCertsRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.DescribeResourceInstanceCertsResponse, _err error) {
_err = request.Validate()
if _err != nil {
return _result, _err
}
query := map[string]interface{}{}
if !dara.IsNil(request.InstanceId) {
query["InstanceId"] = request.InstanceId
}
if !dara.IsNil(request.PageNumber) {
query["PageNumber"] = request.PageNumber
}
if !dara.IsNil(request.PageSize) {
query["PageSize"] = request.PageSize
}
if !dara.IsNil(request.RegionId) {
query["RegionId"] = request.RegionId
}
if !dara.IsNil(request.ResourceInstanceId) {
query["ResourceInstanceId"] = request.ResourceInstanceId
}
if !dara.IsNil(request.ResourceManagerResourceGroupId) {
query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId
}
req := &openapiutil.OpenApiRequest{
Query: openapiutil.Query(query),
}
params := &openapiutil.Params{
Action: dara.String("DescribeResourceInstanceCerts"),
Version: dara.String("2021-10-01"),
Protocol: dara.String("HTTPS"),
Pathname: dara.String("/"),
Method: dara.String("POST"),
AuthType: dara.String("AK"),
Style: dara.String("RPC"),
ReqBodyType: dara.String("formData"),
BodyType: dara.String("json"),
}
_result = &aliwaf.DescribeResourceInstanceCertsResponse{}
_body, _err := client.CallApiWithCtx(ctx, params, req, runtime)
if _err != nil {
return _result, _err
}
_err = dara.Convert(_body, &_result)
return _result, _err
}
func (client *WafClient) ModifyCloudResourceWithContext(ctx context.Context, tmpReq *aliwaf.ModifyCloudResourceRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.ModifyCloudResourceResponse, _err error) {
_err = tmpReq.Validate()
if _err != nil {
return _result, _err
}
request := &aliwaf.ModifyCloudResourceShrinkRequest{}
openapiutil.Convert(tmpReq, request)
if !dara.IsNil(tmpReq.Listen) {
request.ListenShrink = openapiutil.ArrayToStringWithSpecifiedStyle(tmpReq.Listen, dara.String("Listen"), dara.String("json"))
}
if !dara.IsNil(tmpReq.Redirect) {
request.RedirectShrink = openapiutil.ArrayToStringWithSpecifiedStyle(tmpReq.Redirect, dara.String("Redirect"), dara.String("json"))
}
query := map[string]interface{}{}
if !dara.IsNil(request.InstanceId) {
query["InstanceId"] = request.InstanceId
}
if !dara.IsNil(request.ListenShrink) {
query["Listen"] = request.ListenShrink
}
if !dara.IsNil(request.RedirectShrink) {
query["Redirect"] = request.RedirectShrink
}
if !dara.IsNil(request.RegionId) {
query["RegionId"] = request.RegionId
}
if !dara.IsNil(request.ResourceManagerResourceGroupId) {
query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId
}
req := &openapiutil.OpenApiRequest{
Query: openapiutil.Query(query),
}
params := &openapiutil.Params{
Action: dara.String("ModifyCloudResource"),
Version: dara.String("2021-10-01"),
Protocol: dara.String("HTTPS"),
Pathname: dara.String("/"),
Method: dara.String("POST"),
AuthType: dara.String("AK"),
Style: dara.String("RPC"),
ReqBodyType: dara.String("formData"),
BodyType: dara.String("json"),
}
_result = &aliwaf.ModifyCloudResourceResponse{}
_body, _err := client.CallApiWithCtx(ctx, params, req, runtime)
if _err != nil {
return _result, _err
}
_err = dara.Convert(_body, &_result)
return _result, _err
}
func (client *WafClient) ModifyDefaultHttpsWithContext(ctx context.Context, request *aliwaf.ModifyDefaultHttpsRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.ModifyDefaultHttpsResponse, _err error) {
_err = request.Validate()
if _err != nil {

View File

@ -26,9 +26,9 @@ type SSLDeployerProviderConfig struct {
Endpoint string `json:"endpoint,omitempty"`
// 腾讯云地域。
Region string `json:"region"`
// 云资源类型。
ResourceType string `json:"resourceType"`
// 云资源 ID 数组。
// 云产品类型。
ResourceProduct string `json:"resourceProduct"`
// 云产品资源 ID 数组。
ResourceIds []string `json:"resourceIds,omitempty"`
}
@ -79,8 +79,8 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
}
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
if d.config.ResourceType == "" {
return nil, errors.New("config `resourceType` is required")
if d.config.ResourceProduct == "" {
return nil, errors.New("config `resourceProduct` is required")
}
if len(d.config.ResourceIds) == 0 {
return nil, errors.New("config `resourceIds` is required")
@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// REF: https://cloud.tencent.com/document/api/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
deployCertificateInstanceReq.ResourceType = common.StringPtr(d.config.ResourceType)
deployCertificateInstanceReq.ResourceType = common.StringPtr(d.config.ResourceProduct)
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(d.config.ResourceIds)
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)

View File

@ -29,9 +29,9 @@ type SSLDeployerProviderConfig struct {
CertificateId string `json:"certificateId"`
// 是否替换原有证书(即保持原证书 ID 不变)。
IsReplaced bool `json:"isReplaced,omitempty"`
// 云资源类型数组。
ResourceTypes []string `json:"resourceTypes"`
// 云资源地域数组。
// 云产品类型数组。
ResourceProducts []string `json:"resourceProducts"`
// 云产品地域数组。
ResourceRegions []string `json:"resourceRegions"`
}
@ -85,8 +85,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
if d.config.CertificateId == "" {
return nil, errors.New("config `certificateId` is required")
}
if len(d.config.ResourceTypes) == 0 {
return nil, errors.New("config `resourceTypes` is required")
if len(d.config.ResourceProducts) == 0 {
return nil, errors.New("config `resourceProducts` is required")
}
if d.config.IsReplaced {
@ -124,8 +124,8 @@ func (d *SSLDeployerProvider) executeUpdateCertificateInstance(ctx context.Conte
updateCertificateInstanceReq := tcssl.NewUpdateCertificateInstanceRequest()
updateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId)
updateCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
updateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes)
updateCertificateInstanceReq.ResourceTypesRegions = wrapResourceTypeRegions(d.config.ResourceTypes, d.config.ResourceRegions)
updateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceProducts)
updateCertificateInstanceReq.ResourceTypesRegions = wrapResourceProductRegions(d.config.ResourceProducts, d.config.ResourceRegions)
updateCertificateInstanceResp, err := d.sdkClient.UpdateCertificateInstance(updateCertificateInstanceReq)
d.logger.Debug("sdk request 'ssl.UpdateCertificateInstance'", slog.Any("request", updateCertificateInstanceReq), slog.Any("response", updateCertificateInstanceResp))
if err != nil {
@ -200,8 +200,8 @@ func (d *SSLDeployerProvider) executeUploadUpdateCertificateInstance(ctx context
uploadUpdateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId)
uploadUpdateCertificateInstanceReq.CertificatePublicKey = common.StringPtr(certPEM)
uploadUpdateCertificateInstanceReq.CertificatePrivateKey = common.StringPtr(privkeyPEM)
uploadUpdateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes)
uploadUpdateCertificateInstanceReq.ResourceTypesRegions = wrapResourceTypeRegions(d.config.ResourceTypes, d.config.ResourceRegions)
uploadUpdateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceProducts)
uploadUpdateCertificateInstanceReq.ResourceTypesRegions = wrapResourceProductRegions(d.config.ResourceProducts, d.config.ResourceRegions)
uploadUpdateCertificateInstanceResp, err := d.sdkClient.UploadUpdateCertificateInstance(uploadUpdateCertificateInstanceReq)
d.logger.Debug("sdk request 'ssl.UploadUpdateCertificateInstance'", slog.Any("request", uploadUpdateCertificateInstanceReq), slog.Any("response", uploadUpdateCertificateInstanceResp))
if err != nil {
@ -278,19 +278,19 @@ func createSDKClient(secretId, secretKey, endpoint string) (*internal.SslClient,
return client, nil
}
func wrapResourceTypeRegions(resourceTypes, resourceRegions []string) []*tcssl.ResourceTypeRegions {
if len(resourceTypes) == 0 || len(resourceRegions) == 0 {
func wrapResourceProductRegions(resourceProducts, resourceRegions []string) []*tcssl.ResourceTypeRegions {
if len(resourceProducts) == 0 || len(resourceRegions) == 0 {
return nil
}
// 仅以下云资源类型支持地域
resourceTypesRequireRegion := []string{"apigateway", "clb", "cos", "tcb", "tke", "tse", "waf"}
// 仅以下云产品类型支持地域
resourceProductsRequireRegion := []string{"apigateway", "clb", "cos", "tcb", "tke", "tse", "waf"}
temp := make([]*tcssl.ResourceTypeRegions, 0)
for _, resourceType := range resourceTypes {
if slices.Contains(resourceTypesRequireRegion, resourceType) {
for _, resourceProduct := range resourceProducts {
if slices.Contains(resourceProductsRequireRegion, resourceProduct) {
temp = append(temp, &tcssl.ResourceTypeRegions{
ResourceType: common.StringPtr(resourceType),
ResourceType: common.StringPtr(resourceProduct),
Regions: common.StringPtrs(resourceRegions),
})
}

View File

@ -31,6 +31,16 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => {
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_apigw_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "serviceType"]}
initialValue={initialValues.serviceType}
@ -47,16 +57,6 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => {
</Select>
</Form.Item>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_apigw_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")} />
</Form.Item>
<Show when={fieldServiceType === SERVICE_TYPE_CLOUDNATIVE}>
<Form.Item
name={[parentNamePath, "gatewayId"]}

View File

@ -1,12 +1,16 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Select } from "antd";
import { AutoComplete, Form, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { validDomainName } from "@/utils/validators";
import Show from "@/components/Show";
import { validDomainName, validPortNumber } from "@/utils/validators";
import { useFormNestedFieldsContext } from "./_context";
const SERVICE_TYPE_CLOUDRESOURCE = "cloudresource" as const;
const SERVICE_TYPE_CNAME = "cname" as const;
const BizDeployNodeConfigFieldsProviderAliyunWAF = () => {
const { i18n, t } = useTranslation();
@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderAliyunWAF = () => {
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const formInst = Form.useFormInstance();
const initialValues = getInitialValues();
const fieldServiceType = Form.useWatch([parentNamePath, "serviceType"], formInst);
return (
<>
<Form.Item
@ -42,6 +49,22 @@ const BizDeployNodeConfigFieldsProviderAliyunWAF = () => {
</Select>
</Form.Item>
<Form.Item
name={[parentNamePath, "serviceType"]}
initialValue={initialValues.serviceType}
label={t("workflow_node.deploy.form.aliyun_waf_service_type.label")}
rules={[formRule]}
>
<Select placeholder={t("workflow_node.deploy.form.aliyun_waf_service_type.placeholder")}>
<Select.Option key={SERVICE_TYPE_CLOUDRESOURCE} value={SERVICE_TYPE_CLOUDRESOURCE}>
{t("workflow_node.deploy.form.aliyun_waf_service_type.option.cloudresource.label")}
</Select.Option>
<Select.Option key={SERVICE_TYPE_CNAME} value={SERVICE_TYPE_CNAME}>
{t("workflow_node.deploy.form.aliyun_waf_service_type.option.cname.label")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
name={[parentNamePath, "instanceId"]}
initialValue={initialValues.instanceId}
@ -52,6 +75,39 @@ const BizDeployNodeConfigFieldsProviderAliyunWAF = () => {
<Input placeholder={t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")} />
</Form.Item>
<Show when={fieldServiceType === SERVICE_TYPE_CLOUDRESOURCE}>
<Form.Item
name={[parentNamePath, "resourceProduct"]}
initialValue={initialValues.resourceProduct}
label={t("workflow_node.deploy.form.aliyun_waf_resource_product.label")}
rules={[formRule]}
>
<AutoComplete
options={["ecs", "clb4", "clb7", "nlb"].map((value) => ({ value }))}
placeholder={t("workflow_node.deploy.form.aliyun_waf_resource_product.placeholder")}
filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "resourceId"]}
initialValue={initialValues.resourceId}
label={t("workflow_node.deploy.form.aliyun_waf_resource_id.label")}
rules={[formRule]}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_waf_resource_id.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "resourcePort"]}
initialValue={initialValues.resourcePort}
label={t("workflow_node.deploy.form.aliyun_waf_resource_port.label")}
rules={[formRule]}
>
<Input type="number" min={1} max={65535} placeholder={t("workflow_node.deploy.form.aliyun_waf_resource_port.placeholder")} />
</Form.Item>
</Show>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
@ -70,23 +126,62 @@ const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
region: "",
serviceVersion: "3.0",
instanceId: "",
resourceProduct: "",
resourceId: "",
resourcePort: 443,
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")),
serviceVersion: z.literal("3.0", t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder")),
instanceId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")),
domain: z
.string()
.nullish()
.refine((v) => {
return !v || validDomainName(v!, { allowWildcard: true });
}, t("common.errmsg.domain_invalid")),
});
return z
.object({
region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")),
serviceVersion: z.literal("3.0", t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder")),
serviceType: z.literal([SERVICE_TYPE_CLOUDRESOURCE, SERVICE_TYPE_CNAME], t("workflow_node.deploy.form.aliyun_waf_service_type.placeholder")),
instanceId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")),
resourceProduct: z.string().nullish(),
resourceId: z.string().nullish(),
resourcePort: z.preprocess((v) => (v == null || v === "" ? void 0 : Number(v)), z.number().nullish()),
domain: z
.string()
.nullish()
.refine((v) => {
return !v || validDomainName(v!, { allowWildcard: true });
}, t("common.errmsg.domain_invalid")),
})
.superRefine((values, ctx) => {
switch (values.serviceType) {
case SERVICE_TYPE_CLOUDRESOURCE:
{
if (!values.resourceProduct) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.aliyun_waf_resource_product.placeholder"),
path: ["resourceProduct"],
});
}
if (!values.resourceId) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.aliyun_waf_resource_id.placeholder"),
path: ["resourceId"],
});
}
if (!validPortNumber(values.resourcePort!)) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.aliyun_waf_resource_port.placeholder"),
path: ["resourcePort"],
});
}
}
break;
}
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunWAF, {

View File

@ -47,15 +47,15 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy = () => {
</Form.Item>
<Form.Item
name={[parentNamePath, "resourceType"]}
initialValue={initialValues.resourceType}
label={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.label")}
name={[parentNamePath, "resourceProduct"]}
initialValue={initialValues.resourceProduct}
label={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.tooltip") }}></span>}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.tooltip") }}></span>}
>
<AutoComplete
options={["apigateway", "cdn", "clb", "cos", "ddos", "lighthouse", "live", "tcb", "teo", "tke", "tse", "vod", "waf"].map((value) => ({ value }))}
placeholder={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder")}
placeholder={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder")}
filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())}
/>
</Form.Item>
@ -83,7 +83,7 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
resourceType: "",
resourceProduct: "",
resourceIds: "",
};
};
@ -94,7 +94,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
return z.object({
endpoint: z.string().nullish(),
region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder")),
resourceType: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder")),
resourceProduct: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder")),
resourceIds: z.string().refine((v) => {
if (!v) return false;
return String(v)

View File

@ -47,17 +47,17 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate = () => {
</Form.Item>
<Form.Item
name={[parentNamePath, "resourceTypes"]}
initialValue={initialValues.resourceTypes}
label={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.label")}
extra={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.help")}
name={[parentNamePath, "resourceProducts"]}
initialValue={initialValues.resourceProducts}
label={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.label")}
extra={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.tooltip") }}></span>}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.tooltip") }}></span>}
>
<MultipleSplitValueInput
modalTitle={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.title")}
placeholder={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder")}
placeholderInModal={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.placeholder")}
modalTitle={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.title")}
placeholder={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder")}
placeholderInModal={t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.placeholder")}
separator={MULTIPLE_INPUT_SEPARATOR}
splitOptions={{ removeEmpty: true, trimSpace: true }}
/>
@ -96,7 +96,7 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
certificateId: "",
resourceTypes: "",
resourceProducts: "",
};
};
@ -106,12 +106,12 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
return z.object({
endpoint: z.string().nullish(),
certificateId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder")),
resourceTypes: z.string().refine((v) => {
resourceProducts: z.string().refine((v) => {
if (!v) return false;
return String(v)
.split(MULTIPLE_INPUT_SEPARATOR)
.every((e) => !!e.trim());
}, t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder")),
}, t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder")),
resourceRegions: z
.string()
.nullish()

View File

@ -230,13 +230,13 @@
"workflow_node.deploy.form.aliyun_alb_snidomain.label": "Alibaba Cloud ALB SNI domain (Optional)",
"workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "Please enter Alibaba Cloud ALB SNI domain name",
"workflow_node.deploy.form.aliyun_alb_snidomain.help": "",
"workflow_node.deploy.form.aliyun_apigw_region.label": "Alibaba Cloud region",
"workflow_node.deploy.form.aliyun_apigw_region.placeholder": "Please enter Alibaba Cloud API gateway region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_apigw_region.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions\" target=\"_blank\">https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions</a>",
"workflow_node.deploy.form.aliyun_apigw_service_type.label": "Alibaba Cloud API gateway type",
"workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "Please select Alibaba Cloud API gateway type",
"workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "Cloud-native API gateway",
"workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "Traditional API gateway",
"workflow_node.deploy.form.aliyun_apigw_region.label": "Alibaba Cloud region",
"workflow_node.deploy.form.aliyun_apigw_region.placeholder": "Please enter Alibaba Cloud API gateway region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_apigw_region.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions\" target=\"_blank\">https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions</a>",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "Alibaba Cloud API gateway ID",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "Please enter Alibaba Cloud API gateway ID",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "For more information, see <a href=\"https://apigw.console.aliyun.com\" target=\"_blank\">https://apigw.console.aliyun.com</a>",
@ -358,11 +358,21 @@
"workflow_node.deploy.form.aliyun_waf_region.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint\" target=\"_blank\">https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint</a>",
"workflow_node.deploy.form.aliyun_waf_service_version.label": "Alibaba Cloud WAF version",
"workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "Please select Alibaba Cloud WAF version",
"workflow_node.deploy.form.aliyun_waf_service_type.label": "Alibaba Cloud WAF access type",
"workflow_node.deploy.form.aliyun_waf_service_type.placeholder": "Please select Alibaba Cloud WAF access type",
"workflow_node.deploy.form.aliyun_waf_service_type.option.cloudresource.label": "Cloud product access",
"workflow_node.deploy.form.aliyun_waf_service_type.option.cname.label": "CNAME access",
"workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID",
"workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID",
"workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see <a href=\"https://waf.console.aliyun.com\" target=\"_blank\">https://waf.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF domain (Optional)",
"workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF domain name",
"workflow_node.deploy.form.aliyun_waf_resource_product.label": "Alibaba Cloud WAF accessed resource product",
"workflow_node.deploy.form.aliyun_waf_resource_product.placeholder": "Please enter Alibaba Cloud WAF accessed resource product",
"workflow_node.deploy.form.aliyun_waf_resource_id.label": "Alibaba Cloud WAF accessed resource ID",
"workflow_node.deploy.form.aliyun_waf_resource_id.placeholder": "Please enter Alibaba Cloud WAF accessed resource ID",
"workflow_node.deploy.form.aliyun_waf_resource_port.label": "Alibaba Cloud WAF accessed resource port",
"workflow_node.deploy.form.aliyun_waf_resource_port.placeholder": "Please enter Alibaba Cloud WAF accessed resource port",
"workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF SNI domain (Optional)",
"workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF SNI domain name",
"workflow_node.deploy.form.aliyun_waf_domain.help": "",
"workflow_node.deploy.form.apisix_resource_type.label": "Resource type",
"workflow_node.deploy.form.apisix_resource_type.placeholder": "Please select resource type",
@ -845,9 +855,9 @@
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.label": "Tencent Cloud region",
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder": "Please enter Tencent Cloud service region (e.g. ap-guangzhou)",
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.tooltip": "For more information, see <a href=\"https://www.tencentcloud.com/document/product/1007/36573\" target=\"_blank\">https://www.tencentcloud.com/document/product/1007/36573</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.label": "Tencent Cloud resource type",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder": "Please enter Tencent Cloud resource type",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/400/91667\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91667</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.label": "Tencent Cloud resource product",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder": "Please enter Tencent Cloud resource product",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/400/91667\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91667</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.label": "Tencent Cloud resource IDs",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.placeholder": "Please enter Tencent Cloud resource IDs (separated by semicolons)",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.errmsg.invalid": "Please enter a valid Tencent Cloud resource ID",
@ -862,12 +872,12 @@
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.label": "Tencent Cloud SSL certificate ID",
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder": "Please enter Tencent Cloud SSL certificate ID",
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.tooltip": "For more information, see <a href=\"https://console.cloud.tencent.com/certoverview\" target=\"_blank\">https://console.cloud.tencent.com/certoverview</a>",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.label": "Tencent Cloud resource types",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder": "Please enter Tencent Cloud resource types (separated by semicolons)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.help": "Notes: Multi-values should be separated by semicolons.",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.tooltip": "For more information, see <a href=\"https://www.tencentcloud.com/document/product/1007/57981\" target=\"_blank\">https://www.tencentcloud.com/document/product/1007/57981</a> or <a href=\"https://www.tencentcloud.com/document/product/1007/70503\" target=\"_blank\">https://www.tencentcloud.com/document/product/1007/70503</a>",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.title": "Change Tencent Cloud resource types",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource type",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.label": "Tencent Cloud resource products",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder": "Please enter Tencent Cloud resource products (separated by semicolons)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.help": "Notes: Multi-values should be separated by semicolons.",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.tooltip": "For more information, see <a href=\"https://www.tencentcloud.com/document/product/1007/57981\" target=\"_blank\">https://www.tencentcloud.com/document/product/1007/57981</a> or <a href=\"https://www.tencentcloud.com/document/product/1007/70503\" target=\"_blank\">https://www.tencentcloud.com/document/product/1007/70503</a>",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.title": "Change Tencent Cloud resource products",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource product",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.label": "Tencent Cloud resource regions (Optional)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.placeholder": "Please enter Tencent Cloud resource regions (separated by semicolons)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.help": "Notes: Multi-values should be separated by semicolons.",

View File

@ -229,13 +229,13 @@
"workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)",
"workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名",
"workflow_node.deploy.form.aliyun_alb_snidomain.help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。",
"workflow_node.deploy.form.aliyun_apigw_region.label": "阿里云服务地域",
"workflow_node.deploy.form.aliyun_apigw_region.placeholder": "请输入阿里云 API 网关地域例如cn-hangzhou",
"workflow_node.deploy.form.aliyun_apigw_region.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions\" target=\"_blank\">https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions</a>",
"workflow_node.deploy.form.aliyun_apigw_service_type.label": "阿里云 API 网关服务类型",
"workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "请选择阿里云 API 网关服务类型",
"workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "云原生 API 网关",
"workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "原 API 网关",
"workflow_node.deploy.form.aliyun_apigw_region.label": "阿里云服务地域",
"workflow_node.deploy.form.aliyun_apigw_region.placeholder": "请输入阿里云 API 网关地域例如cn-hangzhou",
"workflow_node.deploy.form.aliyun_apigw_region.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions\" target=\"_blank\">https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions</a>",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "阿里云 API 网关 ID",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "请输入阿里云 API 网关 ID",
"workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "这是什么?请参阅 <a href=\"https://apigw.console.aliyun.com\" target=\"_blank\">https://apigw.console.aliyun.com</a>",
@ -357,11 +357,21 @@
"workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint\" target=\"_blank\">https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint</a>",
"workflow_node.deploy.form.aliyun_waf_service_version.label": "阿里云 WAF 服务版本",
"workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "请选择阿里云 WAF 服务版本",
"workflow_node.deploy.form.aliyun_waf_service_type.label": "阿里云 WAF 服务接入方式",
"workflow_node.deploy.form.aliyun_waf_service_type.placeholder": "请选择阿里云 WAF 服务接入方式",
"workflow_node.deploy.form.aliyun_waf_service_type.option.cloudresource.label": "云产品接入",
"workflow_node.deploy.form.aliyun_waf_service_type.option.cname.label": "CNAME 接入",
"workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID",
"workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID",
"workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 <a href=\"https://waf.console.aliyun.com\" target=\"_blank\">https://waf.console.aliyun.com</a><br>仅支持 CNAME 接入。",
"workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 接入域名(可选)",
"workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 接入域名",
"workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 <a href=\"https://waf.console.aliyun.com\" target=\"_blank\">https://waf.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_waf_resource_product.label": "阿里云 WAF 云产品接入资源类型",
"workflow_node.deploy.form.aliyun_waf_resource_product.placeholder": "请选择 WAF 云产品接入资源类型",
"workflow_node.deploy.form.aliyun_waf_resource_id.label": "阿里云 WAF 云产品接入资源 ID",
"workflow_node.deploy.form.aliyun_waf_resource_id.placeholder": "请选择阿里云 WAF 云产品接入资源 ID",
"workflow_node.deploy.form.aliyun_waf_resource_port.label": "阿里云 WAF 云产品接入端口",
"workflow_node.deploy.form.aliyun_waf_resource_port.placeholder": "请选择阿里云 WAF 云产品接入端口",
"workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 扩展域名(可选)",
"workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 扩展域名",
"workflow_node.deploy.form.aliyun_waf_domain.help": "提示:不填写时,将替换实例的默认证书;否则,将替换扩展域名证书。",
"workflow_node.deploy.form.apisix_resource_type.label": "证书部署方式",
"workflow_node.deploy.form.apisix_resource_type.placeholder": "请选择证书部署方式",
@ -843,9 +853,9 @@
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.label": "腾讯云服务地域",
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder": "请输入腾讯云云产品服务地域例如ap-guangzhou",
"workflow_node.deploy.form.tencentcloud_ssldeploy_region.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/400/41659\" target=\"_blank\">https://cloud.tencent.com/document/product/400/41659</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.label": "腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder": "请输入腾讯云产品资源类型",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/400/91667\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91667</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.label": "腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder": "请输入腾讯云产品资源类型",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/400/91667\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91667</a>",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.label": "腾讯云云产品资源 ID",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.placeholder": "请输入腾讯云云产品资源 ID多个值请用半角分号隔开",
"workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.errmsg.invalid": "请输入正确的腾讯云云产品资源 ID",
@ -860,12 +870,12 @@
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.label": "腾讯云原证书 ID",
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder": "请输入腾讯云原证书 ID",
"workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/certoverview\" target=\"_blank\">https://console.cloud.tencent.com/certoverview</a>",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.label": "腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.help": "提示:多个类型请用半角分号隔开。",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/400/91649\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91649</a> 或 <a href=\"https://cloud.tencent.com/document/product/400/119791\" target=\"_blank\">https://cloud.tencent.com/document/product/400/119791</a><br>注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.title": "修改腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.label": "腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.help": "提示:多个类型请用半角分号隔开。",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/400/91649\" target=\"_blank\">https://cloud.tencent.com/document/product/400/91649</a> 或 <a href=\"https://cloud.tencent.com/document/product/400/119791\" target=\"_blank\">https://cloud.tencent.com/document/product/400/119791</a><br>注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.title": "修改腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.label": "腾讯云云产品部署地域(可选)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.placeholder": "请输入腾讯云云产品部署地域(多个值请用半角分号隔开)",
"workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.help": "提示:多个地域请用半角分号隔开。",