diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 5496e213..d1d72408 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -962,8 +962,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServerConfig)) - for i, jumpServer := range access.JumpServerConfig { + jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServers)) + for i, jumpServer := range access.JumpServers { jumpServers[i] = pSSH.JumpServerConfig{ SshHost: jumpServer.Host, SshPort: jumpServer.Port, @@ -981,19 +981,19 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - JumpServerConfig: jumpServers, - UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + JumpServers: jumpServers, + UseSCP: maputil.GetBool(options.ProviderServiceConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } diff --git a/internal/domain/access.go b/internal/domain/access.go index 454ae954..13975392 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -284,20 +284,20 @@ type AccessConfigForSafeLine struct { } type AccessConfigForSSH struct { - Host string `json:"host"` - Port int32 `json:"port"` - Username string `json:"username"` - Password string `json:"password,omitempty"` - Key string `json:"key,omitempty"` - KeyPassphrase string `json:"keyPassphrase,omitempty"` - JumpServerConfig []struct { + Host string `json:"host"` + Port int32 `json:"port"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Key string `json:"key,omitempty"` + KeyPassphrase string `json:"keyPassphrase,omitempty"` + JumpServers []struct { Host string `json:"host"` Port int32 `json:"port"` Username string `json:"username"` Password string `json:"password,omitempty"` Key string `json:"key,omitempty"` KeyPassphrase string `json:"keyPassphrase,omitempty"` - } `json:"jumpServerConfig,omitempty"` + } `json:"jumpServers,omitempty"` } type AccessConfigForSSLCom struct { diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 6f3cccea..6a96dd81 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -105,11 +105,6 @@ type WorkflowNodeConfigForNotify struct { } func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { - skipBeforeExpiryDays := maputil.GetInt32(n.Config, "skipBeforeExpiryDays") - if skipBeforeExpiryDays == 0 { - skipBeforeExpiryDays = 30 - } - return WorkflowNodeConfigForApply{ Domains: maputil.GetString(n.Config, "domains"), ContactEmail: maputil.GetString(n.Config, "contactEmail"), @@ -126,7 +121,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"), DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"), DisableARI: maputil.GetBool(n.Config, "disableARI"), - SkipBeforeExpiryDays: skipBeforeExpiryDays, + SkipBeforeExpiryDays: maputil.GetOrDefaultInt32(n.Config, "skipBeforeExpiryDays", 30), } } diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go index bf7026da..56deaa2d 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go @@ -24,6 +24,7 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, providerConfig := internal.NewDefaultConfig() providerConfig.SecretID = config.AccessKeyId providerConfig.SecretKey = config.AccessKeySecret + providerConfig.RegionID = config.Region if config.DnsPropagationTimeout != 0 { providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second } diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 2a67b441..ae6e459f 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -49,8 +49,8 @@ type DeployerConfig struct { SshKey string `json:"sshKey,omitempty"` // SSH 登录私钥口令。 SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` - // 跳板机配置 - JumpServerConfig []JumpServerConfig `json:"jumpServerConfig,omitempty"` + // 跳板机配置数组。 + JumpServers []JumpServerConfig `json:"jumpServers,omitempty"` // 是否回退使用 SCP。 UseSCP bool `json:"useSCP,omitempty"` // 前置命令。 @@ -120,9 +120,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var targetConn net.Conn // 连接到跳板机 - if len(d.config.JumpServerConfig) > 0 { + if len(d.config.JumpServers) > 0 { var jumpClient *ssh.Client - for i, jumpServerConf := range d.config.JumpServerConfig { + for i, jumpServerConf := range d.config.JumpServers { d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) var jumpConn net.Conn @@ -154,13 +154,14 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE jumpClient = newClient d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) } - // 通过跳板机发起到目标服务器的TCP连接 + + // 通过跳板机发起 TCP 连接到目标服务器 targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) if err != nil { return nil, fmt.Errorf("failed to connect to target server: %w", err) } } else { - // 直接TCP连接到目标服务器 + // 直接发起 TCP 连接到目标服务器 targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) if err != nil { return nil, fmt.Errorf("failed to connect to target server: %w", err) @@ -168,7 +169,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } defer targetConn.Close() - // 通过已有的连接创建目标服务器SSH客户端 + // 通过已有的连接创建目标服务器 SSH 客户端 client, err := createSshClient( targetConn, d.config.SshHost, diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go index 01f71d9e..a92e4eb1 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -2,9 +2,11 @@ package tencentcloudcdn import ( "context" + "errors" "fmt" "log/slog" "strings" + "time" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -132,6 +134,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index 0c2f8902..2f7c0f22 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "time" tcclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -151,6 +152,49 @@ func (d *DeployerProvider) deployViaSslService(ctx context.Context, cloudCertId return fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } + return nil } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go index 0949c5a3..99ee9b2f 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "time" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -102,6 +103,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } + return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go index ebe87025..88840f4a 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -2,9 +2,11 @@ package tencentcloudecdn import ( "context" + "errors" "fmt" "log/slog" "strings" + "time" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -103,7 +105,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } else { d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds)) - // 证书部署到 ECDN 实例 + // 证书部署到 CDN 实例 // REF: https://cloud.tencent.com/document/product/400/91667 deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest() deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) @@ -115,6 +117,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index 5f13660d..585000d9 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -116,30 +116,35 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) - describeHostDeployRecordDetailReq.Limit = common.Uint64Ptr(100) describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) } + var runningCount, succeededCount, failedCount, totalCount int64 if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected deployment job status") } else { - acc := int64(0) + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - acc += *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount } if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - acc += *describeHostDeployRecordDetailResp.Response.FailedTotalCount + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount } - if acc == *describeHostDeployRecordDetailResp.Response.TotalCount { + if succeededCount+failedCount == totalCount { break } } - d.logger.Info("waiting for deployment job completion ...") + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) time.Sleep(time.Second * 5) } diff --git a/ui/src/components/access/AccessFormSSHConfig.tsx b/ui/src/components/access/AccessFormSSHConfig.tsx index cdd3c8e2..32d1b8bc 100644 --- a/ui/src/components/access/AccessFormSSHConfig.tsx +++ b/ui/src/components/access/AccessFormSSHConfig.tsx @@ -1,4 +1,5 @@ import { useTranslation } from "react-i18next"; +import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons"; import { Button, Collapse, Form, type FormInstance, Input, InputNumber, Space } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -6,7 +7,6 @@ import { z } from "zod"; import TextFileInput from "@/components/TextFileInput"; import { type AccessConfigForSSH } from "@/domain/access"; import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators"; -import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons"; type AccessFormSSHConfigFieldValues = Nullish; @@ -29,42 +29,6 @@ const initFormModel = (): AccessFormSSHConfigFieldValues => { const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSSHConfigProps) => { const { t } = useTranslation(); - const jumpServerConfigItemSchema = z - .object({ - host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), - port: z.preprocess( - (v) => Number(v), - z - .number() - .int(t("access.form.ssh_port.placeholder")) - .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) - ), - username: z - .string() - .min(1, t("access.form.ssh_username.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })), - password: z - .string() - .max(64, t("common.errmsg.string_max", { max: 64 })) - .nullish(), - key: z - .string() - .max(20480, t("common.errmsg.string_max", { max: 20480 })) - .nullish(), - keyPassphrase: z - .string() - .max(20480, t("common.errmsg.string_max", { max: 20480 })) - .nullish(), - }) - .superRefine((data, ctx) => { - if (data.keyPassphrase && !data.key) { - ctx.addIssue({ - path: ["keyPassphrase"], - code: z.ZodIssueCode.custom, - message: t("access.form.ssh_key.placeholder"), - }); - } - }); const formSchema = z.object({ host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), port: z.preprocess( @@ -91,7 +55,46 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues .max(20480, t("common.errmsg.string_max", { max: 20480 })) .nullish() .refine((v) => !v || formInst.getFieldValue("key"), t("access.form.ssh_key.placeholder")), - jumpServerConfig: jumpServerConfigItemSchema.array().nullish(), + jumpServers: z + .array( + z + .object({ + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + port: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("access.form.ssh_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + username: z + .string() + .min(1, t("access.form.ssh_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + password: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .nullish(), + key: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + keyPassphrase: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + }) + .superRefine((data, ctx) => { + if (data.keyPassphrase && !data.key) { + ctx.addIssue({ + path: ["keyPassphrase"], + code: z.ZodIssueCode.custom, + message: t("access.form.ssh_key.placeholder"), + }); + } + }) + ) + .nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -153,49 +156,63 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues - } - > - + + {(fields, { add, remove, move }) => ( {fields?.length > 0 ? ( { const Label = () => { - const itemHost = Form.useWatch(["jumpServerConfig", field.name, "host"], formInst); + const host = Form.useWatch(["jumpServers", field.name, "host"], formInst); + const port = Form.useWatch(["jumpServers", field.name, "port"], formInst); + const addr = !!host && !!port ? `${host}:${port}` : host ? host : port ? `:${port}` : "unknown"; return ( - - [{t("access.form.ssh_jump_server_config.item.label")} {field.name + 1}] {itemHost ?? ""} + + [{t("access.form.ssh_jump_servers.item.label")} {field.name + 1}] {addr} ); }; return { key: field.key, - label: ); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx index b4e4092e..a40142ee 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx @@ -37,7 +37,7 @@ const NotifyNodeConfigFormTelegramBotConfig = ({ .nullish() .refine((v) => { if (v == null || v + "" === "") return true; - return /^\d+$/.test(v + "") && +v! > 0; + return !Number.isNaN(+v!) && +v! !== 0; }, t("workflow_node.notify.form.telegram_bot_chat_id.placeholder")) ) .nullish(), @@ -63,7 +63,7 @@ const NotifyNodeConfigFormTelegramBotConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 2407cfb2..41b81636 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -392,10 +392,9 @@ "access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)", "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase", "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.", - "access.form.ssh_jump_server_config.label": "SSH jump server (Optional)", - "access.form.ssh_jump_server_config.tooltip": "Optional when using a jump server to connect to the server.", - "access.form.ssh_jump_server_config.item.label": "Jump Server", - "access.form.ssh_jump_server_config.add": "Add Jump Server", + "access.form.ssh_jump_servers.label": "SSH jump server (Optional)", + "access.form.ssh_jump_servers.item.label": "Jump server", + "access.form.ssh_jump_servers.add": "Add jump server", "access.form.sslcom_eab_kid.label": "ACME EAB KID", "access.form.sslcom_eab_kid.placeholder": "Please enter ACME EAB KID", "access.form.sslcom_eab_kid.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index bedf3d74..a5e89438 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -386,10 +386,9 @@ "access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", - "access.form.ssh_jump_server_config.label": "SSH 跳板机(可选)", - "access.form.ssh_jump_server_config.tooltip": "使用跳板机连接到服务器时选填。", - "access.form.ssh_jump_server_config.item.label": "跳板机", - "access.form.ssh_jump_server_config.add": "添加跳板机", + "access.form.ssh_jump_servers.label": "SSH 跳板机(可选)", + "access.form.ssh_jump_servers.item.label": "跳板机", + "access.form.ssh_jump_servers.add": "添加跳板机", "access.form.sslcom_eab_kid.label": "ACME EAB KID", "access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/",