diff --git a/go.mod b/go.mod index 8fa0f6a6..c5116a5b 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect diff --git a/go.sum b/go.sum index d848839f..b212a754 100644 --- a/go.sum +++ b/go.sum @@ -379,6 +379,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index 8f38f4a4..51512416 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -18,16 +18,6 @@ import ( "github.com/usual2970/certimate/internal/repository" ) -type applyConfig struct { - Domains string - ContactEmail string - AccessConfig string - KeyAlgorithm string - Nameservers string - PropagationTimeout int32 - DisableFollowCNAME bool -} - type ApplyCertResult struct { Certificate string PrivateKey string @@ -41,6 +31,18 @@ type Applicant interface { Apply() (*ApplyCertResult, error) } +type applicantOptions struct { + Domains []string + ContactEmail string + Provider domain.ApplyDNSProviderType + ProviderAccessConfig map[string]any + ProviderApplyConfig map[string]any + KeyAlgorithm string + Nameservers []string + PropagationTimeout int32 + DisableFollowCNAME bool +} + func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { if node.Type != domain.WorkflowNodeTypeApply { return nil, fmt.Errorf("node type is not apply") @@ -53,29 +55,35 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { return nil, fmt.Errorf("failed to get access #%s record: %w", accessId, err) } - applyProvider := node.GetConfigString("provider") - applyConfig := &applyConfig{ - Domains: node.GetConfigString("domains"), - ContactEmail: node.GetConfigString("contactEmail"), - AccessConfig: access.Config, - KeyAlgorithm: node.GetConfigString("keyAlgorithm"), - Nameservers: node.GetConfigString("nameservers"), - PropagationTimeout: node.GetConfigInt32("propagationTimeout"), - DisableFollowCNAME: node.GetConfigBool("disableFollowCNAME"), + accessConfig, err := access.UnmarshalConfigToMap() + if err != nil { + return nil, fmt.Errorf("failed to unmarshal access config: %w", err) } - challengeProvider, err := createChallengeProvider(domain.ApplyDNSProviderType(applyProvider), access.Config, applyConfig) + options := &applicantOptions{ + Domains: strings.Split(node.GetConfigString("domains"), ";"), + ContactEmail: node.GetConfigString("contactEmail"), + Provider: domain.ApplyDNSProviderType(node.GetConfigString("provider")), + ProviderAccessConfig: accessConfig, + ProviderApplyConfig: node.GetConfigMap("providerConfig"), + KeyAlgorithm: node.GetConfigString("keyAlgorithm"), + Nameservers: strings.Split(node.GetConfigString("nameservers"), ";"), + PropagationTimeout: node.GetConfigInt32("propagationTimeout"), + DisableFollowCNAME: node.GetConfigBool("disableFollowCNAME"), + } + + applicant, err := createApplicant(options) if err != nil { return nil, err } return &proxyApplicant{ - applicant: challengeProvider, - applyConfig: applyConfig, + applicant: applicant, + options: options, }, nil } -func apply(challengeProvider challenge.Provider, applyConfig *applyConfig) (*ApplyCertResult, error) { +func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) { settingsRepo := repository.NewSettingsRepository() settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider") @@ -93,20 +101,20 @@ func apply(challengeProvider challenge.Provider, applyConfig *applyConfig) (*App sslProviderConfig.Provider = defaultSSLProvider } - myUser, err := newAcmeUser(sslProviderConfig.Provider, applyConfig.ContactEmail) + myUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail) if err != nil { return nil, err } // Some unified lego environment variables are configured here. // link: https://github.com/go-acme/lego/issues/1867 - os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(applyConfig.DisableFollowCNAME)) + os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME)) config := lego.NewConfig(myUser) // This CA URL is configured for a local dev instance of Boulder running in Docker in a VM. config.CADirURL = sslProviderUrls[sslProviderConfig.Provider] - config.Certificate.KeyType = parseKeyAlgorithm(applyConfig.KeyAlgorithm) + config.Certificate.KeyType = parseKeyAlgorithm(options.KeyAlgorithm) // A client facilitates communication with the CA server. client, err := lego.NewClient(config) @@ -115,8 +123,8 @@ func apply(challengeProvider challenge.Provider, applyConfig *applyConfig) (*App } challengeOptions := make([]dns01.ChallengeOption, 0) - if len(applyConfig.Nameservers) > 0 { - challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(strings.Split(applyConfig.Nameservers, ";")))) + if len(options.Nameservers) > 0 { + challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers))) challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement()) } @@ -132,7 +140,7 @@ func apply(challengeProvider challenge.Provider, applyConfig *applyConfig) (*App } certRequest := certificate.ObtainRequest{ - Domains: strings.Split(applyConfig.Domains, ";"), + Domains: options.Domains, Bundle: true, } certResource, err := client.Certificate.Obtain(certRequest) @@ -144,9 +152,9 @@ func apply(challengeProvider challenge.Provider, applyConfig *applyConfig) (*App PrivateKey: string(certResource.PrivateKey), Certificate: string(certResource.Certificate), IssuerCertificate: string(certResource.IssuerCertificate), - CSR: string(certResource.CSR), ACMECertUrl: certResource.CertURL, ACMECertStableUrl: certResource.CertStableURL, + CSR: string(certResource.CSR), }, nil } @@ -171,10 +179,10 @@ func parseKeyAlgorithm(algo string) certcrypto.KeyType { // TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑 type proxyApplicant struct { - applicant challenge.Provider - applyConfig *applyConfig + applicant challenge.Provider + options *applicantOptions } func (d *proxyApplicant) Apply() (*ApplyCertResult, error) { - return apply(d.applicant, d.applyConfig) + return apply(d.applicant, d.options) } diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 147fbc72..94bd8541 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -1,7 +1,6 @@ package applicant import ( - "encoding/json" "fmt" "github.com/go-acme/lego/v4/challenge" @@ -18,19 +17,20 @@ import ( providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns" providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud" providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine" + "github.com/usual2970/certimate/internal/pkg/utils/maps" ) -func createChallengeProvider(provider domain.ApplyDNSProviderType, accessConfig string, applyConfig *applyConfig) (challenge.Provider, error) { +func createApplicant(options *applicantOptions) (challenge.Provider, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ - switch provider { + switch options.Provider { case domain.ApplyDNSProviderTypeACMEHttpReq: { - access := &domain.AccessConfigForACMEHttpReq{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForACMEHttpReq{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerACMEHttpReq.NewChallengeProvider(&providerACMEHttpReq.ACMEHttpReqApplicantConfig{ @@ -38,162 +38,162 @@ func createChallengeProvider(provider domain.ApplyDNSProviderType, accessConfig Mode: access.Mode, Username: access.Username, Password: access.Password, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS: { - access := &domain.AccessConfigForAliyun{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForAliyun{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerAliyun.NewChallengeProvider(&providerAliyun.AliyunApplicantConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53: { - access := &domain.AccessConfigForAWS{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForAWS{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerAWS.NewChallengeProvider(&providerAWS.AWSApplicantConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: access.Region, - HostedZoneId: access.HostedZoneId, - PropagationTimeout: applyConfig.PropagationTimeout, + Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"), + HostedZoneId: maps.GetValueAsString(options.ProviderApplyConfig, "hostedZoneId"), + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeCloudflare: { - access := &domain.AccessConfigForCloudflare{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForCloudflare{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerCloudflare.NewChallengeProvider(&providerCloudflare.CloudflareApplicantConfig{ DnsApiToken: access.DnsApiToken, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeGoDaddy: { - access := &domain.AccessConfigForGoDaddy{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForGoDaddy{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerGoDaddy.NewChallengeProvider(&providerGoDaddy.GoDaddyApplicantConfig{ ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS: { - access := &domain.AccessConfigForHuaweiCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForHuaweiCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerHuaweiCloud.NewChallengeProvider(&providerHuaweiCloud.HuaweiCloudApplicantConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: access.Region, - PropagationTimeout: applyConfig.PropagationTimeout, + Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"), + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeNameDotCom: { - access := &domain.AccessConfigForNameDotCom{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForNameDotCom{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerNameDotCom.NewChallengeProvider(&providerNameDotCom.NameDotComApplicantConfig{ Username: access.Username, ApiToken: access.ApiToken, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeNameSilo: { - access := &domain.AccessConfigForNameSilo{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForNameSilo{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerNameSilo.NewChallengeProvider(&providerNameSilo.NameSiloApplicantConfig{ ApiKey: access.ApiKey, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypePowerDNS: { - access := &domain.AccessConfigForPowerDNS{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForPowerDNS{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerPowerDNS.NewChallengeProvider(&providerPowerDNS.PowerDNSApplicantConfig{ ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS: { - access := &domain.AccessConfigForTencentCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForTencentCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerTencentCloud.NewChallengeProvider(&providerTencentCloud.TencentCloudApplicantConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS: { - access := &domain.AccessConfigForVolcEngine{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForVolcEngine{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) } applicant, err := providerVolcEngine.NewChallengeProvider(&providerVolcEngine.VolcEngineApplicantConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - PropagationTimeout: applyConfig.PropagationTimeout, + PropagationTimeout: options.PropagationTimeout, }) return applicant, err } } - return nil, fmt.Errorf("unsupported applicant provider: %s", provider) + return nil, fmt.Errorf("unsupported applicant provider: %s", string(options.Provider)) } diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 5ad5d6ce..41c589bc 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -14,6 +14,12 @@ type Deployer interface { Deploy(ctx context.Context) error } +type deployerOptions struct { + Provider domain.DeployProviderType + ProviderAccessConfig map[string]any + ProviderDeployConfig map[string]any +} + func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { Certificate string PrivateKey string @@ -30,9 +36,16 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { return nil, fmt.Errorf("failed to get access #%s record: %w", accessId, err) } - deployProvider := node.GetConfigString("provider") - deployConfig := node.GetConfigMap("providerConfig") - deployer, logger, err := createDeployer(domain.DeployProviderType(deployProvider), access.Config, deployConfig) + accessConfig, err := access.UnmarshalConfigToMap() + if err != nil { + return nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, logger, err := createDeployer(&deployerOptions{ + Provider: domain.DeployProviderType(node.GetConfigString("provider")), + ProviderAccessConfig: accessConfig, + ProviderDeployConfig: node.GetConfigMap("providerConfig"), + }) if err != nil { return nil, err } diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 62cc1313..dbb75851 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -1,7 +1,6 @@ package deployer import ( - "encoding/json" "fmt" "strconv" @@ -34,30 +33,30 @@ import ( "github.com/usual2970/certimate/internal/pkg/utils/maps" ) -func createDeployer(provider domain.DeployProviderType, accessConfig string, deployConfig map[string]any) (deployer.Deployer, logger.Logger, error) { +func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, error) { logger := logger.NewDefaultLogger() /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ - switch provider { + switch options.Provider { case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS: { - access := &domain.AccessConfigForAliyun{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForAliyun{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - switch provider { + switch options.Provider { case domain.DeployProviderTypeAliyunALB: deployer, err := providerAliyunALB.NewWithLogger(&providerAliyunALB.AliyunALBDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maps.GetValueAsString(deployConfig, "region"), - ResourceType: providerAliyunALB.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), - LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), - ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: providerAliyunALB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"), }, logger) return deployer, logger, err @@ -65,7 +64,7 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerAliyunCDN.NewWithLogger(&providerAliyunCDN.AliyunCDNDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -73,10 +72,10 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerAliyunCLB.NewWithLogger(&providerAliyunCLB.AliyunCLBDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maps.GetValueAsString(deployConfig, "region"), - ResourceType: providerAliyunCLB.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), - LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), - ListenerPort: maps.GetValueAsInt32(deployConfig, "listenerPort"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: providerAliyunCLB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), + ListenerPort: maps.GetValueAsInt32(options.ProviderDeployConfig, "listenerPort"), }, logger) return deployer, logger, err @@ -84,7 +83,7 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerAliyunDCDN.NewWithLogger(&providerAliyunDCDN.AliyunDCDNDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -92,10 +91,10 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerAliyunNLB.NewWithLogger(&providerAliyunNLB.AliyunNLBDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maps.GetValueAsString(deployConfig, "region"), - ResourceType: providerAliyunNLB.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), - LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), - ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: providerAliyunNLB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"), }, logger) return deployer, logger, err @@ -103,9 +102,9 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerAliyunOSS.NewWithLogger(&providerAliyunOSS.AliyunOSSDeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maps.GetValueAsString(deployConfig, "region"), - Bucket: maps.GetValueAsString(deployConfig, "bucket"), - Domain: maps.GetValueAsString(deployConfig, "domain"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -116,63 +115,63 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep case domain.DeployProviderTypeBaiduCloudCDN: { - access := &domain.AccessConfigForBaiduCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForBaiduCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerBaiduCloudCDN.NewWithLogger(&providerBaiduCloudCDN.BaiduCloudCDNDeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeBytePlusCDN: { - access := &domain.AccessConfigForBytePlus{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForBytePlus{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerBytePlusCDN.NewWithLogger(&providerBytePlusCDN.BytePlusCDNDeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeDogeCloudCDN: { - access := &domain.AccessConfigForDogeCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForDogeCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerDogeCDN.NewWithLogger(&providerDogeCDN.DogeCloudCDNDeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB: { - access := &domain.AccessConfigForHuaweiCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForHuaweiCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - switch provider { + switch options.Provider { case domain.DeployProviderTypeHuaweiCloudCDN: deployer, err := providerHuaweiCloudCDN.NewWithLogger(&providerHuaweiCloudCDN.HuaweiCloudCDNDeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maps.GetValueAsString(deployConfig, "region"), - Domain: maps.GetValueAsString(deployConfig, "domain"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -180,11 +179,11 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerHuaweiCloudELB.NewWithLogger(&providerHuaweiCloudELB.HuaweiCloudELBDeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maps.GetValueAsString(deployConfig, "region"), - ResourceType: providerHuaweiCloudELB.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), - CertificateId: maps.GetValueAsString(deployConfig, "certificateId"), - LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), - ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: providerHuaweiCloudELB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + CertificateId: maps.GetValueAsString(options.ProviderDeployConfig, "certificateId"), + LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"), }, logger) return deployer, logger, err @@ -196,58 +195,58 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep case domain.DeployProviderTypeLocal: { deployer, err := providerLocal.NewWithLogger(&providerLocal.LocalDeployerConfig{ - ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(deployConfig, "shellEnv")), - PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), - PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), - OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", string(providerLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"), - OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"), - PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), - JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), - JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"), - JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"), + ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(options.ProviderDeployConfig, "shellEnv")), + PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"), + PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"), + OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maps.GetValueAsString(options.ProviderDeployConfig, "certPath"), + OutputKeyPath: maps.GetValueAsString(options.ProviderDeployConfig, "keyPath"), + PfxPassword: maps.GetValueAsString(options.ProviderDeployConfig, "pfxPassword"), + JksAlias: maps.GetValueAsString(options.ProviderDeployConfig, "jksAlias"), + JksKeypass: maps.GetValueAsString(options.ProviderDeployConfig, "jksKeypass"), + JksStorepass: maps.GetValueAsString(options.ProviderDeployConfig, "jksStorepass"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeKubernetesSecret: { - access := &domain.AccessConfigForKubernetes{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForKubernetes{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerK8sSecret.NewWithLogger(&providerK8sSecret.K8sSecretDeployerConfig{ KubeConfig: access.KubeConfig, - Namespace: maps.GetValueOrDefaultAsString(deployConfig, "namespace", "default"), - SecretName: maps.GetValueAsString(deployConfig, "secretName"), - SecretType: maps.GetValueOrDefaultAsString(deployConfig, "secretType", "kubernetes.io/tls"), - SecretDataKeyForCrt: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForCrt", "tls.crt"), - SecretDataKeyForKey: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForKey", "tls.key"), + Namespace: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "namespace", "default"), + SecretName: maps.GetValueAsString(options.ProviderDeployConfig, "secretName"), + SecretType: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretType", "kubernetes.io/tls"), + SecretDataKeyForCrt: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretDataKeyForKey", "tls.key"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeQiniuCDN: { - access := &domain.AccessConfigForQiniu{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForQiniu{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerQiniuCDN.NewWithLogger(&providerQiniuCDN.QiniuCDNDeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeSSH: { - access := &domain.AccessConfigForSSH{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForSSH{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } sshPort, _ := strconv.ParseInt(access.Port, 10, 32) @@ -258,32 +257,32 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), - PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), - OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"), - OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"), - PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), - JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), - JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"), - JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"), + PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"), + PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"), + OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maps.GetValueAsString(options.ProviderDeployConfig, "certPath"), + OutputKeyPath: maps.GetValueAsString(options.ProviderDeployConfig, "keyPath"), + PfxPassword: maps.GetValueAsString(options.ProviderDeployConfig, "pfxPassword"), + JksAlias: maps.GetValueAsString(options.ProviderDeployConfig, "jksAlias"), + JksKeypass: maps.GetValueAsString(options.ProviderDeployConfig, "jksKeypass"), + JksStorepass: maps.GetValueAsString(options.ProviderDeployConfig, "jksStorepass"), }, logger) return deployer, logger, err } case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO: { - access := &domain.AccessConfigForTencentCloud{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForTencentCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - switch provider { + switch options.Provider { case domain.DeployProviderTypeTencentCloudCDN: deployer, err := providerTencentCloudCDN.NewWithLogger(&providerTencentCloudCDN.TencentCloudCDNDeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -291,11 +290,11 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerTencentCloudCLB.NewWithLogger(&providerTencentCloudCLB.TencentCloudCLBDeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maps.GetValueAsString(deployConfig, "region"), - ResourceType: providerTencentCloudCLB.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), - LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), - ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), - Domain: maps.GetValueAsString(deployConfig, "domain"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: providerTencentCloudCLB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -303,9 +302,9 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerTencentCloudCOD.NewWithLogger(&providerTencentCloudCOD.TencentCloudCOSDeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maps.GetValueAsString(deployConfig, "region"), - Bucket: maps.GetValueAsString(deployConfig, "bucket"), - Domain: maps.GetValueAsString(deployConfig, "domain"), + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -313,7 +312,7 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerTencentCloudECDN.NewWithLogger(&providerTencentCloudECDN.TencentCloudECDNDeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -321,8 +320,8 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerTencentCloudEO.NewWithLogger(&providerTencentCloudEO.TencentCloudEODeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maps.GetValueAsString(deployConfig, "zoneId"), - Domain: maps.GetValueAsString(deployConfig, "domain"), + ZoneId: maps.GetValueAsString(options.ProviderDeployConfig, "zoneId"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -333,17 +332,17 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineLive: { - access := &domain.AccessConfigForVolcEngine{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForVolcEngine{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - switch provider { + switch options.Provider { case domain.DeployProviderTypeVolcEngineCDN: deployer, err := providerVolcEngineCDN.NewWithLogger(&providerVolcEngineCDN.VolcEngineCDNDeployerConfig{ AccessKey: access.AccessKeyId, SecretKey: access.SecretAccessKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -351,7 +350,7 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep deployer, err := providerVolcEngineLive.NewWithLogger(&providerVolcEngineLive.VolcEngineLiveDeployerConfig{ AccessKey: access.AccessKeyId, SecretKey: access.SecretAccessKey, - Domain: maps.GetValueAsString(deployConfig, "domain"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -362,18 +361,18 @@ func createDeployer(provider domain.DeployProviderType, accessConfig string, dep case domain.DeployProviderTypeWebhook: { - access := &domain.AccessConfigForWebhook{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + access := domain.AccessConfigForWebhook{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{ WebhookUrl: access.Url, - WebhookData: maps.GetValueAsString(deployConfig, "webhookData"), + WebhookData: maps.GetValueAsString(options.ProviderDeployConfig, "webhookData"), }, logger) return deployer, logger, err } } - return nil, nil, fmt.Errorf("unsupported deployer provider: %s", provider) + return nil, nil, fmt.Errorf("unsupported deployer provider: %s", string(options.Provider)) } diff --git a/internal/domain/access.go b/internal/domain/access.go index 9a2bcb7d..b90405e6 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -1,6 +1,9 @@ package domain -import "time" +import ( + "encoding/json" + "time" +) type Access struct { Meta @@ -11,6 +14,15 @@ type Access struct { DeletedAt time.Time `json:"deleted" db:"deleted"` } +func (a *Access) UnmarshalConfigToMap() (map[string]any, error) { + config := make(map[string]any) + if err := json.Unmarshal([]byte(a.Config), &config); err != nil { + return nil, err + } + + return config, nil +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode"` @@ -26,8 +38,6 @@ type AccessConfigForAliyun struct { type AccessConfigForAWS struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` - Region string `json:"region"` - HostedZoneId string `json:"hostedZoneId"` } type AccessConfigForBaiduCloud struct { @@ -57,7 +67,6 @@ type AccessConfigForGoDaddy struct { type AccessConfigForHuaweiCloud struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` - Region string `json:"region"` } type AccessConfigForLocal struct{} diff --git a/internal/pkg/utils/maps/maps.go b/internal/pkg/utils/maps/maps.go index a7c11592..f0e82efc 100644 --- a/internal/pkg/utils/maps/maps.go +++ b/internal/pkg/utils/maps/maps.go @@ -1,6 +1,10 @@ package maps -import "strconv" +import ( + "strconv" + + mapstructure "github.com/go-viper/mapstructure/v2" +) // 以字符串形式从字典中获取指定键的值。 // @@ -178,3 +182,27 @@ func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool) return defaultValue } + +// 将字典解码为指定类型的结构体。 +// +// 入参: +// - dict: 字典。 +// - output: 结构体指针。 +// +// 出参: +// - 错误信息。如果解码失败,则返回错误信息。 +func Decode(dict map[string]any, output any) error { + config := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + TagName: "json", + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(dict) +} diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index d5bdf7ad..1c1e99c8 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -130,20 +130,22 @@ const AccessForm = forwardRef(({ className, }; const handleFormChange = (_: unknown, values: AccessFormFieldValues) => { - if (values.provider !== fieldProvider) { - formInst.setFieldValue("provider", values.provider); - } - onValuesChange?.(values); }; useImperativeHandle(ref, () => { return { getFieldsValue: () => { - return formInst.getFieldsValue(true); + const values = formInst.getFieldsValue(true); + values.config = nestedFormInst.getFieldsValue(); + return values; }, resetFields: (fields) => { - return formInst.resetFields(fields); + formInst.resetFields(fields); + + if (!!fields && fields.includes("config")) { + nestedFormInst.resetFields(fields); + } }, validateFields: (nameList, config) => { const t1 = formInst.validateFields(nameList, config); diff --git a/ui/src/components/access/AccessFormAWSConfig.tsx b/ui/src/components/access/AccessFormAWSConfig.tsx index 5ca88099..1eee6247 100644 --- a/ui/src/components/access/AccessFormAWSConfig.tsx +++ b/ui/src/components/access/AccessFormAWSConfig.tsx @@ -19,8 +19,6 @@ const initFormModel = (): AccessFormAWSConfigFieldValues => { return { accessKeyId: "", secretAccessKey: "", - region: "us-east-1", - hostedZoneId: "", }; }; @@ -38,18 +36,6 @@ const AccessFormAWSConfig = ({ form: formInst, formName, disabled, initialValues .min(1, t("access.form.aws_secret_access_key.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), - // TODO: 该字段仅用于申请证书,后续迁移到工作流表单中 - region: z - .string() - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim() - .nullish(), - // TODO: 该字段仅用于申请证书,后续迁移到工作流表单中 - hostedZoneId: z - .string() - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim() - .nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -83,24 +69,6 @@ const AccessFormAWSConfig = ({ form: formInst, formName, disabled, initialValues > - - } - > - - - - } - > - - ); }; diff --git a/ui/src/components/access/AccessFormHuaweiCloudConfig.tsx b/ui/src/components/access/AccessFormHuaweiCloudConfig.tsx index d4765e81..f1d56ff0 100644 --- a/ui/src/components/access/AccessFormHuaweiCloudConfig.tsx +++ b/ui/src/components/access/AccessFormHuaweiCloudConfig.tsx @@ -19,7 +19,6 @@ const initFormModel = (): AccessFormHuaweiCloudConfigFieldValues => { return { accessKeyId: "", secretAccessKey: "", - region: "cn-north-1", }; }; @@ -37,12 +36,6 @@ const AccessFormHuaweiCloudConfig = ({ form: formInst, formName, disabled, initi .min(1, t("access.form.huaweicloud_secret_access_key.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), - // TODO: 该字段仅用于申请证书,后续迁移到工作流表单中 - region: z - .string() - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim() - .nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -76,15 +69,6 @@ const AccessFormHuaweiCloudConfig = ({ form: formInst, formName, disabled, initi > - - } - > - - ); }; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index d59e7fd4..672d8621 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -1,4 +1,4 @@ -import { forwardRef, memo, useEffect, useImperativeHandle, useState } from "react"; +import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons"; import { useControllableValue } from "ahooks"; @@ -11,13 +11,16 @@ import MultipleInput from "@/components/MultipleInput"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; import ApplyDNSProviderSelect from "@/components/provider/ApplyDNSProviderSelect"; -import { ACCESS_USAGES, accessProvidersMap, applyDNSProvidersMap } from "@/domain/provider"; +import { ACCESS_PROVIDERS, ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyDNSProvidersMap } from "@/domain/provider"; import { type WorkflowNodeConfigForApply } from "@/domain/workflow"; -import { useAntdForm, useZustandShallowSelector } from "@/hooks"; +import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks"; import { useAccessesStore } from "@/stores/access"; import { useContactEmailsStore } from "@/stores/contact"; import { validDomainName, validIPv4Address, validIPv6Address } from "@/utils/validators"; +import ApplyNodeConfigFormAWSRoute53Config from "./ApplyNodeConfigFormAWSRoute53Config"; +import ApplyNodeConfigFormHuaweiCloudDNSConfig from "./ApplyNodeConfigFormHuaweiCloudDNSConfig"; + type ApplyNodeConfigFormFieldValues = Partial; export type ApplyNodeConfigFormProps = { @@ -61,6 +64,7 @@ const ApplyNodeConfigForm = forwardRef("domains", formInst); const fieldNameservers = Form.useWatch("nameservers", formInst); + const [nestedFormInst] = Form.useForm(); + const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeApplyConfigFormProviderConfigForm" }); + const nestedFormEl = useMemo(() => { + const nestedFormProps = { + form: nestedFormInst, + formName: nestedFormName, + disabled: disabled, + initialValues: initialValues?.providerConfig, + }; + + /* + 注意:如果追加新的子组件,请保持以 ASCII 排序。 + NOTICE: If you add new child component, please keep ASCII order. + */ + switch (fieldProvider) { + case ACCESS_PROVIDERS.AWS: + case APPLY_DNS_PROVIDERS.AWS_ROUTE53: + return ; + case ACCESS_PROVIDERS.HUAWEICLOUD: + case APPLY_DNS_PROVIDERS.HUAWEICLOUD_DNS: + return ; + } + }, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]); + const handleProviderSelect = (value: string) => { if (fieldProvider === value) return; @@ -115,6 +143,13 @@ const ApplyNodeConfigForm = forwardRef { + if (name === nestedFormName) { + formInst.setFieldValue("providerConfig", nestedFormInst.getFieldsValue()); + onValuesChange?.(formInst.getFieldsValue(true)); + } + }; + const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values as ApplyNodeConfigFormFieldValues); }; @@ -122,101 +157,113 @@ const ApplyNodeConfigForm = forwardRef { return { getFieldsValue: () => { - return formInst.getFieldsValue(true); + const values = formInst.getFieldsValue(true); + values.providerConfig = nestedFormInst.getFieldsValue(); + return values; }, resetFields: (fields) => { - return formInst.resetFields(fields); + formInst.resetFields(fields); + + if (!!fields && fields.includes("providerConfig")) { + nestedFormInst.resetFields(fields); + } }, validateFields: (nameList, config) => { - return formInst.validateFields(nameList, config); + const t1 = formInst.validateFields(nameList, config); + const t2 = nestedFormInst.validateFields(undefined, config); + return Promise.all([t1, t2]).then(() => t1); }, } as ApplyNodeConfigFormInstance; }); return ( -
- } - > - - - - - - - - } - onChange={(v) => { - formInst.setFieldValue("domains", v); - }} - /> - - + + + } + > + + + + + + + + } + onChange={(v) => { + formInst.setFieldValue("domains", v); + }} + /> + + - } - > - - + } + > + + - - - - - - { - const provider = accessProvidersMap.get(record.provider); - return ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.APPLY === provider?.usage; - }} - onChange={handleProviderAccessSelect} + - + + + + + { + const provider = accessProvidersMap.get(record.provider); + return ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.APPLY === provider?.usage; + }} + onChange={handleProviderAccessSelect} + /> + + + + + {nestedFormEl} @@ -224,71 +271,73 @@ const ApplyNodeConfigForm = forwardRef - - ({ + label: e, + value: e, + }))} + placeholder={t("workflow_node.apply.form.key_algorithm.placeholder")} + /> + - } - > - - - } + > + + + { + formInst.setFieldValue("nameservers", e.target.value); + }} + /> + + { - formInst.setFieldValue("nameservers", e.target.value); + trigger={ + + } + onChange={(value) => { + formInst.setFieldValue("nameservers", value); }} /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("nameservers", value); - }} + + + + } + > + - - + - } - > - - - - } - > - - - + } + > + + + +
); } ); diff --git a/ui/src/components/workflow/node/ApplyNodeConfigFormAWSRoute53Config.tsx b/ui/src/components/workflow/node/ApplyNodeConfigFormAWSRoute53Config.tsx new file mode 100644 index 00000000..a14a7057 --- /dev/null +++ b/ui/src/components/workflow/node/ApplyNodeConfigFormAWSRoute53Config.tsx @@ -0,0 +1,81 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type ApplyNodeConfigFormAWSRoute53ConfigFieldValues = Nullish<{ + region: string; + hostedZoneId: string; +}>; + +export type ApplyNodeConfigFormAWSRoute53ConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: ApplyNodeConfigFormAWSRoute53ConfigFieldValues; + onValuesChange?: (values: ApplyNodeConfigFormAWSRoute53ConfigFieldValues) => void; +}; + +const initFormModel = (): ApplyNodeConfigFormAWSRoute53ConfigFieldValues => { + return { + region: "us-east-1", + hostedZoneId: "", + }; +}; + +const ApplyNodeConfigFormAWSRoute53Config = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: ApplyNodeConfigFormAWSRoute53ConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.apply.form.aws_route53_region.placeholder") }) + .nonempty(t("workflow_node.apply.form.aws_route53_region.placeholder")) + .trim(), + hostedZoneId: z + .string({ message: t("workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder") }) + .nonempty(t("workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default ApplyNodeConfigFormAWSRoute53Config; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigFormHuaweiCloudDNSConfig.tsx b/ui/src/components/workflow/node/ApplyNodeConfigFormHuaweiCloudDNSConfig.tsx new file mode 100644 index 00000000..dcde7c75 --- /dev/null +++ b/ui/src/components/workflow/node/ApplyNodeConfigFormHuaweiCloudDNSConfig.tsx @@ -0,0 +1,66 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type ApplyNodeConfigFormHuaweiCloudDNSConfigFieldValues = Nullish<{ + region: string; +}>; + +export type ApplyNodeConfigFormHuaweiCloudDNSConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: ApplyNodeConfigFormHuaweiCloudDNSConfigFieldValues; + onValuesChange?: (values: ApplyNodeConfigFormHuaweiCloudDNSConfigFieldValues) => void; +}; + +const initFormModel = (): ApplyNodeConfigFormHuaweiCloudDNSConfigFieldValues => { + return { + region: "cn-north-1", + }; +}; + +const ApplyNodeConfigFormHuaweiCloudDNSConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: ApplyNodeConfigFormHuaweiCloudDNSConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.apply.form.huaweicloud_dns_region.placeholder") }) + .nonempty(t("workflow_node.apply.form.huaweicloud_dns_region.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default ApplyNodeConfigFormHuaweiCloudDNSConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 798d6a7f..272e5f18 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -80,7 +80,8 @@ const DeployNodeConfigForm = forwardRef !!formInst.getFieldValue("provider"), t("workflow_node.deploy.form.provider.placeholder")), providerConfig: z.any(), }); const formRule = createSchemaFieldRule(formSchema); @@ -200,10 +201,16 @@ const DeployNodeConfigForm = forwardRef { return { getFieldsValue: () => { - return formInst.getFieldsValue(true); + const values = formInst.getFieldsValue(true); + values.providerConfig = nestedFormInst.getFieldsValue(); + return values; }, resetFields: (fields) => { - return formInst.resetFields(fields); + formInst.resetFields(fields); + + if (!!fields && fields.includes("providerConfig")) { + nestedFormInst.resetFields(fields); + } }, validateFields: (nameList, config) => { const t1 = formInst.validateFields(nameList, config); @@ -297,16 +304,18 @@ const DeployNodeConfigForm = forwardRef - - - - {t("workflow_node.deploy.form.params_config.label")} - - - {nestedFormEl} + + + + {t("workflow_node.deploy.form.params_config.label")} + + + + {nestedFormEl} + ); } diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 01d7b8b2..c86078ac 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -105,6 +105,7 @@ export type WorkflowNodeConfigForApply = { contactEmail: string; provider: string; providerAccessId: string; + providerConfig?: Record; keyAlgorithm: string; nameservers?: string; propagationTimeout?: number; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 0da23efb..94ca5979 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -45,12 +45,6 @@ "access.form.aws_secret_access_key.label": "AWS SecretAccessKey", "access.form.aws_secret_access_key.placeholder": "Please enter AWS SecretAccessKey", "access.form.aws_secret_access_key.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html", - "access.form.aws_region.label": "AWS Region", - "access.form.aws_region.placeholder": "Please enter AWS region (e.g. us-east-1)", - "access.form.aws_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", - "access.form.aws_hosted_zone_id.label": "AWS hosted zone ID", - "access.form.aws_hosted_zone_id.placeholder": "Please enter AWS hosted zone ID", - "access.form.aws_hosted_zone_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/Route53/latest/DeveloperGuide/hosted-zones-working-with.html", "access.form.baiducloud_access_key_id.label": "Baidu Cloud AccessKeyID", "access.form.baiducloud_access_key_id.placeholder": "Please enter Baidu Cloud AccessKeyID", "access.form.baiducloud_access_key_id.tooltip": "For more information, see https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en", @@ -84,9 +78,6 @@ "access.form.huaweicloud_secret_access_key.label": "Huawei Cloud SecretAccessKey", "access.form.huaweicloud_secret_access_key.placeholder": "Please enter Huawei Cloud SecretAccessKey", "access.form.huaweicloud_secret_access_key.tooltip": "For more information, see https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html", - "access.form.huaweicloud_region.label": "Huawei Cloud region", - "access.form.huaweicloud_region.placeholder": "Please enter Huawei Cloud region (e.g. cn-north-1)", - "access.form.huaweicloud_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.upload": "Choose File ...", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index b7de2fbe..2095445e 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -37,6 +37,15 @@ "workflow_node.apply.form.provider_access.placeholder": "Please select an authorization of DNS provider", "workflow_node.apply.form.provider_access.tooltip": "Used to manage DNS records during ACME DNS-01 authentication.", "workflow_node.apply.form.provider_access.button": "Create", + "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 Region", + "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS Route53 region (e.g. us-east-1)", + "workflow_node.apply.form.aws_route53_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 hosted zone ID", + "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "Please enter AWS Route53 hosted zone ID", + "workflow_node.apply.form.aws_route53_hosted_zone_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/Route53/latest/DeveloperGuide/hosted-zones-working-with.html", + "workflow_node.apply.form.huaweicloud_dns_region.label": "Huawei Cloud DNS region", + "workflow_node.apply.form.huaweicloud_dns_region.placeholder": "Please enter Huawei Cloud DNS region (e.g. cn-north-1)", + "workflow_node.apply.form.huaweicloud_dns_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.apply.form.advanced_config.label": "Advanced settings", "workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm", "workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 37645e5b..b908f33e 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -45,12 +45,6 @@ "access.form.aws_secret_access_key.label": "AWS SecretAccessKey", "access.form.aws_secret_access_key.placeholder": "请输入 AWS SecretAccessKey", "access.form.aws_secret_access_key.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html", - "access.form.aws_region.label": "AWS 区域", - "access.form.aws_region.placeholder": "请输入 AWS 区域(例如:us-east-1)", - "access.form.aws_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", - "access.form.aws_hosted_zone_id.label": "AWS 托管区域 ID", - "access.form.aws_hosted_zone_id.placeholder": "请输入 AWS 托管区域 ID", - "access.form.aws_hosted_zone_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/Route53/latest/DeveloperGuide/hosted-zones-working-with.html", "access.form.baiducloud_access_key_id.label": "百度智能云 AccessKeyID", "access.form.baiducloud_access_key_id.placeholder": "请输入百度智能云 AccessKeyID", "access.form.baiducloud_access_key_id.tooltip": "这是什么?请参阅 https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p", @@ -84,9 +78,6 @@ "access.form.huaweicloud_secret_access_key.label": "华为云 SecretAccessKey", "access.form.huaweicloud_secret_access_key.placeholder": "请输入华为云 SecretAccessKey", "access.form.huaweicloud_secret_access_key.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html", - "access.form.huaweicloud_region.label": "华为云区域", - "access.form.huaweicloud_region.placeholder": "请输入华为云区域(例如:cn-north-1)", - "access.form.huaweicloud_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件", "access.form.k8s_kubeconfig.upload": "选择文件", diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index fe2644cb..87482988 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.json +++ b/ui/src/i18n/locales/zh/nls.workflow.json @@ -9,7 +9,7 @@ "workflow.action.delete.confirm": "确定要删除此工作流吗?", "workflow.action.enable": "启用", "workflow.action.enable.failed.uncompleted": "请先完成流程编排并发布更改", - "workflow.action.disable": "禁用", + "workflow.action.disable": "停用", "workflow.props.name": "名称", "workflow.props.description": "描述", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 83739848..6ede2b58 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -37,6 +37,15 @@ "workflow_node.apply.form.provider_access.placeholder": "请选择 DNS 提供商授权", "workflow_node.apply.form.provider_access.tooltip": "用于 ACME DNS-01 认证时操作域名解析记录,注意与部署阶段所需的主机提供商相区分。", "workflow_node.apply.form.provider_access.button": "新建", + "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 区域", + "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS Route53 区域(例如:us-east-1)", + "workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 托管区域 ID", + "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "请输入 AWS Route53 托管区域 ID", + "workflow_node.apply.form.aws_route53_hosted_zone_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/Route53/latest/DeveloperGuide/hosted-zones-working-with.html", + "workflow_node.apply.form.huaweicloud_dns_region.label": "华为云 DNS 服务区域", + "workflow_node.apply.form.huaweicloud_dns_region.placeholder": "请输入华为云 DNS 服务区域(例如:cn-north-1)", + "workflow_node.apply.form.huaweicloud_dns_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.apply.form.advanced_config.label": "高级设置", "workflow_node.apply.form.key_algorithm.label": "数字证书算法", "workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法",