Merge pull request #714 from fudiwei/bugfix

bugfix
This commit is contained in:
RHQYZ 2025-05-20 23:14:06 +08:00 committed by GitHub
commit 7643975ef9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 307 additions and 109 deletions

View File

@ -962,8 +962,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
return nil, fmt.Errorf("failed to populate provider access config: %w", err) return nil, fmt.Errorf("failed to populate provider access config: %w", err)
} }
jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServerConfig)) jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServers))
for i, jumpServer := range access.JumpServerConfig { for i, jumpServer := range access.JumpServers {
jumpServers[i] = pSSH.JumpServerConfig{ jumpServers[i] = pSSH.JumpServerConfig{
SshHost: jumpServer.Host, SshHost: jumpServer.Host,
SshPort: jumpServer.Port, SshPort: jumpServer.Port,
@ -981,19 +981,19 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
SshPassword: access.Password, SshPassword: access.Password,
SshKey: access.Key, SshKey: access.Key,
SshKeyPassphrase: access.KeyPassphrase, SshKeyPassphrase: access.KeyPassphrase,
JumpServerConfig: jumpServers, JumpServers: jumpServers,
UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), UseSCP: maputil.GetBool(options.ProviderServiceConfig, "useSCP"),
PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"),
PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"),
OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))),
OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"),
OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"),
OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"),
OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"),
PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"),
JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"),
JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"),
JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"),
}) })
return deployer, err return deployer, err
} }

View File

@ -284,20 +284,20 @@ type AccessConfigForSafeLine struct {
} }
type AccessConfigForSSH struct { type AccessConfigForSSH struct {
Host string `json:"host"` Host string `json:"host"`
Port int32 `json:"port"` Port int32 `json:"port"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"` KeyPassphrase string `json:"keyPassphrase,omitempty"`
JumpServerConfig []struct { JumpServers []struct {
Host string `json:"host"` Host string `json:"host"`
Port int32 `json:"port"` Port int32 `json:"port"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"` KeyPassphrase string `json:"keyPassphrase,omitempty"`
} `json:"jumpServerConfig,omitempty"` } `json:"jumpServers,omitempty"`
} }
type AccessConfigForSSLCom struct { type AccessConfigForSSLCom struct {

View File

@ -105,11 +105,6 @@ type WorkflowNodeConfigForNotify struct {
} }
func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
skipBeforeExpiryDays := maputil.GetInt32(n.Config, "skipBeforeExpiryDays")
if skipBeforeExpiryDays == 0 {
skipBeforeExpiryDays = 30
}
return WorkflowNodeConfigForApply{ return WorkflowNodeConfigForApply{
Domains: maputil.GetString(n.Config, "domains"), Domains: maputil.GetString(n.Config, "domains"),
ContactEmail: maputil.GetString(n.Config, "contactEmail"), ContactEmail: maputil.GetString(n.Config, "contactEmail"),
@ -126,7 +121,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"), DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"),
DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"), DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"),
DisableARI: maputil.GetBool(n.Config, "disableARI"), DisableARI: maputil.GetBool(n.Config, "disableARI"),
SkipBeforeExpiryDays: skipBeforeExpiryDays, SkipBeforeExpiryDays: maputil.GetOrDefaultInt32(n.Config, "skipBeforeExpiryDays", 30),
} }
} }

View File

@ -24,6 +24,7 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider,
providerConfig := internal.NewDefaultConfig() providerConfig := internal.NewDefaultConfig()
providerConfig.SecretID = config.AccessKeyId providerConfig.SecretID = config.AccessKeyId
providerConfig.SecretKey = config.AccessKeySecret providerConfig.SecretKey = config.AccessKeySecret
providerConfig.RegionID = config.Region
if config.DnsPropagationTimeout != 0 { if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
} }

View File

@ -49,8 +49,8 @@ type DeployerConfig struct {
SshKey string `json:"sshKey,omitempty"` SshKey string `json:"sshKey,omitempty"`
// SSH 登录私钥口令。 // SSH 登录私钥口令。
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
// 跳板机配置 // 跳板机配置数组。
JumpServerConfig []JumpServerConfig `json:"jumpServerConfig,omitempty"` JumpServers []JumpServerConfig `json:"jumpServers,omitempty"`
// 是否回退使用 SCP。 // 是否回退使用 SCP。
UseSCP bool `json:"useSCP,omitempty"` UseSCP bool `json:"useSCP,omitempty"`
// 前置命令。 // 前置命令。
@ -120,9 +120,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
var targetConn net.Conn var targetConn net.Conn
// 连接到跳板机 // 连接到跳板机
if len(d.config.JumpServerConfig) > 0 { if len(d.config.JumpServers) > 0 {
var jumpClient *ssh.Client 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)) d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost))
var jumpConn net.Conn var jumpConn net.Conn
@ -154,13 +154,14 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
jumpClient = newClient jumpClient = newClient
d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) 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)) targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to target server: %w", err) return nil, fmt.Errorf("failed to connect to target server: %w", err)
} }
} else { } else {
// 直接TCP连接到目标服务器 // 直接发起 TCP 连接到目标服务器
targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to target server: %w", err) 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() defer targetConn.Close()
// 通过已有的连接创建目标服务器SSH客户端 // 通过已有的连接创建目标服务器 SSH 客户端
client, err := createSshClient( client, err := createSshClient(
targetConn, targetConn,
d.config.SshHost, d.config.SshHost,

View File

@ -2,9 +2,11 @@ package tencentcloudcdn
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"strings" "strings"
"time"
tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "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 { if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) 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 return &deployer.DeployResult{}, nil

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"time"
tcclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" tcclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "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) 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 return nil
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"time"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" "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) 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 return &deployer.DeployResult{}, nil
} }

View File

@ -2,9 +2,11 @@ package tencentcloudecdn
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"strings" "strings"
"time"
tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
@ -103,7 +105,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
} else { } else {
d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds)) d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds))
// 证书部署到 ECDN 实例 // 证书部署到 CDN 实例
// REF: https://cloud.tencent.com/document/product/400/91667 // REF: https://cloud.tencent.com/document/product/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest() deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
@ -115,6 +117,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) 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 return &deployer.DeployResult{}, nil

View File

@ -116,30 +116,35 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest()
describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId))
describeHostDeployRecordDetailReq.Limit = common.Uint64Ptr(100)
describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq)
d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err)
} }
var runningCount, succeededCount, failedCount, totalCount int64
if describeHostDeployRecordDetailResp.Response.TotalCount == nil { if describeHostDeployRecordDetailResp.Response.TotalCount == nil {
return nil, errors.New("unexpected deployment job status") return nil, errors.New("unexpected deployment job status")
} else { } else {
acc := int64(0) if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil {
runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount
}
if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil {
acc += *describeHostDeployRecordDetailResp.Response.SuccessTotalCount succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount
} }
if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { 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 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) time.Sleep(time.Second * 5)
} }

View File

@ -1,4 +1,5 @@
import { useTranslation } from "react-i18next"; 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 { Button, Collapse, Form, type FormInstance, Input, InputNumber, Space } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
@ -6,7 +7,6 @@ import { z } from "zod";
import TextFileInput from "@/components/TextFileInput"; import TextFileInput from "@/components/TextFileInput";
import { type AccessConfigForSSH } from "@/domain/access"; import { type AccessConfigForSSH } from "@/domain/access";
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators"; import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons";
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>; type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
@ -29,42 +29,6 @@ const initFormModel = (): AccessFormSSHConfigFieldValues => {
const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSSHConfigProps) => { const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSSHConfigProps) => {
const { t } = useTranslation(); 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({ const formSchema = z.object({
host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")),
port: z.preprocess( port: z.preprocess(
@ -91,7 +55,46 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
.max(20480, t("common.errmsg.string_max", { max: 20480 })) .max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish() .nullish()
.refine((v) => !v || formInst.getFieldValue("key"), t("access.form.ssh_key.placeholder")), .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); const formRule = createSchemaFieldRule(formSchema);
@ -153,49 +156,63 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} /> <Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item name="jumpServers" label={t("access.form.ssh_jump_servers.label")} rules={[formRule]}>
label={t("access.form.ssh_jump_server_config.label")} <Form.List name="jumpServers">
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_jump_server_config.tooltip") }}></span>}
>
<Form.List name="jumpServerConfig">
{(fields, { add, remove, move }) => ( {(fields, { add, remove, move }) => (
<Space className="w-full" direction="vertical" size="small"> <Space className="w-full" direction="vertical" size="small">
{fields?.length > 0 ? ( {fields?.length > 0 ? (
<Collapse <Collapse
items={fields.map((field, index) => { items={fields.map((field, index) => {
const Label = () => { 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 ( return (
<span style={{ userSelect: "none" }}> <span className="select-none">
[{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}
</span> </span>
); );
}; };
return { return {
key: field.key, key: field.key,
label: <Label />, // 这里用组件渲染 label: <Label />,
extra: ( extra: (
<Space> <Space.Compact>
<ArrowUpOutlined <Button
icon={<ArrowUpOutlined />}
color="default"
disabled={disabled || index === 0}
size="small"
type="text"
onClick={(e) => { onClick={(e) => {
move(index, index - 1); move(index, index - 1);
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
<ArrowDownOutlined <Button
icon={<ArrowDownOutlined />}
color="default"
disabled={disabled || index === fields.length - 1}
size="small"
type="text"
onClick={(e) => { onClick={(e) => {
move(index, index + 1); move(index, index + 1);
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
<CloseOutlined <Button
icon={<CloseOutlined />}
color="default"
disabled={disabled}
size="small"
type="text"
onClick={(e) => { onClick={(e) => {
remove(field.name); remove(field.name);
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
</Space> </Space.Compact>
), ),
children: ( children: (
<> <>
@ -211,9 +228,11 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
</Form.Item> </Form.Item>
</div> </div>
</div> </div>
<Form.Item name={[field.name, "username"]} label={t("access.form.ssh_username.label")} rules={[formRule]}> <Form.Item name={[field.name, "username"]} label={t("access.form.ssh_username.label")} rules={[formRule]}>
<Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} /> <Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={[field.name, "password"]} name={[field.name, "password"]}
label={t("access.form.ssh_password.label")} label={t("access.form.ssh_password.label")}
@ -222,6 +241,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
> >
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} /> <Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={[field.name, "key"]} name={[field.name, "key"]}
label={t("access.form.ssh_key.label")} label={t("access.form.ssh_key.label")}
@ -230,6 +250,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
> >
<TextFileInput allowClear autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("access.form.ssh_key.placeholder")} /> <TextFileInput allowClear autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("access.form.ssh_key.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={[field.name, "keyPassphrase"]} name={[field.name, "keyPassphrase"]}
label={t("access.form.ssh_key_passphrase.label")} label={t("access.form.ssh_key_passphrase.label")}
@ -245,7 +266,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
/> />
) : null} ) : null}
<Button type="dashed" className="w-full" icon={<PlusOutlined />} onClick={() => add()}> <Button type="dashed" className="w-full" icon={<PlusOutlined />} onClick={() => add()}>
{t("access.form.ssh_jump_server_config.add")} {t("access.form.ssh_jump_servers.add")}
</Button> </Button>
</Space> </Space>
)} )}
@ -256,4 +277,3 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
}; };
export default AccessFormSSHConfig; export default AccessFormSSHConfig;

View File

@ -37,7 +37,7 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi
.nullish() .nullish()
.refine((v) => { .refine((v) => {
if (v == null || v + "" === "") return true; if (v == null || v + "" === "") return true;
return /^\d+$/.test(v + "") && +v! > 0; return !Number.isNaN(+v!) && +v! !== 0;
}, t("access.form.telegram_bot_default_chat_id.placeholder")) }, t("access.form.telegram_bot_default_chat_id.placeholder"))
) )
.nullish(), .nullish(),
@ -72,7 +72,7 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_bot_default_chat_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_bot_default_chat_id.tooltip") }}></span>}
> >
<Input type="number" allowClear placeholder={t("access.form.telegram_bot_default_chat_id.placeholder")} /> <Input allowClear placeholder={t("access.form.telegram_bot_default_chat_id.placeholder")} />
</Form.Item> </Form.Item>
</Form> </Form>
); );

View File

@ -37,7 +37,7 @@ const NotifyNodeConfigFormTelegramBotConfig = ({
.nullish() .nullish()
.refine((v) => { .refine((v) => {
if (v == null || v + "" === "") return true; 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")) }, t("workflow_node.notify.form.telegram_bot_chat_id.placeholder"))
) )
.nullish(), .nullish(),
@ -63,7 +63,7 @@ const NotifyNodeConfigFormTelegramBotConfig = ({
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.telegram_bot_chat_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.telegram_bot_chat_id.tooltip") }}></span>}
> >
<Input type="number" allowClear placeholder={t("workflow_node.notify.form.telegram_bot_chat_id.placeholder")} /> <Input allowClear placeholder={t("workflow_node.notify.form.telegram_bot_chat_id.placeholder")} />
</Form.Item> </Form.Item>
</Form> </Form>
); );

View File

@ -392,10 +392,9 @@
"access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)", "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.placeholder": "Please enter SSH key passphrase",
"access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.", "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_servers.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_servers.item.label": "Jump server",
"access.form.ssh_jump_server_config.item.label": "Jump Server", "access.form.ssh_jump_servers.add": "Add jump server",
"access.form.ssh_jump_server_config.add": "Add Jump Server",
"access.form.sslcom_eab_kid.label": "ACME EAB KID", "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.placeholder": "Please enter ACME EAB KID",
"access.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>", "access.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",

View File

@ -386,10 +386,9 @@
"access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)", "access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)",
"access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
"access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。",
"access.form.ssh_jump_server_config.label": "SSH 跳板机(可选)", "access.form.ssh_jump_servers.label": "SSH 跳板机(可选)",
"access.form.ssh_jump_server_config.tooltip": "使用跳板机连接到服务器时选填。", "access.form.ssh_jump_servers.item.label": "跳板机",
"access.form.ssh_jump_server_config.item.label": "跳板机", "access.form.ssh_jump_servers.add": "添加跳板机",
"access.form.ssh_jump_server_config.add": "添加跳板机",
"access.form.sslcom_eab_kid.label": "ACME EAB KID", "access.form.sslcom_eab_kid.label": "ACME EAB KID",
"access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID",
"access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>", "access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",