mirror of https://github.com/usual2970/certimate
commit
c41f34c352
7
go.mod
7
go.mod
|
|
@ -12,7 +12,6 @@ require (
|
||||||
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
|
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
|
||||||
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
||||||
github.com/alibabacloud-go/tea v1.2.2
|
github.com/alibabacloud-go/tea v1.2.2
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/go-acme/lego/v4 v4.19.2
|
github.com/go-acme/lego/v4 v4.19.2
|
||||||
github.com/gojek/heimdall/v7 v7.0.3
|
github.com/gojek/heimdall/v7 v7.0.3
|
||||||
|
|
@ -25,7 +24,8 @@ require (
|
||||||
github.com/pocketbase/pocketbase v0.22.18
|
github.com/pocketbase/pocketbase v0.22.18
|
||||||
github.com/qiniu/go-sdk/v7 v7.22.0
|
github.com/qiniu/go-sdk/v7 v7.22.0
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
|
|
@ -41,6 +41,7 @@ require (
|
||||||
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
||||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
||||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
|
|
@ -141,7 +142,7 @@ require (
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/cobra v1.8.1 // indirect
|
github.com/spf13/cobra v1.8.1 // indirect
|
||||||
|
|
|
||||||
7
go.sum
7
go.sum
|
|
@ -204,8 +204,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
||||||
|
|
@ -460,11 +458,14 @@ github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQ
|
||||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017 h1:OymmfmyFkvHirY3WHsoRT3cdTEsqygLbMn8jM41erK4=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017 h1:OymmfmyFkvHirY3WHsoRT3cdTEsqygLbMn8jM41erK4=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017/go.mod h1:gnLxGXlLmF+jDqWR1/RVoF/UUwxQxomQhkc0oN7KeuI=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017/go.mod h1:gnLxGXlLmF+jDqWR1/RVoF/UUwxQxomQhkc0oN7KeuI=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031 h1:/eVMCl+jadCex6HxNN6/hFbC0iWl+e8s4PSIcI8aqS4=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031/go.mod h1:8Km0fRIaDS7PssuyxDFvRRFBUFmECqG+ICpViCs/Vak=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030 h1:kwiUoCkooUgy7iPyhEEbio7WT21kGJUeZ5JeJfb/dYk=
|
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031 h1:3ouglYKE5cwhx2vwICGeW7pAlwyCLnpQd7O0l3hCSTg=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 h1:QwE0dRkAAbdf+eACnkNULgDn9ZKUJpPWRyXdqJolP5E=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 h1:QwE0dRkAAbdf+eACnkNULgDn9ZKUJpPWRyXdqJolP5E=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002/go.mod h1:WdC0FYbqYhJwQ3kbqri6hVP5HAEp+rzX9FToItTAzUg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002/go.mod h1:WdC0FYbqYhJwQ3kbqri6hVP5HAEp+rzX9FToItTAzUg=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992 h1:A6O89OlCJQUpNxGqC/E5By04UNKBryIt5olQIGOx8mg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992 h1:A6O89OlCJQUpNxGqC/E5By04UNKBryIt5olQIGOx8mg=
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,29 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
alb20200616 "github.com/alibabacloud-go/alb-20200616/v2/client"
|
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunALBDeployer struct {
|
type AliyunALBDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
sdkClient *alb20200616.Client
|
sdkClient *aliyunAlb.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := (&AliyunALBDeployer{}).createSdkClient(
|
client, err := (&AliyunALBDeployer{}).createSdkClient(
|
||||||
access.AccessKeyId,
|
access.AccessKeyId,
|
||||||
|
|
@ -32,16 +36,16 @@ func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploader, err := uploader.NewAliyunCASUploader(&uploader.AliyunCASUploaderConfig{
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunALBDeployer{
|
return &AliyunALBDeployer{
|
||||||
|
|
@ -56,7 +60,7 @@ func (d *AliyunALBDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) GetInfo() []string {
|
func (d *AliyunALBDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,12 +81,12 @@ func (d *AliyunALBDeployer) Deploy(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*alb20200616.Client, error) {
|
func (d *AliyunALBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-hangzhou" // ALB 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // ALB 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +100,7 @@ func (d *AliyunALBDeployer) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
client, err := alb20200616.NewClient(aConfig)
|
client, err := aliyunAlb.NewClient(aConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -114,12 +118,12 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
|
||||||
// 查询负载均衡实例的详细信息
|
// 查询负载均衡实例的详细信息
|
||||||
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute
|
||||||
getLoadBalancerAttributeReq := &alb20200616.GetLoadBalancerAttributeRequest{
|
getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
}
|
}
|
||||||
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'alb.GetLoadBalancerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp))
|
||||||
|
|
@ -130,7 +134,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
listListenersLimit := int32(100)
|
listListenersLimit := int32(100)
|
||||||
var listListenersToken *string = nil
|
var listListenersToken *string = nil
|
||||||
for {
|
for {
|
||||||
listListenersReq := &alb20200616.ListListenersRequest{
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
MaxResults: tea.Int32(listListenersLimit),
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
NextToken: listListenersToken,
|
NextToken: listListenersToken,
|
||||||
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
|
@ -138,7 +142,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listListenersResp.Body.Listeners != nil {
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
|
@ -162,7 +166,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
listListenersPage = 1
|
listListenersPage = 1
|
||||||
listListenersToken = nil
|
listListenersToken = nil
|
||||||
for {
|
for {
|
||||||
listListenersReq := &alb20200616.ListListenersRequest{
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
MaxResults: tea.Int32(listListenersLimit),
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
NextToken: listListenersToken,
|
NextToken: listListenersToken,
|
||||||
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
|
@ -170,7 +174,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listListenersResp.Body.Listeners != nil {
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
|
@ -190,17 +194,17 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", aliListenerIds))
|
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", aliListenerIds))
|
||||||
|
|
||||||
// 上传证书到 SSL
|
// 上传证书到 SSL
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 批量更新监听证书
|
// 批量更新监听证书
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, aliListenerId := range aliListenerIds {
|
for _, aliListenerId := range aliListenerIds {
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -218,15 +222,15 @@ func (d *AliyunALBDeployer) deployToListener(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书到 SSL
|
// 上传证书到 SSL
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 更新监听
|
// 更新监听
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,27 +240,27 @@ func (d *AliyunALBDeployer) deployToListener(ctx context.Context) error {
|
||||||
func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
||||||
// 查询监听的属性
|
// 查询监听的属性
|
||||||
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute
|
||||||
getListenerAttributeReq := &alb20200616.GetListenerAttributeRequest{
|
getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{
|
||||||
ListenerId: tea.String(aliListenerId),
|
ListenerId: tea.String(aliListenerId),
|
||||||
}
|
}
|
||||||
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'alb.GetListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ALB 监听配置", getListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 ALB 监听配置", getListenerAttributeResp))
|
||||||
|
|
||||||
// 修改监听的属性
|
// 修改监听的属性
|
||||||
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute
|
||||||
updateListenerAttributeReq := &alb20200616.UpdateListenerAttributeRequest{
|
updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{
|
||||||
ListenerId: tea.String(aliListenerId),
|
ListenerId: tea.String(aliListenerId),
|
||||||
Certificates: []*alb20200616.UpdateListenerAttributeRequestCertificates{{
|
Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{
|
||||||
CertificateId: tea.String(aliCertId),
|
CertificateId: tea.String(aliCertId),
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'alb.UpdateListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 ALB 监听配置", updateListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已更新 ALB 监听配置", updateListenerAttributeResp))
|
||||||
|
|
|
||||||
|
|
@ -4,39 +4,41 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
cdn20180510 "github.com/alibabacloud-go/cdn-20180510/v5/client"
|
aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunCDNDeployer struct {
|
type AliyunCDNDeployer struct {
|
||||||
client *cdn20180510.Client
|
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunCdn.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunCDNDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
|
func NewAliyunCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
d := &AliyunCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
client, err := (&AliyunCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunCDNDeployer{
|
return &AliyunCDNDeployer{
|
||||||
client: client,
|
|
||||||
option: option,
|
option: option,
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,41 +46,43 @@ func (d *AliyunCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetInfo() []string {
|
func (d *AliyunCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
// 设置 CDN 域名域名证书
|
||||||
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
|
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
|
||||||
DomainName: tea.String(getDeployString(d.option.DeployConfig, "domain")),
|
setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{
|
||||||
CertName: tea.String(certName),
|
DomainName: tea.String(d.option.DeployConfig.GetConfigAsString("domain")),
|
||||||
|
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
CertType: tea.String("upload"),
|
CertType: tea.String("upload"),
|
||||||
SSLProtocol: tea.String("on"),
|
SSLProtocol: tea.String("on"),
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
SSLPub: tea.String(d.option.Certificate.Certificate),
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
||||||
CertRegion: tea.String("cn-hangzhou"),
|
|
||||||
}
|
}
|
||||||
|
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
|
||||||
runtime := &util.RuntimeOptions{}
|
|
||||||
|
|
||||||
resp, err := d.client.SetCdnDomainSSLCertificateWithOptions(setCdnDomainSSLCertificateRequest, runtime)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("cdn设置证书", resp))
|
d.infos = append(d.infos, toStr("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) createClient(accessKeyId, accessKeySecret string) (_result *cdn20180510.Client, _err error) {
|
func (d *AliyunCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) {
|
||||||
config := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("cdn.aliyuncs.com"),
|
||||||
}
|
}
|
||||||
config.Endpoint = tea.String("cdn.aliyuncs.com")
|
|
||||||
_result = &cdn20180510.Client{}
|
client, err := aliyunCdn.NewClient(aConfig)
|
||||||
_result, _err = cdn20180510.NewClient(config)
|
if err != nil {
|
||||||
return _result, _err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,29 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
slb20140515 "github.com/alibabacloud-go/slb-20140515/v4/client"
|
aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunCLBDeployer struct {
|
type AliyunCLBDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
sdkClient *slb20140515.Client
|
sdkClient *aliyunSlb.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunCLBDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunCLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := (&AliyunCLBDeployer{}).createSdkClient(
|
client, err := (&AliyunCLBDeployer{}).createSdkClient(
|
||||||
access.AccessKeyId,
|
access.AccessKeyId,
|
||||||
|
|
@ -32,16 +36,16 @@ func NewAliyunCLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploader, err := uploader.NewAliyunSLBUploader(&uploader.AliyunSLBUploaderConfig{
|
uploader, err := uploaderAliyunSlb.New(&uploaderAliyunSlb.AliyunSLBUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunCLBDeployer{
|
return &AliyunCLBDeployer{
|
||||||
|
|
@ -56,7 +60,7 @@ func (d *AliyunCLBDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) GetInfo() []string {
|
func (d *AliyunCLBDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,12 +81,12 @@ func (d *AliyunCLBDeployer) Deploy(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*slb20140515.Client, error) {
|
func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +103,7 @@ func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
client, err := slb20140515.NewClient(aConfig)
|
client, err := aliyunSlb.NewClient(aConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -109,21 +113,20 @@ func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
aliListenerPorts := make([]int32, 0)
|
||||||
if aliLoadbalancerId == "" {
|
if aliLoadbalancerId == "" {
|
||||||
return errors.New("`loadbalancerId` is required")
|
return errors.New("`loadbalancerId` is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
aliListenerPorts := make([]int32, 0)
|
|
||||||
|
|
||||||
// 查询负载均衡实例的详细信息
|
// 查询负载均衡实例的详细信息
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute
|
||||||
describeLoadBalancerAttributeReq := &slb20140515.DescribeLoadBalancerAttributeRequest{
|
describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{
|
||||||
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
}
|
}
|
||||||
describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq)
|
describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp))
|
||||||
|
|
@ -134,7 +137,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
listListenersLimit := int32(100)
|
listListenersLimit := int32(100)
|
||||||
var listListenersToken *string = nil
|
var listListenersToken *string = nil
|
||||||
for {
|
for {
|
||||||
describeLoadBalancerListenersReq := &slb20140515.DescribeLoadBalancerListenersRequest{
|
describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{
|
||||||
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
MaxResults: tea.Int32(listListenersLimit),
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
NextToken: listListenersToken,
|
NextToken: listListenersToken,
|
||||||
|
|
@ -143,7 +146,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq)
|
describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerListeners': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerListeners'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if describeLoadBalancerListenersResp.Body.Listeners != nil {
|
if describeLoadBalancerListenersResp.Body.Listeners != nil {
|
||||||
|
|
@ -163,17 +166,17 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", aliListenerPorts))
|
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", aliListenerPorts))
|
||||||
|
|
||||||
// 上传证书到 SLB
|
// 上传证书到 SLB
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 批量更新监听证书
|
// 批量更新监听证书
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, aliListenerPort := range aliListenerPorts {
|
for _, aliListenerPort := range aliListenerPorts {
|
||||||
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,15 +199,15 @@ func (d *AliyunCLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书到 SLB
|
// 上传证书到 SLB
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 更新监听
|
// 更新监听
|
||||||
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,27 +217,27 @@ func (d *AliyunCLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLoadbalancerId string, aliListenerPort int32, aliCertId string) error {
|
func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLoadbalancerId string, aliListenerPort int32, aliCertId string) error {
|
||||||
// 查询监听配置
|
// 查询监听配置
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute
|
||||||
describeLoadBalancerHTTPSListenerAttributeReq := &slb20140515.DescribeLoadBalancerHTTPSListenerAttributeRequest{
|
describeLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.DescribeLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
}
|
}
|
||||||
describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq)
|
describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp))
|
||||||
|
|
||||||
// 查询扩展域名
|
// 查询扩展域名
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions
|
||||||
describeDomainExtensionsReq := &slb20140515.DescribeDomainExtensionsRequest{
|
describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{
|
||||||
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
}
|
}
|
||||||
describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq)
|
describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.DescribeDomainExtensions': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 CLB 扩展域名", describeDomainExtensionsResp))
|
d.infos = append(d.infos, toStr("已查询到 CLB 扩展域名", describeDomainExtensionsResp))
|
||||||
|
|
@ -249,14 +252,14 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLo
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
setDomainExtensionAttributeReq := &slb20140515.SetDomainExtensionAttributeRequest{
|
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
||||||
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
DomainExtensionId: tea.String(*domainExtension.DomainExtensionId),
|
DomainExtensionId: tea.String(*domainExtension.DomainExtensionId),
|
||||||
ServerCertificateId: tea.String(aliCertId),
|
ServerCertificateId: tea.String(aliCertId),
|
||||||
}
|
}
|
||||||
_, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq)
|
_, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.SetDomainExtensionAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +268,7 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLo
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute
|
||||||
//
|
//
|
||||||
// 注意修改监听配置要放在修改扩展域名之后
|
// 注意修改监听配置要放在修改扩展域名之后
|
||||||
setLoadBalancerHTTPSListenerAttributeReq := &slb20140515.SetLoadBalancerHTTPSListenerAttributeRequest{
|
setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
|
|
@ -273,7 +276,7 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLo
|
||||||
}
|
}
|
||||||
setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq)
|
setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunDcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunDCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunDcdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunDCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&AliyunDCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunDCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 支持泛解析域名,在 Aliyun DCDN 中泛解析域名表示为 .example.com
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domain = strings.TrimPrefix(domain, "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate
|
||||||
|
setDcdnDomainSSLCertificateReq := &aliyunDcdn.SetDcdnDomainSSLCertificateRequest{
|
||||||
|
DomainName: tea.String(domain),
|
||||||
|
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
|
CertType: tea.String("upload"),
|
||||||
|
SSLProtocol: tea.String("on"),
|
||||||
|
SSLPub: tea.String(d.option.Certificate.Certificate),
|
||||||
|
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
||||||
|
}
|
||||||
|
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) {
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("dcdn.aliyuncs.com"),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunDcdn.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* @Author: Bin
|
|
||||||
* @Date: 2024-09-17
|
|
||||||
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
|
|
||||||
*/
|
|
||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
|
||||||
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunESADeployer struct {
|
|
||||||
client *dcdn20180115.Client
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunESADeployer(option *DeployerOption) (*AliyunESADeployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
|
||||||
|
|
||||||
d := &AliyunESADeployer{
|
|
||||||
option: option,
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunESADeployer{
|
|
||||||
client: client,
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetInfo() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
|
|
||||||
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
|
||||||
|
|
||||||
// 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domain = strings.TrimPrefix(domain, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
|
||||||
DomainName: tea.String(domain),
|
|
||||||
CertName: tea.String(certName),
|
|
||||||
CertType: tea.String("upload"),
|
|
||||||
SSLProtocol: tea.String("on"),
|
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
|
||||||
CertRegion: tea.String("cn-hangzhou"),
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime := &util.RuntimeOptions{}
|
|
||||||
|
|
||||||
resp, err := d.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("dcdn设置证书", resp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
|
|
||||||
config := &openapi.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
config.Endpoint = tea.String("dcdn.aliyuncs.com")
|
|
||||||
_result = &dcdn20180115.Client{}
|
|
||||||
_result, _err = dcdn20180115.NewClient(config)
|
|
||||||
return _result, _err
|
|
||||||
}
|
|
||||||
|
|
@ -6,25 +6,29 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
nlb20220430 "github.com/alibabacloud-go/nlb-20220430/v2/client"
|
aliyunNlb "github.com/alibabacloud-go/nlb-20220430/v2/client"
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunNLBDeployer struct {
|
type AliyunNLBDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
sdkClient *nlb20220430.Client
|
sdkClient *aliyunNlb.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := (&AliyunNLBDeployer{}).createSdkClient(
|
client, err := (&AliyunNLBDeployer{}).createSdkClient(
|
||||||
access.AccessKeyId,
|
access.AccessKeyId,
|
||||||
|
|
@ -32,16 +36,16 @@ func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploader, err := uploader.NewAliyunCASUploader(&uploader.AliyunCASUploaderConfig{
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunNLBDeployer{
|
return &AliyunNLBDeployer{
|
||||||
|
|
@ -56,7 +60,7 @@ func (d *AliyunNLBDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) GetInfo() []string {
|
func (d *AliyunNLBDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,12 +81,12 @@ func (d *AliyunNLBDeployer) Deploy(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*nlb20220430.Client, error) {
|
func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +98,7 @@ func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
client, err := nlb20220430.NewClient(aConfig)
|
client, err := aliyunNlb.NewClient(aConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -112,12 +116,12 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
|
||||||
// 查询负载均衡实例的详细信息
|
// 查询负载均衡实例的详细信息
|
||||||
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute
|
||||||
getLoadBalancerAttributeReq := &nlb20220430.GetLoadBalancerAttributeRequest{
|
getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
}
|
}
|
||||||
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'nlb.GetLoadBalancerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp))
|
||||||
|
|
@ -128,7 +132,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
listListenersLimit := int32(100)
|
listListenersLimit := int32(100)
|
||||||
var listListenersToken *string = nil
|
var listListenersToken *string = nil
|
||||||
for {
|
for {
|
||||||
listListenersReq := &nlb20220430.ListListenersRequest{
|
listListenersReq := &aliyunNlb.ListListenersRequest{
|
||||||
MaxResults: tea.Int32(listListenersLimit),
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
NextToken: listListenersToken,
|
NextToken: listListenersToken,
|
||||||
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
|
@ -136,7 +140,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.ListListeners'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listListenersResp.Body.Listeners != nil {
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
|
@ -156,17 +160,17 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", aliListenerIds))
|
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", aliListenerIds))
|
||||||
|
|
||||||
// 上传证书到 SSL
|
// 上传证书到 SSL
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 批量更新监听证书
|
// 批量更新监听证书
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, aliListenerId := range aliListenerIds {
|
for _, aliListenerId := range aliListenerIds {
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,15 +188,15 @@ func (d *AliyunNLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书到 SSL
|
// 上传证书到 SSL
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 更新监听
|
// 更新监听
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil {
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,25 +206,25 @@ func (d *AliyunNLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
||||||
// 查询监听的属性
|
// 查询监听的属性
|
||||||
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute
|
||||||
getListenerAttributeReq := &nlb20220430.GetListenerAttributeRequest{
|
getListenerAttributeReq := &aliyunNlb.GetListenerAttributeRequest{
|
||||||
ListenerId: tea.String(aliListenerId),
|
ListenerId: tea.String(aliListenerId),
|
||||||
}
|
}
|
||||||
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'nlb.GetListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 NLB 监听配置", getListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已查询到 NLB 监听配置", getListenerAttributeResp))
|
||||||
|
|
||||||
// 修改监听的属性
|
// 修改监听的属性
|
||||||
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute
|
||||||
updateListenerAttributeReq := &nlb20220430.UpdateListenerAttributeRequest{
|
updateListenerAttributeReq := &aliyunNlb.UpdateListenerAttributeRequest{
|
||||||
ListenerId: tea.String(aliListenerId),
|
ListenerId: tea.String(aliListenerId),
|
||||||
CertificateIds: []*string{tea.String(aliCertId)},
|
CertificateIds: []*string{tea.String(aliCertId)},
|
||||||
}
|
}
|
||||||
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'nlb.UpdateListenerAttribute': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp))
|
d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp))
|
||||||
|
|
|
||||||
|
|
@ -3,48 +3,62 @@ package deployer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunOSSDeployer struct {
|
type AliyunOSSDeployer struct {
|
||||||
client *oss.Client
|
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *oss.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
d := &AliyunOSSDeployer{
|
client, err := (&AliyunOSSDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
option.DeployConfig.GetConfigAsString("endpoint"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunOSSDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
}
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d.client = client
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetID() string {
|
func (d *AliyunOSSDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetInfo() []string {
|
func (d *AliyunOSSDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
||||||
err := d.client.PutBucketCnameWithCertificate(getDeployString(d.option.DeployConfig, "bucket"), oss.PutBucketCname{
|
aliBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
||||||
Cname: getDeployString(d.option.DeployConfig, "domain"),
|
if aliBucket == "" {
|
||||||
|
return errors.New("`bucket` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为存储空间绑定自定义域名
|
||||||
|
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
|
||||||
|
err := d.sdkClient.PutBucketCnameWithCertificate(aliBucket, oss.PutBucketCname{
|
||||||
|
Cname: d.option.DeployConfig.GetConfigAsString("domain"),
|
||||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||||
Certificate: d.option.Certificate.Certificate,
|
Certificate: d.option.Certificate.Certificate,
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
PrivateKey: d.option.Certificate.PrivateKey,
|
||||||
|
|
@ -52,19 +66,21 @@ func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("deploy aliyun oss error: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
|
func (d *AliyunOSSDeployer) createSdkClient(accessKeyId, accessKeySecret, endpoint string) (*oss.Client, error) {
|
||||||
client, err := oss.New(
|
if endpoint == "" {
|
||||||
getDeployString(d.option.DeployConfig, "endpoint"),
|
endpoint = "oss.aliyuncs.com"
|
||||||
accessKeyId,
|
|
||||||
accessKeySecret,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create aliyun client error: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
const (
|
const (
|
||||||
targetAliyunOSS = "aliyun-oss"
|
targetAliyunOSS = "aliyun-oss"
|
||||||
targetAliyunCDN = "aliyun-cdn"
|
targetAliyunCDN = "aliyun-cdn"
|
||||||
targetAliyunESA = "aliyun-dcdn"
|
targetAliyunDCDN = "aliyun-dcdn"
|
||||||
targetAliyunCLB = "aliyun-clb"
|
targetAliyunCLB = "aliyun-clb"
|
||||||
targetAliyunALB = "aliyun-alb"
|
targetAliyunALB = "aliyun-alb"
|
||||||
targetAliyunNLB = "aliyun-nlb"
|
targetAliyunNLB = "aliyun-nlb"
|
||||||
|
|
@ -52,7 +52,7 @@ type DeployerOption struct {
|
||||||
|
|
||||||
type Deployer interface {
|
type Deployer interface {
|
||||||
Deploy(ctx context.Context) error
|
Deploy(ctx context.Context) error
|
||||||
GetInfo() []string
|
GetInfos() []string
|
||||||
GetID() string
|
GetID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,8 +112,8 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
||||||
return NewAliyunOSSDeployer(option)
|
return NewAliyunOSSDeployer(option)
|
||||||
case targetAliyunCDN:
|
case targetAliyunCDN:
|
||||||
return NewAliyunCDNDeployer(option)
|
return NewAliyunCDNDeployer(option)
|
||||||
case targetAliyunESA:
|
case targetAliyunDCDN:
|
||||||
return NewAliyunESADeployer(option)
|
return NewAliyunDCDNDeployer(option)
|
||||||
case targetAliyunCLB:
|
case targetAliyunCLB:
|
||||||
return NewAliyunCLBDeployer(option)
|
return NewAliyunCLBDeployer(option)
|
||||||
case targetAliyunALB:
|
case targetAliyunALB:
|
||||||
|
|
@ -156,41 +156,6 @@ func toStr(tag string, data any) string {
|
||||||
return tag + ":" + string(byts)
|
return tag + ":" + string(byts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployString(conf domain.DeployConfig, key string) string {
|
|
||||||
if _, ok := conf.Config[key]; !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := conf.Config[key].(string)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeployVariables(conf domain.DeployConfig) map[string]string {
|
|
||||||
rs := make(map[string]string)
|
|
||||||
data, ok := conf.Config["variables"]
|
|
||||||
if !ok {
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
bts, _ := json.Marshal(data)
|
|
||||||
|
|
||||||
kvData := make([]domain.KV, 0)
|
|
||||||
|
|
||||||
if err := json.Unmarshal(bts, &kvData); err != nil {
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, kv := range kvData {
|
|
||||||
rs[kv.Key] = kv.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) {
|
func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) {
|
||||||
cert, err := x509.ParseCertificateFromPEM(certificate)
|
cert, err := x509.ParseCertificateFromPEM(certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -204,7 +169,7 @@ func convertPEMToPFX(certificate string, privateKey string, password string) ([]
|
||||||
|
|
||||||
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
|
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to encode as pfx %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pfxData, nil
|
return pfxData, nil
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,32 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderHcScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
hcCdnEx "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HuaweiCloudCDNDeployer struct {
|
type HuaweiCloudCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
sdkClient *hcCdn.CdnClient
|
sdkClient *hcCdnEx.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.HuaweiCloudAccess{}
|
access := &domain.HuaweiCloudAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient(
|
client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient(
|
||||||
|
|
@ -36,17 +38,16 @@ func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
|
uploader, err := uploaderHcScm.New(&uploaderHcScm.HuaweiCloudSCMUploaderConfig{
|
||||||
uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
Region: "",
|
Region: "",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HuaweiCloudCDNDeployer{
|
return &HuaweiCloudCDNDeployer{
|
||||||
|
|
@ -61,11 +62,19 @@ func (d *HuaweiCloudCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
|
func (d *HuaweiCloudCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 查询加速域名配置
|
// 查询加速域名配置
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
||||||
showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
|
showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
|
||||||
|
|
@ -73,7 +82,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
|
showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
||||||
|
|
@ -81,37 +90,21 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 更新加速域名配置
|
// 更新加速域名配置
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
||||||
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
|
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
|
||||||
updateDomainMultiCertificatesReqBodyContent := &huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent{}
|
updateDomainMultiCertificatesReqBodyContent := &hcCdnEx.UpdateDomainMultiCertificatesExRequestBodyContent{}
|
||||||
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
|
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
|
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
|
||||||
var updateDomainMultiCertificatesResp *hcCdnModel.UpdateDomainMultiCertificatesResponse
|
|
||||||
if d.option.DeployConfig.GetConfigAsBool("useSCM") {
|
|
||||||
// 上传证书到 SCM
|
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
|
||||||
|
|
||||||
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
|
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
|
||||||
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(uploadResult.CertId)
|
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(upres.CertId)
|
||||||
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(uploadResult.CertName)
|
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(upres.CertName)
|
||||||
} else {
|
updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs)
|
||||||
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(0)
|
updateDomainMultiCertificatesReq := &hcCdnEx.UpdateDomainMultiCertificatesExRequest{
|
||||||
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli()))
|
Body: &hcCdnEx.UpdateDomainMultiCertificatesExRequestBody{
|
||||||
updateDomainMultiCertificatesReqBodyContent.Certificate = cast.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
updateDomainMultiCertificatesReqBodyContent.PrivateKey = cast.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
}
|
|
||||||
updateDomainMultiCertificatesReqBodyContent = mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, updateDomainMultiCertificatesReqBodyContent)
|
|
||||||
updateDomainMultiCertificatesReq := &huaweicloudCDNUpdateDomainMultiCertificatesRequest{
|
|
||||||
Body: &huaweicloudCDNUpdateDomainMultiCertificatesRequestBody{
|
|
||||||
Https: updateDomainMultiCertificatesReqBodyContent,
|
Https: updateDomainMultiCertificatesReqBodyContent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(d.sdkClient, updateDomainMultiCertificatesReq)
|
updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
||||||
|
|
@ -119,7 +112,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdn.CdnClient, error) {
|
func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdnEx.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-north-1" // CDN 服务默认区域:华北一北京
|
region = "cn-north-1" // CDN 服务默认区域:华北一北京
|
||||||
}
|
}
|
||||||
|
|
@ -145,69 +138,6 @@ func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, r
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := hcCdn.NewCdnClient(hcClient)
|
client := hcCdnEx.NewClient(hcClient)
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent struct {
|
|
||||||
hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
|
|
||||||
|
|
||||||
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBody struct {
|
|
||||||
Https *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent `json:"https,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type huaweicloudCDNUpdateDomainMultiCertificatesRequest struct {
|
|
||||||
Body *huaweicloudCDNUpdateDomainMultiCertificatesRequestBody `json:"body,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *hcCdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*hcCdnModel.UpdateDomainMultiCertificatesResponse, error) {
|
|
||||||
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求
|
|
||||||
// 可能需要等之后 SDK 更新
|
|
||||||
|
|
||||||
requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates()
|
|
||||||
|
|
||||||
if resp, err := client.HcClient.Sync(request, requestDef); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return resp.(*hcCdnModel.UpdateDomainMultiCertificatesResponse), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeHuaweiCloudCDNConfig(src *hcCdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent {
|
|
||||||
if src == nil {
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去
|
|
||||||
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化
|
|
||||||
|
|
||||||
if *src.OriginProtocol == "follow" {
|
|
||||||
dest.AccessOriginWay = cast.Int32Ptr(1)
|
|
||||||
} else if *src.OriginProtocol == "http" {
|
|
||||||
dest.AccessOriginWay = cast.Int32Ptr(2)
|
|
||||||
} else if *src.OriginProtocol == "https" {
|
|
||||||
dest.AccessOriginWay = cast.Int32Ptr(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.ForceRedirect != nil {
|
|
||||||
dest.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
|
|
||||||
|
|
||||||
if src.ForceRedirect.Status == "on" {
|
|
||||||
dest.ForceRedirectConfig.Switch = 1
|
|
||||||
dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
|
|
||||||
} else {
|
|
||||||
dest.ForceRedirectConfig.Switch = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.Https != nil {
|
|
||||||
if *src.Https.Http2Status == "on" {
|
|
||||||
dest.Http2 = cast.Int32Ptr(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ import (
|
||||||
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
||||||
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
||||||
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderHcElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,7 +35,7 @@ type HuaweiCloudELBDeployer struct {
|
||||||
func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
|
func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.HuaweiCloudAccess{}
|
access := &domain.HuaweiCloudAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := (&HuaweiCloudELBDeployer{}).createSdkClient(
|
client, err := (&HuaweiCloudELBDeployer{}).createSdkClient(
|
||||||
|
|
@ -42,16 +44,16 @@ func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploader, err := uploader.NewHuaweiCloudELBUploader(&uploader.HuaweiCloudELBUploaderConfig{
|
uploader, err := uploaderHcElb.New(&uploaderHcElb.HuaweiCloudELBUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HuaweiCloudELBDeployer{
|
return &HuaweiCloudELBDeployer{
|
||||||
|
|
@ -66,21 +68,24 @@ func (d *HuaweiCloudELBDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) GetInfo() []string {
|
func (d *HuaweiCloudELBDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context) error {
|
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context) error {
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
case "certificate":
|
case "certificate":
|
||||||
|
// 部署到指定证书
|
||||||
if err := d.deployToCertificate(ctx); err != nil {
|
if err := d.deployToCertificate(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "loadbalancer":
|
case "loadbalancer":
|
||||||
|
// 部署到指定负载均衡器
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "listener":
|
case "listener":
|
||||||
|
// 部署到指定监听器
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +174,7 @@ func (u *HuaweiCloudELBDeployer) getSdkProjectId(accessKeyId, secretAccessKey, r
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
||||||
return "", fmt.Errorf("no project found")
|
return "", errors.New("no project found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (*response.Projects)[0].Id, nil
|
return (*response.Projects)[0].Id, nil
|
||||||
|
|
@ -194,7 +199,7 @@ func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context) error
|
||||||
}
|
}
|
||||||
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
|
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.UpdateCertificate': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 ELB 证书", updateCertificateResp))
|
d.infos = append(d.infos, toStr("已更新 ELB 证书", updateCertificateResp))
|
||||||
|
|
@ -217,7 +222,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error
|
||||||
}
|
}
|
||||||
showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
|
showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.ShowLoadBalancer': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器", showLoadBalancerResp))
|
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器", showLoadBalancerResp))
|
||||||
|
|
@ -235,7 +240,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error
|
||||||
}
|
}
|
||||||
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.ListListeners': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListListeners'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listListenersResp.Listeners != nil {
|
if listListenersResp.Listeners != nil {
|
||||||
|
|
@ -254,17 +259,17 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error
|
||||||
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器下的监听器", hcListenerIds))
|
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器下的监听器", hcListenerIds))
|
||||||
|
|
||||||
// 上传证书到 SCM
|
// 上传证书到 SCM
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 批量更新监听器证书
|
// 批量更新监听器证书
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, hcListenerId := range hcListenerIds {
|
for _, hcListenerId := range hcListenerIds {
|
||||||
if err := d.updateListenerCertificate(ctx, hcListenerId, uploadResult.CertId); err != nil {
|
if err := d.modifyListenerCertificate(ctx, hcListenerId, upres.CertId); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,22 +287,22 @@ func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书到 SCM
|
// 上传证书到 SCM
|
||||||
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 更新监听器证书
|
// 更新监听器证书
|
||||||
if err := d.updateListenerCertificate(ctx, hcListenerId, uploadResult.CertId); err != nil {
|
if err := d.modifyListenerCertificate(ctx, hcListenerId, upres.CertId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context, hcListenerId string, hcCertId string) error {
|
func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, hcListenerId string, hcCertId string) error {
|
||||||
// 查询监听器详情
|
// 查询监听器详情
|
||||||
// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
|
// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
|
||||||
showListenerReq := &hcElbModel.ShowListenerRequest{
|
showListenerReq := &hcElbModel.ShowListenerRequest{
|
||||||
|
|
@ -305,7 +310,7 @@ func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context,
|
||||||
}
|
}
|
||||||
showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
|
showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.ShowListener': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ELB 监听器", showListenerResp))
|
d.infos = append(d.infos, toStr("已查询到 ELB 监听器", showListenerResp))
|
||||||
|
|
@ -331,7 +336,7 @@ func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context,
|
||||||
}
|
}
|
||||||
listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq)
|
listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'")
|
||||||
}
|
}
|
||||||
|
|
||||||
showNewCertificateReq := &hcElbModel.ShowCertificateRequest{
|
showNewCertificateReq := &hcElbModel.ShowCertificateRequest{
|
||||||
|
|
@ -339,7 +344,7 @@ func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context,
|
||||||
}
|
}
|
||||||
showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq)
|
showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.ShowCertificate': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, certificate := range *listOldCertificateResp.Certificates {
|
for _, certificate := range *listOldCertificateResp.Certificates {
|
||||||
|
|
@ -372,7 +377,7 @@ func (d *HuaweiCloudELBDeployer) updateListenerCertificate(ctx context.Context,
|
||||||
}
|
}
|
||||||
updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
|
updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute sdk request 'elb.UpdateListener': %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateListener'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 ELB 监听器", updateListenerResp))
|
d.infos = append(d.infos, toStr("已更新 ELB 监听器", updateListenerResp))
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ package deployer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
xerrors "github.com/pkg/errors"
|
||||||
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
k8sCore "k8s.io/api/core/v1"
|
||||||
|
k8sMeta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
|
@ -19,12 +21,25 @@ import (
|
||||||
type K8sSecretDeployer struct {
|
type K8sSecretDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
k8sClient *kubernetes.Clientset
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.KubernetesAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&K8sSecretDeployer{}).createK8sClient(access)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create k8s client")
|
||||||
|
}
|
||||||
|
|
||||||
return &K8sSecretDeployer{
|
return &K8sSecretDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
k8sClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,75 +47,53 @@ func (d *K8sSecretDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetInfo() []string {
|
func (d *K8sSecretDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.KubernetesAccess{}
|
namespace := d.option.DeployConfig.GetConfigAsString("namespace")
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
secretName := d.option.DeployConfig.GetConfigAsString("secretName")
|
||||||
return err
|
secretDataKeyForCrt := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForCrt", "tls.crt")
|
||||||
}
|
secretDataKeyForKey := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForKey", "tls.key")
|
||||||
|
|
||||||
client, err := d.createClient(access)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("kubeClient create success.", nil))
|
|
||||||
|
|
||||||
namespace := getDeployString(d.option.DeployConfig, "namespace")
|
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
namespace = "default"
|
namespace = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
secretName := getDeployString(d.option.DeployConfig, "secretName")
|
|
||||||
if secretName == "" {
|
if secretName == "" {
|
||||||
return fmt.Errorf("k8s secret name is empty")
|
return errors.New("`secretName` is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
secretDataKeyForCrt := getDeployString(d.option.DeployConfig, "secretDataKeyForCrt")
|
certX509, err := x509.ParseCertificateFromPEM(d.option.Certificate.Certificate)
|
||||||
if secretDataKeyForCrt == "" {
|
|
||||||
namespace = "tls.crt"
|
|
||||||
}
|
|
||||||
|
|
||||||
secretDataKeyForKey := getDeployString(d.option.DeployConfig, "secretDataKeyForKey")
|
|
||||||
if secretDataKeyForKey == "" {
|
|
||||||
namespace = "tls.key"
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := x509.ParseCertificateFromPEM(d.option.Certificate.Certificate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretPayload := corev1.Secret{
|
secretPayload := k8sCore.Secret{
|
||||||
TypeMeta: k8sMetaV1.TypeMeta{
|
TypeMeta: k8sMeta.TypeMeta{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
},
|
},
|
||||||
ObjectMeta: k8sMetaV1.ObjectMeta{
|
ObjectMeta: k8sMeta.ObjectMeta{
|
||||||
Name: secretName,
|
Name: secretName,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"certimate/domains": d.option.Domain,
|
"certimate/domains": d.option.Domain,
|
||||||
"certimate/alt-names": strings.Join(certificate.DNSNames, ","),
|
"certimate/alt-names": strings.Join(certX509.DNSNames, ","),
|
||||||
"certimate/common-name": certificate.Subject.CommonName,
|
"certimate/common-name": certX509.Subject.CommonName,
|
||||||
"certimate/issuer-organization": strings.Join(certificate.Issuer.Organization, ","),
|
"certimate/issuer-organization": strings.Join(certX509.Issuer.Organization, ","),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: corev1.SecretType("kubernetes.io/tls"),
|
Type: k8sCore.SecretType("kubernetes.io/tls"),
|
||||||
}
|
}
|
||||||
|
|
||||||
secretPayload.Data = make(map[string][]byte)
|
secretPayload.Data = make(map[string][]byte)
|
||||||
secretPayload.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
|
secretPayload.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
|
||||||
secretPayload.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
|
secretPayload.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
|
||||||
|
|
||||||
// 获取 Secret 实例
|
// 获取 Secret 实例
|
||||||
_, err = client.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMetaV1.GetOptions{})
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMeta.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err = client.CoreV1().Secrets(namespace).Create(context.TODO(), &secretPayload, k8sMetaV1.CreateOptions{})
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Create(context.TODO(), &secretPayload, k8sMeta.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create k8s secret: %w", err)
|
return xerrors.Wrap(err, "failed to create k8s secret")
|
||||||
} else {
|
} else {
|
||||||
d.infos = append(d.infos, toStr("Certificate has been created in K8s Secret", nil))
|
d.infos = append(d.infos, toStr("Certificate has been created in K8s Secret", nil))
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -108,9 +101,9 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 Secret 实例
|
// 更新 Secret 实例
|
||||||
_, err = client.CoreV1().Secrets(namespace).Update(context.TODO(), &secretPayload, k8sMetaV1.UpdateOptions{})
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Update(context.TODO(), &secretPayload, k8sMeta.UpdateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update k8s secret: %w", err)
|
return xerrors.Wrap(err, "failed to update k8s secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("Certificate has been updated to K8s Secret", nil))
|
d.infos = append(d.infos, toStr("Certificate has been updated to K8s Secret", nil))
|
||||||
|
|
@ -118,7 +111,7 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
func (d *K8sSecretDeployer) createK8sClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
||||||
var config *rest.Config
|
var config *rest.Config
|
||||||
var err error
|
var err error
|
||||||
if access.KubeConfig == "" {
|
if access.KubeConfig == "" {
|
||||||
|
|
@ -129,7 +122,6 @@ func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kube
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config, err = kubeConfig.ClientConfig()
|
config, err = kubeConfig.ClientConfig()
|
||||||
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -139,5 +131,6 @@ func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kube
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ package deployer
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,22 +41,17 @@ func (d *LocalDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) GetInfo() []string {
|
func (d *LocalDeployer) GetInfos() []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.LocalAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行前置命令
|
// 执行前置命令
|
||||||
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
||||||
if preCommand != "" {
|
if preCommand != "" {
|
||||||
stdout, stderr, err := d.execCommand(preCommand)
|
stdout, stderr, err := d.execCommand(preCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("执行前置命令成功", stdout))
|
d.infos = append(d.infos, toStr("执行前置命令成功", stdout))
|
||||||
|
|
@ -65,13 +61,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
||||||
case certFormatPEM:
|
case certFormatPEM:
|
||||||
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
||||||
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
||||||
return fmt.Errorf("failed to save private key file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
||||||
|
|
@ -83,11 +79,11 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
@ -101,11 +97,11 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
@ -116,7 +112,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
if command != "" {
|
if command != "" {
|
||||||
stdout, stderr, err := d.execCommand(command)
|
stdout, stderr, err := d.execCommand(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("执行命令成功", stdout))
|
d.infos = append(d.infos, toStr("执行命令成功", stdout))
|
||||||
|
|
@ -146,7 +142,7 @@ func (d *LocalDeployer) execCommand(command string) (string, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", "", fmt.Errorf("unsupported shell")
|
return "", "", errors.New("unsupported shell")
|
||||||
}
|
}
|
||||||
|
|
||||||
var stdoutBuf bytes.Buffer
|
var stdoutBuf bytes.Buffer
|
||||||
|
|
@ -156,8 +152,8 @@ func (d *LocalDeployer) execCommand(command string) (string, string, error) {
|
||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("failed to execute script: %w", err)
|
return "", "", xerrors.Wrap(err, "failed to execute shell script")
|
||||||
}
|
}
|
||||||
|
|
||||||
return stdoutBuf.String(), stderrBuf.String(), err
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,54 @@
|
||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
|
||||||
|
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const qiniuGateway = "http://api.qiniu.com"
|
|
||||||
|
|
||||||
type QiniuCDNDeployer struct {
|
type QiniuCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
info []string
|
infos []string
|
||||||
credentials *auth.Credentials
|
|
||||||
|
sdkClient *qiniuEx.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) {
|
func NewQiniuCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.QiniuAccess{}
|
access := &domain.QiniuAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&QiniuCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKey,
|
||||||
|
access.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderQiniu.New(&uploaderQiniu.QiniuSSLCertUploaderConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &QiniuCDNDeployer{
|
return &QiniuCDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
info: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
credentials: auth.New(access.AccessKey, access.SecretKey),
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,177 +56,52 @@ func (d *QiniuCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetInfo() []string {
|
func (d *QiniuCDNDeployer) GetInfos() []string {
|
||||||
return d.info
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// 上传证书
|
||||||
certId, err := d.uploadCert()
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("uploadCert failed: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 获取域名信息
|
// 获取域名信息
|
||||||
domainInfo, err := d.getDomainInfo()
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getDomainInfo failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断域名是否启用 https
|
d.infos = append(d.infos, toStr("已获取域名信息", getDomainInfoResp))
|
||||||
if domainInfo.Https != nil && domainInfo.Https.CertID != "" {
|
|
||||||
// 启用了 https
|
// 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS
|
||||||
// 修改域名证书
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
err = d.modifyDomainCert(certId, domainInfo.Https.ForceHttps, domainInfo.Https.Http2Enable)
|
if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" {
|
||||||
|
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("modifyDomainCert failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改域名证书", modifyDomainHttpsConfResp))
|
||||||
} else {
|
} else {
|
||||||
// 没启用 https
|
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true)
|
||||||
// 启用 https
|
|
||||||
err = d.enableHttps(certId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("enableHttps failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已将域名升级为 HTTPS", enableDomainHttpsResp))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) enableHttps(certId string) error {
|
func (u *QiniuCDNDeployer) createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) {
|
||||||
domain := d.option.DeployConfig.GetDomain()
|
credential := auth.New(accessKey, secretKey)
|
||||||
path := fmt.Sprintf("/domain/%s/sslize", domain)
|
client := qiniuEx.NewClient(credential)
|
||||||
|
return client, nil
|
||||||
body := &qiniuModifyDomainCertReq{
|
|
||||||
CertID: certId,
|
|
||||||
ForceHttps: true,
|
|
||||||
Http2Enable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable https failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable https failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuDomainInfo struct {
|
|
||||||
Https *qiniuModifyDomainCertReq `json:"https"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) getDomainInfo() (*qiniuDomainInfo, error) {
|
|
||||||
domain := d.option.DeployConfig.GetDomain()
|
|
||||||
|
|
||||||
path := fmt.Sprintf("/domain/%s", domain)
|
|
||||||
|
|
||||||
res, err := d.req(qiniuGateway+path, http.MethodGet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &qiniuDomainInfo{}
|
|
||||||
err = json.Unmarshal(res, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("json.Unmarshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuUploadCertReq struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CommonName string `json:"common_name"`
|
|
||||||
Pri string `json:"pri"`
|
|
||||||
Ca string `json:"ca"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuUploadCertResp struct {
|
|
||||||
CertID string `json:"certID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) uploadCert() (string, error) {
|
|
||||||
path := "/sslcert"
|
|
||||||
|
|
||||||
body := &qiniuUploadCertReq{
|
|
||||||
Name: getDeployString(d.option.DeployConfig, "domain"),
|
|
||||||
CommonName: getDeployString(d.option.DeployConfig, "domain"),
|
|
||||||
Pri: d.option.Certificate.PrivateKey,
|
|
||||||
Ca: d.option.Certificate.Certificate,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("json.Marshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := d.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
resp := &qiniuUploadCertResp{}
|
|
||||||
err = json.Unmarshal(res, resp)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("json.Unmarshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.CertID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuModifyDomainCertReq struct {
|
|
||||||
CertID string `json:"certId"`
|
|
||||||
ForceHttps bool `json:"forceHttps"`
|
|
||||||
Http2Enable bool `json:"http2Enable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) modifyDomainCert(certId string, forceHttps, http2Enable bool) error {
|
|
||||||
domain := d.option.DeployConfig.GetDomain()
|
|
||||||
path := fmt.Sprintf("/domain/%s/httpsconf", domain)
|
|
||||||
|
|
||||||
body := &qiniuModifyDomainCertReq{
|
|
||||||
CertID: certId,
|
|
||||||
ForceHttps: forceHttps,
|
|
||||||
Http2Enable: http2Enable,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("json.Marshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) req(url, method string, body io.Reader) ([]byte, error) {
|
|
||||||
req := xhttp.BuildReq(url, method, body, map[string]string{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := d.credentials.AddToken(auth.TokenQBox, req); err != nil {
|
|
||||||
return nil, fmt.Errorf("credentials.AddToken failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody, err := xhttp.ToRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ToRequest failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer respBody.Close()
|
|
||||||
|
|
||||||
res, err := io.ReadAll(respBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("io.ReadAll failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/applicant"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_qiuniu_uploadCert(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
option *DeployerOption
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
fields: fields{
|
|
||||||
option: &DeployerOption{
|
|
||||||
DomainId: "1",
|
|
||||||
Domain: "example.com",
|
|
||||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
|
||||||
Certificate: applicant.Certificate{
|
|
||||||
Certificate: "",
|
|
||||||
PrivateKey: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q, _ := NewQiniuCDNDeployer(tt.fields.option)
|
|
||||||
got, err := q.uploadCert()
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("qiuniu.uploadCert() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_qiuniu_modifyDomainCert(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
option *DeployerOption
|
|
||||||
info []string
|
|
||||||
credentials *auth.Credentials
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
certId string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
fields: fields{
|
|
||||||
option: &DeployerOption{
|
|
||||||
DomainId: "1",
|
|
||||||
Domain: "jt1.ikit.fun",
|
|
||||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q, _ := NewQiniuCDNDeployer(tt.fields.option)
|
|
||||||
if err := q.modifyDomainCert(tt.args.certId, true, true); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ func (d *SSHDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) GetInfo() []string {
|
func (d *SSHDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +56,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
if preCommand != "" {
|
if preCommand != "" {
|
||||||
stdout, stderr, err := d.sshExecCommand(client, preCommand)
|
stdout, stderr, err := d.sshExecCommand(client, preCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
|
d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
|
||||||
|
|
@ -65,13 +66,13 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
||||||
case certFormatPEM:
|
case certFormatPEM:
|
||||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
||||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
||||||
return fmt.Errorf("failed to upload private key file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
||||||
|
|
@ -83,11 +84,11 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
@ -101,11 +102,11 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
@ -116,7 +117,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
if command != "" {
|
if command != "" {
|
||||||
stdout, stderr, err := d.sshExecCommand(client, command)
|
stdout, stderr, err := d.sshExecCommand(client, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 执行命令成功", stdout))
|
d.infos = append(d.infos, toStr("SSH 执行命令成功", stdout))
|
||||||
|
|
@ -158,7 +159,7 @@ func (d *SSHDeployer) createSshClient(access *domain.SSHAccess) (*ssh.Client, er
|
||||||
func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string, string, error) {
|
func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string, string, error) {
|
||||||
session, err := client.NewSession()
|
session, err := client.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("failed to create ssh session: %w", err)
|
return "", "", xerrors.Wrap(err, "failed to create ssh session")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
@ -167,7 +168,11 @@ func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string
|
||||||
var stderrBuf bytes.Buffer
|
var stderrBuf bytes.Buffer
|
||||||
session.Stderr = &stderrBuf
|
session.Stderr = &stderrBuf
|
||||||
err = session.Run(command)
|
err = session.Run(command)
|
||||||
return stdoutBuf.String(), stderrBuf.String(), err
|
if err != nil {
|
||||||
|
return "", "", xerrors.Wrap(err, "failed to execute ssh script")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, content string) error {
|
func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, content string) error {
|
||||||
|
|
@ -177,23 +182,23 @@ func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, conte
|
||||||
func (d *SSHDeployer) writeSftpFile(client *ssh.Client, path string, data []byte) error {
|
func (d *SSHDeployer) writeSftpFile(client *ssh.Client, path string, data []byte) error {
|
||||||
sftpCli, err := sftp.NewClient(client)
|
sftpCli, err := sftp.NewClient(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create sftp client: %w", err)
|
return xerrors.Wrap(err, "failed to create sftp client")
|
||||||
}
|
}
|
||||||
defer sftpCli.Close()
|
defer sftpCli.Close()
|
||||||
|
|
||||||
if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
|
if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
|
||||||
return fmt.Errorf("failed to create remote directory: %w", err)
|
return xerrors.Wrap(err, "failed to create remote directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open remote file: %w", err)
|
return xerrors.Wrap(err, "failed to open remote file")
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
_, err = file.Write(data)
|
_, err = file.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write to remote file: %w", err)
|
return xerrors.Wrap(err, "failed to write to remote file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,58 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||||
cdn "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"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentCDNDeployer struct {
|
type TencentCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentCDNDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentCDNDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
clients, err := (&TencentCDNDeployer{}).createSdkClients(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentCDNDeployer{
|
return &TencentCDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,147 +65,130 @@ func (d *TencentCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetInfo() []string {
|
func (d *TencentCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// 上传证书到 SSL
|
||||||
certId, err := d.uploadCert()
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
return err
|
||||||
}
|
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) uploadCert() (string, error) {
|
// 获取待部署的 CDN 实例
|
||||||
cpf := profile.NewClientProfile()
|
// 如果是泛域名,根据证书匹配 CDN 实例
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
tcInstanceIds := make([]string, 0)
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
tcInstanceIds = domains
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) deploy(certId string) error {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
|
||||||
request := ssl.NewDeployCertificateInstanceRequest()
|
|
||||||
|
|
||||||
request.CertificateId = common.StringPtr(certId)
|
|
||||||
request.ResourceType = common.StringPtr("cdn")
|
|
||||||
request.Status = common.Int64Ptr(1)
|
|
||||||
|
|
||||||
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
if strings.Contains(domain, "*") {
|
|
||||||
list, errGetList := d.getDomainList(certId)
|
|
||||||
if errGetList != nil {
|
|
||||||
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
|
|
||||||
}
|
|
||||||
if len(list) == 0 {
|
|
||||||
d.infos = append(d.infos, "没有需要部署的实例")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
request.InstanceIdList = common.StringPtrs(list)
|
|
||||||
} else { // 否则直接使用传入的域名
|
|
||||||
deployed, _ := d.isDomainDeployed(certId, domain)
|
|
||||||
if deployed {
|
|
||||||
d.infos = append(d.infos, "域名已部署")
|
|
||||||
return nil
|
|
||||||
} else {
|
} else {
|
||||||
request.InstanceIdList = common.StringPtrs([]string{domain})
|
tcInstanceIds = append(tcInstanceIds, domain)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
// 跳过已部署的 CDN 实例
|
||||||
resp, err := client.DeployCertificateInstance(request)
|
if len(tcInstanceIds) > 0 {
|
||||||
|
deployedDomains, err := d.getDeployedDomainsByCertificateId(upres.CertId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
|
temp := make([]string, 0)
|
||||||
|
for _, aliInstanceId := range tcInstanceIds {
|
||||||
|
if !slices.Contains(deployedDomains, aliInstanceId) {
|
||||||
|
temp = append(temp, aliInstanceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcInstanceIds = temp
|
||||||
|
}
|
||||||
|
if len(tcInstanceIds) == 0 {
|
||||||
|
d.infos = append(d.infos, "已部署过或没有要部署的 CDN 实例")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书部署到 CDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(tcInstanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) getDomainList(certId string) ([]string, error) {
|
func (d *TencentCDNDeployer) createSdkClients(secretId, secretKey string) (*tencentCDNDeployerSdkClients, error) {
|
||||||
cpf := profile.NewClientProfile()
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
|
|
||||||
client, _ := cdn.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
request := cdn.NewDescribeCertDomainsRequest()
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
|
||||||
request.CertId = common.StringPtr(certId)
|
|
||||||
|
|
||||||
response, err := client.DescribeCertDomains(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get domain list: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deployedDomains, err := d.getDeployedDomainList(certId)
|
cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get deployed domain list: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentCDNDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
|
// 获取证书中的可用域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
|
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("cdn")
|
||||||
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := make([]string, 0)
|
domains := make([]string, 0)
|
||||||
for _, domain := range response.Response.Domains {
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
domainStr := *domain
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
if !slices.Contains(deployedDomains, domainStr) {
|
|
||||||
domains = append(domains, domainStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) isDomainDeployed(certId, domain string) (bool, error) {
|
|
||||||
deployedDomains, err := d.getDeployedDomainList(certId)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Contains(deployedDomains, domain), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) getDeployedDomainList(certId string) ([]string, error) {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
request := ssl.NewDescribeDeployedResourcesRequest()
|
|
||||||
request.CertificateIds = common.StringPtrs([]string{certId})
|
|
||||||
request.ResourceType = common.StringPtr("cdn")
|
|
||||||
|
|
||||||
response, err := client.DescribeDeployedResources(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get deployed domain list: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
domains := make([]string, 0)
|
|
||||||
for _, domain := range response.Response.DeployedResources[0].Resources {
|
|
||||||
domains = append(domains, *domain)
|
domains = append(domains, *domain)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCDNDeployer) getDeployedDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
|
// 根据证书查询关联 CDN 域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/62674
|
||||||
|
describeDeployedResourcesReq := tcSsl.NewDescribeDeployedResourcesRequest()
|
||||||
|
describeDeployedResourcesReq.CertificateIds = common.StringPtrs([]string{tcCertId})
|
||||||
|
describeDeployedResourcesReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
describeDeployedResourcesResp, err := d.sdkClients.ssl.DescribeDeployedResources(describeDeployedResourcesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeDeployedResources'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeDeployedResourcesResp.Response.DeployedResources != nil {
|
||||||
|
for _, deployedResource := range describeDeployedResourcesResp.Response.DeployedResources {
|
||||||
|
for _, resource := range deployedResource.Resources {
|
||||||
|
domains = append(domains, *resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return domains, nil
|
return domains, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,61 @@ package deployer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
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"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentCLBDeployer struct {
|
type TencentCLBDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentCLBDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentCLBDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
clb *tcClb.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentCLBDeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentCLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
clients, err := (&TencentCLBDeployer{}).createSdkClients(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentCLBDeployer{
|
return &TencentCLBDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,77 +65,266 @@ func (d *TencentCLBDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) GetInfo() []string {
|
func (d *TencentCLBDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// TODO: 直接部署方式
|
||||||
certId, err := d.uploadCert()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
|
||||||
}
|
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
case "ssl-deploy":
|
||||||
|
// 通过 SSL 服务部署到云资源实例
|
||||||
|
err := d.deployToInstanceUseSsl(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "loadbalancer":
|
||||||
|
// 部署到指定负载均衡器
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
// 部署到指定监听器
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "ruledomain":
|
||||||
|
// 部署到指定七层监听转发规则域名
|
||||||
|
if err := d.deployToRuleDomain(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) uploadCert() (string, error) {
|
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
||||||
cpf := profile.NewClientProfile()
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
clbClient, err := tcClb.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentCLBDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
clb: clbClient,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) deploy(certId string) error {
|
func (d *TencentCLBDeployer) deployToInstanceUseSsl(ctx context.Context) error {
|
||||||
cpf := profile.NewClientProfile()
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
client, _ := ssl.NewClient(d.credential, getDeployString(d.option.DeployConfig, "region"), cpf)
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
}
|
||||||
request := ssl.NewDeployCertificateInstanceRequest()
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
request.CertificateId = common.StringPtr(certId)
|
|
||||||
request.ResourceType = common.StringPtr("clb")
|
|
||||||
request.Status = common.Int64Ptr(1)
|
|
||||||
|
|
||||||
clbId := getDeployString(d.option.DeployConfig, "clbId")
|
|
||||||
lsnId := getDeployString(d.option.DeployConfig, "lsnId")
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
|
|
||||||
if(domain == ""){
|
|
||||||
// 未开启SNI,只需要精确到监听器
|
|
||||||
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", clbId, lsnId)})
|
|
||||||
}else{
|
|
||||||
// 开启SNI,需要精确到域名,支持泛域名
|
|
||||||
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", clbId, lsnId, domain)})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
resp, err := client.DeployCertificateInstance(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 证书部署到 CLB 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("clb")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
if tcDomain == "" {
|
||||||
|
// 未开启 SNI,只需指定到监听器
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", tcLoadbalancerId, tcListenerId)})
|
||||||
|
} else {
|
||||||
|
// 开启 SNI,需指定到域名(支持泛域名)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", tcLoadbalancerId, tcListenerId, tcDomain)})
|
||||||
|
}
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerIds := make([]string, 0)
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询负载均衡器详细信息
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/46916
|
||||||
|
describeLoadBalancersDetailReq := tcClb.NewDescribeLoadBalancersDetailRequest()
|
||||||
|
describeLoadBalancersDetailResp, err := d.sdkClients.clb.DescribeLoadBalancersDetail(describeLoadBalancersDetailReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeLoadBalancersDetail'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到负载均衡详细信息", describeLoadBalancersDetailResp))
|
||||||
|
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
} else {
|
||||||
|
if describeListenersResp.Response.Listeners != nil {
|
||||||
|
for _, listener := range describeListenersResp.Response.Listeners {
|
||||||
|
if listener.Protocol == nil || (*listener.Protocol != "HTTPS" && *listener.Protocol != "TCP_SSL" && *listener.Protocol != "QUIC") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tcListenerIds = append(tcListenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到负载均衡器下的监听器", tcListenerIds))
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听器证书
|
||||||
|
var errs []error
|
||||||
|
for _, tcListenerId := range tcListenerIds {
|
||||||
|
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听器证书
|
||||||
|
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToRuleDomain(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
if tcDomain == "" {
|
||||||
|
return errors.New("`domain` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 修改负载均衡七层监听器转发规则的域名级别属性
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/38092
|
||||||
|
modifyDomainAttributesReq := tcClb.NewModifyDomainAttributesRequest()
|
||||||
|
modifyDomainAttributesReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
modifyDomainAttributesReq.ListenerId = common.StringPtr(tcListenerId)
|
||||||
|
modifyDomainAttributesReq.Domain = common.StringPtr(tcDomain)
|
||||||
|
modifyDomainAttributesReq.Certificate = &tcClb.CertificateInput{
|
||||||
|
SSLMode: common.StringPtr("UNIDIRECTIONAL"),
|
||||||
|
CertId: common.StringPtr(upres.CertId),
|
||||||
|
}
|
||||||
|
modifyDomainAttributesResp, err := d.sdkClients.clb.ModifyDomainAttributes(modifyDomainAttributesReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) modifyListenerCertificate(ctx context.Context, tcLoadbalancerId, tcListenerId, tcCertId string) error {
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
describeListenersReq.ListenerIds = common.StringPtrs([]string{tcListenerId})
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
}
|
||||||
|
if len(describeListenersResp.Response.Listeners) == 0 {
|
||||||
|
d.infos = append(d.infos, toStr("未找到监听器", nil))
|
||||||
|
return errors.New("listener not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到监听器属性", describeListenersResp.Response))
|
||||||
|
|
||||||
|
// 修改监听器属性
|
||||||
|
// REF: https://cloud.tencent.com/document/product/214/30681
|
||||||
|
modifyListenerReq := tcClb.NewModifyListenerRequest()
|
||||||
|
modifyListenerReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
modifyListenerReq.ListenerId = common.StringPtr(tcListenerId)
|
||||||
|
modifyListenerReq.Certificate = &tcClb.CertificateInput{CertId: common.StringPtr(tcCertId)}
|
||||||
|
if describeListenersResp.Response.Listeners[0].Certificate != nil && describeListenersResp.Response.Listeners[0].Certificate.SSLMode != nil {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = describeListenersResp.Response.Listeners[0].Certificate.SSLMode
|
||||||
|
modifyListenerReq.Certificate.CertCaId = describeListenersResp.Response.Listeners[0].Certificate.CertCaId
|
||||||
|
} else {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = common.StringPtr("UNIDIRECTIONAL")
|
||||||
|
}
|
||||||
|
modifyListenerResp, err := d.sdkClients.clb.ModifyListener(modifyListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改监听器属性", modifyListenerResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -3,37 +3,54 @@ package deployer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
"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"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentCOSDeployer struct {
|
type TencentCOSDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *tcSsl.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentCOSDeployer{
|
return &TencentCOSDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,68 +58,49 @@ func (d *TencentCOSDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) GetInfo() []string {
|
func (d *TencentCOSDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
tcRegion := d.option.DeployConfig.GetConfigAsString("region")
|
||||||
certId, err := d.uploadCert()
|
tcBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
||||||
if err != nil {
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
if tcBucket == "" {
|
||||||
|
return errors.New("`bucket` is required")
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
// 上传证书到 SSL
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 证书部署到 COS 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cos")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", tcRegion, tcBucket, tcDomain)})
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书,与CDN部署的上传方法一致。
|
func (d *TencentCOSDeployer) createSdkClient(secretId, secretKey string) (*tcSsl.Client, error) {
|
||||||
func (d *TencentCOSDeployer) uploadCert() (string, error) {
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf := profile.NewClientProfile()
|
client, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
return client, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) deploy(certId string) error {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
|
||||||
client, _ := ssl.NewClient(d.credential, getDeployString(d.option.DeployConfig, "region"), cpf)
|
|
||||||
|
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
|
||||||
request := ssl.NewDeployCertificateInstanceRequest()
|
|
||||||
|
|
||||||
request.CertificateId = common.StringPtr(certId)
|
|
||||||
request.ResourceType = common.StringPtr("cos")
|
|
||||||
request.Status = common.Int64Ptr(1)
|
|
||||||
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", getDeployString(d.option.DeployConfig, "region"), getDeployString(d.option.DeployConfig, "bucket"), domain)})
|
|
||||||
|
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
|
||||||
resp, err := client.DeployCertificateInstance(request)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
|
||||||
}
|
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,41 +2,61 @@ package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
xerrors "github.com/pkg/errors"
|
||||||
|
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"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentECDNDeployer struct {
|
type TencentECDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentECDNDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentECDNDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentECDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentECDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
clients, err := (&TencentECDNDeployer{}).createSdkClients(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentECDNDeployer{
|
return &TencentECDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,103 +64,91 @@ func (d *TencentECDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) GetInfo() []string {
|
func (d *TencentECDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) Deploy(ctx context.Context) error {
|
func (d *TencentECDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// 上传证书到 SSL
|
||||||
certId, err := d.uploadCert()
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
|
||||||
|
// 获取待部署的 ECDN 实例
|
||||||
|
// 如果是泛域名,根据证书匹配 ECDN 实例
|
||||||
|
aliInstanceIds := make([]string, 0)
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aliInstanceIds = domains
|
||||||
|
} else {
|
||||||
|
aliInstanceIds = append(aliInstanceIds, domain)
|
||||||
|
}
|
||||||
|
if len(aliInstanceIds) == 0 {
|
||||||
|
d.infos = append(d.infos, "没有要部署的 ECDN 实例")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书部署到 ECDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(aliInstanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) uploadCert() (string, error) {
|
func (d *TencentECDNDeployer) createSdkClients(secretId, secretKey string) (*tencentECDNDeployerSdkClients, error) {
|
||||||
cpf := profile.NewClientProfile()
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentECDNDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) deploy(certId string) error {
|
func (d *TencentECDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
cpf := profile.NewClientProfile()
|
// 获取证书中的可用域名
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("ecdn")
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
request := ssl.NewDeployCertificateInstanceRequest()
|
|
||||||
|
|
||||||
request.CertificateId = common.StringPtr(certId)
|
|
||||||
request.ResourceType = common.StringPtr("ecdn")
|
|
||||||
request.Status = common.Int64Ptr(1)
|
|
||||||
|
|
||||||
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
if strings.Contains(domain, "*") {
|
|
||||||
list, errGetList := d.getDomainList()
|
|
||||||
if errGetList != nil {
|
|
||||||
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
|
|
||||||
}
|
|
||||||
if list == nil || len(list) == 0 {
|
|
||||||
return fmt.Errorf("failed to get certificate domain list: empty list.")
|
|
||||||
}
|
|
||||||
request.InstanceIdList = common.StringPtrs(list)
|
|
||||||
} else { // 否则直接使用传入的域名
|
|
||||||
request.InstanceIdList = common.StringPtrs([]string{domain})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
|
||||||
resp, err := client.DeployCertificateInstance(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
}
|
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) getDomainList() ([]string, error) {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
|
|
||||||
client, _ := cdn.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
request := cdn.NewDescribeCertDomainsRequest()
|
|
||||||
|
|
||||||
cert := base64.StdEncoding.EncodeToString([]byte(d.option.Certificate.Certificate))
|
|
||||||
request.Cert = &cert
|
|
||||||
request.Product = common.StringPtr("ecdn")
|
|
||||||
|
|
||||||
response, err := client.DescribeCertDomains(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get domain list: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := make([]string, 0)
|
domains := make([]string, 0)
|
||||||
for _, domain := range response.Response.Domains {
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
domains = append(domains, *domain)
|
domains = append(domains, *domain)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return domains, nil
|
return domains, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,57 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
teo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901"
|
xerrors "github.com/pkg/errors"
|
||||||
"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"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
tcTeo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentTEODeployer struct {
|
type TencentTEODeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentTEODeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentTEODeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
teo *tcTeo.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentTEODeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentTEODeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
clients, err := (&TencentTEODeployer{}).createSdkClients(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentTEODeployer{
|
return &TencentTEODeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,69 +64,56 @@ func (d *TencentTEODeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentTEODeployer) GetInfo() []string {
|
func (d *TencentTEODeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentTEODeployer) Deploy(ctx context.Context) error {
|
func (d *TencentTEODeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
tcZoneId := d.option.DeployConfig.GetConfigAsString("zoneId")
|
||||||
certId, err := d.uploadCert()
|
if tcZoneId == "" {
|
||||||
if err != nil {
|
return xerrors.New("`zoneId` is required")
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
// 上传证书到 SSL
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://cloud.tencent.com/document/product/1552/80764
|
||||||
|
modifyHostsCertificateReq := tcTeo.NewModifyHostsCertificateRequest()
|
||||||
|
modifyHostsCertificateReq.ZoneId = common.StringPtr(tcZoneId)
|
||||||
|
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||||
|
modifyHostsCertificateReq.Hosts = common.StringPtrs(strings.Split(strings.ReplaceAll(d.option.Domain, "\r\n", "\n"), "\n"))
|
||||||
|
modifyHostsCertificateReq.ServerCertInfo = []*tcTeo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||||
|
modifyHostsCertificateResp, err := d.sdkClients.teo.ModifyHostsCertificate(modifyHostsCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已配置域名证书", modifyHostsCertificateResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentTEODeployer) uploadCert() (string, error) {
|
func (d *TencentTEODeployer) createSdkClients(secretId, secretKey string) (*tencentTEODeployerSdkClients, error) {
|
||||||
cpf := profile.NewClientProfile()
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
teoClient, err := tcTeo.NewClient(credential, "", profile.NewClientProfile())
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentTEODeployer) deploy(certId string) error {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "teo.tencentcloudapi.com"
|
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
|
||||||
client, _ := teo.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
|
||||||
request := teo.NewModifyHostsCertificateRequest()
|
|
||||||
|
|
||||||
request.ZoneId = common.StringPtr(getDeployString(d.option.DeployConfig, "zoneId"))
|
|
||||||
request.Mode = common.StringPtr("sslcert")
|
|
||||||
request.ServerCertInfo = []*teo.ServerCertInfo{{
|
|
||||||
CertId: common.StringPtr(certId),
|
|
||||||
}}
|
|
||||||
|
|
||||||
domains := strings.Split(strings.ReplaceAll(d.option.Domain, "\r\n", "\n"),"\n")
|
|
||||||
request.Hosts = common.StringPtrs(domains)
|
|
||||||
|
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
|
||||||
resp, err := client.ModifyHostsCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
return nil
|
return &tencentTEODeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
teo: teoClient,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
)
|
)
|
||||||
|
|
@ -27,7 +29,7 @@ func (d *WebhookDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetInfo() []string {
|
func (d *WebhookDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,26 +43,24 @@ type webhookData struct {
|
||||||
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.WebhookAccess{}
|
access := &domain.WebhookAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return fmt.Errorf("failed to parse hook access config: %w", err)
|
return xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &webhookData{
|
data := &webhookData{
|
||||||
Domain: d.option.Domain,
|
Domain: d.option.Domain,
|
||||||
Certificate: d.option.Certificate.Certificate,
|
Certificate: d.option.Certificate.Certificate,
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
PrivateKey: d.option.Certificate.PrivateKey,
|
||||||
Variables: getDeployVariables(d.option.DeployConfig),
|
Variables: d.option.DeployConfig.GetConfigAsVariables(),
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(data)
|
body, _ := json.Marshal(data)
|
||||||
|
|
||||||
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send hook request: %w", err)
|
return xerrors.Wrap(err, "failed to send webhook request")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("webhook response", string(resp)))
|
d.infos = append(d.infos, toStr("Webhook Response", string(resp)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type ApplyConfig struct {
|
type ApplyConfig struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|
@ -117,6 +120,33 @@ func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool)
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 以变量字典形式获取配置项。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 变量字典。
|
||||||
|
func (dc *DeployConfig) GetConfigAsVariables() map[string]string {
|
||||||
|
rs := make(map[string]string)
|
||||||
|
|
||||||
|
if dc.Config != nil {
|
||||||
|
value, ok := dc.Config["variables"]
|
||||||
|
if !ok {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs := make([]KV, 0)
|
||||||
|
bts, _ := json.Marshal(value)
|
||||||
|
if err := json.Unmarshal(bts, &kvs); err != nil {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range kvs {
|
||||||
|
rs[kv.Key] = kv.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
// GetDomain returns the domain from the deploy config
|
// GetDomain returns the domain from the deploy config
|
||||||
// if the domain is a wildcard domain, and wildcard is true, return the wildcard domain
|
// if the domain is a wildcard domain, and wildcard is true, return the wildcard domain
|
||||||
func (dc *DeployConfig) GetDomain(wildcard ...bool) string {
|
func (dc *DeployConfig) GetDomain(wildcard ...bool) string {
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,11 @@ func deploy(ctx context.Context, record *models.Record) error {
|
||||||
if err = deployer.Deploy(ctx); err != nil {
|
if err = deployer.Deploy(ctx); err != nil {
|
||||||
|
|
||||||
app.GetApp().Logger().Error("部署失败", "err", err)
|
app.GetApp().Logger().Error("部署失败", "err", err)
|
||||||
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
|
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfos()})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
|
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
|
||||||
Info: deployer.GetInfo(),
|
Info: deployer.GetInfos(),
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package uploader
|
package aliyuncas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cas20200407 "github.com/alibabacloud-go/cas-20200407/v3/client"
|
aliyunCas "github.com/alibabacloud-go/cas-20200407/v3/client"
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,28 +23,26 @@ type AliyunCASUploaderConfig struct {
|
||||||
|
|
||||||
type AliyunCASUploader struct {
|
type AliyunCASUploader struct {
|
||||||
config *AliyunCASUploaderConfig
|
config *AliyunCASUploaderConfig
|
||||||
sdkClient *cas20200407.Client
|
sdkClient *aliyunCas.Client
|
||||||
sdkRuntime *util.RuntimeOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunCASUploader(config *AliyunCASUploaderConfig) (Uploader, error) {
|
func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
|
||||||
client, err := (&AliyunCASUploader{}).createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.AccessKeySecret,
|
config.AccessKeySecret,
|
||||||
config.Region,
|
config.Region,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunCASUploader{
|
return &AliyunCASUploader{
|
||||||
config: config,
|
config: config,
|
||||||
sdkClient: client,
|
sdkClient: client,
|
||||||
sdkRuntime: &util.RuntimeOptions{},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
|
func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -56,25 +55,25 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
listUserCertificateOrderPage := int64(1)
|
listUserCertificateOrderPage := int64(1)
|
||||||
listUserCertificateOrderLimit := int64(50)
|
listUserCertificateOrderLimit := int64(50)
|
||||||
for {
|
for {
|
||||||
listUserCertificateOrderReq := &cas20200407.ListUserCertificateOrderRequest{
|
listUserCertificateOrderReq := &aliyunCas.ListUserCertificateOrderRequest{
|
||||||
CurrentPage: tea.Int64(listUserCertificateOrderPage),
|
CurrentPage: tea.Int64(listUserCertificateOrderPage),
|
||||||
ShowSize: tea.Int64(listUserCertificateOrderLimit),
|
ShowSize: tea.Int64(listUserCertificateOrderLimit),
|
||||||
OrderType: tea.String("CERT"),
|
OrderType: tea.String("CERT"),
|
||||||
}
|
}
|
||||||
listUserCertificateOrderResp, err := u.sdkClient.ListUserCertificateOrderWithOptions(listUserCertificateOrderReq, u.sdkRuntime)
|
listUserCertificateOrderResp, err := u.sdkClient.ListUserCertificateOrder(listUserCertificateOrderReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.ListUserCertificateOrder'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listUserCertificateOrderResp.Body.CertificateOrderList != nil {
|
if listUserCertificateOrderResp.Body.CertificateOrderList != nil {
|
||||||
for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList {
|
for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList {
|
||||||
if strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) {
|
if strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) {
|
||||||
getUserCertificateDetailReq := &cas20200407.GetUserCertificateDetailRequest{
|
getUserCertificateDetailReq := &aliyunCas.GetUserCertificateDetailRequest{
|
||||||
CertId: certDetail.CertificateId,
|
CertId: certDetail.CertificateId,
|
||||||
}
|
}
|
||||||
getUserCertificateDetailResp, err := u.sdkClient.GetUserCertificateDetailWithOptions(getUserCertificateDetailReq, u.sdkRuntime)
|
getUserCertificateDetailResp, err := u.sdkClient.GetUserCertificateDetail(getUserCertificateDetailReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.GetUserCertificateDetail'")
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSameCert bool
|
var isSameCert bool
|
||||||
|
|
@ -91,7 +90,7 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)),
|
CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)),
|
||||||
CertName: *certDetail.Name,
|
CertName: *certDetail.Name,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -116,29 +115,29 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
|
|
||||||
// 上传新证书
|
// 上传新证书
|
||||||
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-uploadusercertificate
|
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-uploadusercertificate
|
||||||
uploadUserCertificateReq := &cas20200407.UploadUserCertificateRequest{
|
uploadUserCertificateReq := &aliyunCas.UploadUserCertificateRequest{
|
||||||
Name: tea.String(certName),
|
Name: tea.String(certName),
|
||||||
Cert: tea.String(certPem),
|
Cert: tea.String(certPem),
|
||||||
Key: tea.String(privkeyPem),
|
Key: tea.String(privkeyPem),
|
||||||
}
|
}
|
||||||
uploadUserCertificateResp, err := u.sdkClient.UploadUserCertificateWithOptions(uploadUserCertificateReq, u.sdkRuntime)
|
uploadUserCertificateResp, err := u.sdkClient.UploadUserCertificate(uploadUserCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'cas.UploadUserCertificate': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.UploadUserCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
certId = fmt.Sprintf("%d", tea.Int64Value(uploadUserCertificateResp.Body.CertId))
|
certId = fmt.Sprintf("%d", tea.Int64Value(uploadUserCertificateResp.Body.CertId))
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certId,
|
CertId: certId,
|
||||||
CertName: certName,
|
CertName: certName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AliyunCASUploader) createSdkClient(accessKeyId, accessKeySecret, region string) (*cas20200407.Client, error) {
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +151,7 @@ func (u *AliyunCASUploader) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
client, err := cas20200407.NewClient(aConfig)
|
client, err := aliyunCas.NewClient(aConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package uploader
|
package aliyunslb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -8,11 +8,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
slb20140515 "github.com/alibabacloud-go/slb-20140515/v4/client"
|
aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,28 +25,26 @@ type AliyunSLBUploaderConfig struct {
|
||||||
|
|
||||||
type AliyunSLBUploader struct {
|
type AliyunSLBUploader struct {
|
||||||
config *AliyunSLBUploaderConfig
|
config *AliyunSLBUploaderConfig
|
||||||
sdkClient *slb20140515.Client
|
sdkClient *aliyunSlb.Client
|
||||||
sdkRuntime *util.RuntimeOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunSLBUploader(config *AliyunSLBUploaderConfig) (Uploader, error) {
|
func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
|
||||||
client, err := (&AliyunSLBUploader{}).createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.AccessKeySecret,
|
config.AccessKeySecret,
|
||||||
config.Region,
|
config.Region,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunSLBUploader{
|
return &AliyunSLBUploader{
|
||||||
config: config,
|
config: config,
|
||||||
sdkClient: client,
|
sdkClient: client,
|
||||||
sdkRuntime: &util.RuntimeOptions{},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
|
func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -54,12 +53,12 @@ func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
|
|
||||||
// 查询证书列表,避免重复上传
|
// 查询证书列表,避免重复上传
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeservercertificates
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeservercertificates
|
||||||
describeServerCertificatesReq := &slb20140515.DescribeServerCertificatesRequest{
|
describeServerCertificatesReq := &aliyunSlb.DescribeServerCertificatesRequest{
|
||||||
RegionId: tea.String(u.config.Region),
|
RegionId: tea.String(u.config.Region),
|
||||||
}
|
}
|
||||||
describeServerCertificatesResp, err := u.sdkClient.DescribeServerCertificatesWithOptions(describeServerCertificatesReq, u.sdkRuntime)
|
describeServerCertificatesResp, err := u.sdkClient.DescribeServerCertificates(describeServerCertificatesReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'slb.DescribeServerCertificates': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeServerCertificates'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil {
|
if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil {
|
||||||
|
|
@ -71,7 +70,7 @@ func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
strings.EqualFold(certX509.Subject.CommonName, *certDetail.CommonName)
|
strings.EqualFold(certX509.Subject.CommonName, *certDetail.CommonName)
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: *certDetail.ServerCertificateId,
|
CertId: *certDetail.ServerCertificateId,
|
||||||
CertName: *certDetail.ServerCertificateName,
|
CertName: *certDetail.ServerCertificateName,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -85,30 +84,30 @@ func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||||
|
|
||||||
// 上传新证书
|
// 上传新证书
|
||||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
|
||||||
uploadServerCertificateReq := &slb20140515.UploadServerCertificateRequest{
|
uploadServerCertificateReq := &aliyunSlb.UploadServerCertificateRequest{
|
||||||
RegionId: tea.String(u.config.Region),
|
RegionId: tea.String(u.config.Region),
|
||||||
ServerCertificateName: tea.String(certName),
|
ServerCertificateName: tea.String(certName),
|
||||||
ServerCertificate: tea.String(certPem),
|
ServerCertificate: tea.String(certPem),
|
||||||
PrivateKey: tea.String(privkeyPem),
|
PrivateKey: tea.String(privkeyPem),
|
||||||
}
|
}
|
||||||
uploadServerCertificateResp, err := u.sdkClient.UploadServerCertificateWithOptions(uploadServerCertificateReq, u.sdkRuntime)
|
uploadServerCertificateResp, err := u.sdkClient.UploadServerCertificate(uploadServerCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'slb.UploadServerCertificate': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'slb.UploadServerCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
certId = *uploadServerCertificateResp.Body.ServerCertificateId
|
certId = *uploadServerCertificateResp.Body.ServerCertificateId
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certId,
|
CertId: certId,
|
||||||
CertName: certName,
|
CertName: certName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AliyunSLBUploader) createSdkClient(accessKeyId, accessKeySecret, region string) (*slb20140515.Client, error) {
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +124,7 @@ func (u *AliyunSLBUploader) createSdkClient(accessKeyId, accessKeySecret, region
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
client, err := slb20140515.NewClient(aConfig)
|
client, err := aliyunSlb.NewClient(aConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package uploader
|
package huaweicloudelb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -13,7 +14,9 @@ import (
|
||||||
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
||||||
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
||||||
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
@ -29,14 +32,14 @@ type HuaweiCloudELBUploader struct {
|
||||||
sdkClient *hcElb.ElbClient
|
sdkClient *hcElb.ElbClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (Uploader, error) {
|
func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
|
||||||
client, err := (&HuaweiCloudELBUploader{}).createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.SecretAccessKey,
|
config.SecretAccessKey,
|
||||||
config.Region,
|
config.Region,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
return nil, xerrors.Wrap(err, "failed to create sdk client: %w")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HuaweiCloudELBUploader{
|
return &HuaweiCloudELBUploader{
|
||||||
|
|
@ -45,7 +48,7 @@ func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (Uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
|
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
newCert, err := x509.ParseCertificateFromPEM(certPem)
|
newCert, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -65,7 +68,7 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
}
|
}
|
||||||
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listCertificatesResp.Certificates != nil {
|
if listCertificatesResp.Certificates != nil {
|
||||||
|
|
@ -84,7 +87,7 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certDetail.Id,
|
CertId: certDetail.Id,
|
||||||
CertName: certDetail.Name,
|
CertName: certDetail.Name,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -105,9 +108,9 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
|
|
||||||
// 获取项目 ID
|
// 获取项目 ID
|
||||||
// REF: https://support.huaweicloud.com/api-iam/iam_06_0001.html
|
// REF: https://support.huaweicloud.com/api-iam/iam_06_0001.html
|
||||||
projectId, err := u.getSdkProjectId(u.config.Region, u.config.AccessKeyId, u.config.SecretAccessKey)
|
projectId, err := getSdkProjectId(u.config.AccessKeyId, u.config.SecretAccessKey, u.config.Region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get SDK project id: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get SDK project id")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成新证书名(需符合华为云命名规则)
|
// 生成新证书名(需符合华为云命名规则)
|
||||||
|
|
@ -128,18 +131,18 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
}
|
}
|
||||||
createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq)
|
createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'elb.CreateCertificate': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'elb.CreateCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
certId = createCertificateResp.Certificate.Id
|
certId = createCertificateResp.Certificate.Id
|
||||||
certName = createCertificateResp.Certificate.Name
|
certName = createCertificateResp.Certificate.Name
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certId,
|
CertId: certId,
|
||||||
CertName: certName,
|
CertName: certName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HuaweiCloudELBUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +172,7 @@ func (u *HuaweiCloudELBUploader) createSdkClient(accessKeyId, secretAccessKey, r
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HuaweiCloudELBUploader) getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
|
func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-north-4" // IAM 服务默认区域:华北四北京
|
region = "cn-north-4" // IAM 服务默认区域:华北四北京
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +210,7 @@ func (u *HuaweiCloudELBUploader) getSdkProjectId(accessKeyId, secretAccessKey, r
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
||||||
return "", fmt.Errorf("no project found")
|
return "", errors.New("no project found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (*response.Projects)[0].Id, nil
|
return (*response.Projects)[0].Id, nil
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package uploader
|
package huaweicloudscm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -9,7 +9,9 @@ import (
|
||||||
hcScm "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3"
|
hcScm "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3"
|
||||||
hcScmModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/model"
|
hcScmModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/model"
|
||||||
hcScmRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/region"
|
hcScmRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
@ -25,14 +27,14 @@ type HuaweiCloudSCMUploader struct {
|
||||||
sdkClient *hcScm.ScmClient
|
sdkClient *hcScm.ScmClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (Uploader, error) {
|
func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
|
||||||
client, err := (&HuaweiCloudSCMUploader{}).createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.SecretAccessKey,
|
config.SecretAccessKey,
|
||||||
config.Region,
|
config.Region,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HuaweiCloudSCMUploader{
|
return &HuaweiCloudSCMUploader{
|
||||||
|
|
@ -41,7 +43,7 @@ func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (Uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
|
func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -63,7 +65,7 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
}
|
}
|
||||||
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'scm.ListCertificates': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ListCertificates'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listCertificatesResp.Certificates != nil {
|
if listCertificatesResp.Certificates != nil {
|
||||||
|
|
@ -76,7 +78,7 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 {
|
if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ExportCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSameCert bool
|
var isSameCert bool
|
||||||
|
|
@ -93,7 +95,7 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certDetail.Id,
|
CertId: certDetail.Id,
|
||||||
CertName: certDetail.Name,
|
CertName: certDetail.Name,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -127,17 +129,17 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
|
||||||
}
|
}
|
||||||
importCertificateResp, err := u.sdkClient.ImportCertificate(importCertificateReq)
|
importCertificateResp, err := u.sdkClient.ImportCertificate(importCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'scm.ImportCertificate': %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ImportCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
certId = *importCertificateResp.CertificateId
|
certId = *importCertificateResp.CertificateId
|
||||||
return &UploadResult{
|
return &uploader.UploadResult{
|
||||||
CertId: certId,
|
CertId: certId,
|
||||||
CertName: certName,
|
CertName: certName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *HuaweiCloudSCMUploader) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcScm.ScmClient, error) {
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcScm.ScmClient, error) {
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "cn-north-4" // SCM 服务默认区域:华北四北京
|
region = "cn-north-4" // SCM 服务默认区域:华北四北京
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package qiniusslcert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QiniuSSLCertUploaderConfig struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QiniuSSLCertUploader struct {
|
||||||
|
config *QiniuSSLCertUploaderConfig
|
||||||
|
sdkClient *qiniuEx.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKey,
|
||||||
|
config.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &QiniuSSLCertUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合七牛云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
||||||
|
uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, privkeyPem, certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = uploadSslCertResp.CertID
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) {
|
||||||
|
credential := auth.New(accessKey, secretKey)
|
||||||
|
client := qiniuEx.NewClient(credential)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package tencentcloudssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudSSLUploaderConfig struct {
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudSSLUploader struct {
|
||||||
|
config *TencentCloudSSLUploaderConfig
|
||||||
|
sdkClient *tcSsl.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.SecretId,
|
||||||
|
config.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudSSLUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TencentCloudSSLUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/41665
|
||||||
|
uploadCertificateReq := tcSsl.NewUploadCertificateRequest()
|
||||||
|
uploadCertificateReq.CertificatePublicKey = common.StringPtr(certPem)
|
||||||
|
uploadCertificateReq.CertificatePrivateKey = common.StringPtr(privkeyPem)
|
||||||
|
uploadCertificateReq.Repeatable = common.BoolPtr(false)
|
||||||
|
uploadCertificateResp, err := u.sdkClient.UploadCertificate(uploadCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.UploadCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId := *uploadCertificateResp.Response.CertificateId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(secretId, secretKey string) (*tcSsl.Client, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
client, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package uploader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
|
||||||
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentCloudSSLUploaderConfig struct {
|
|
||||||
Region string `json:"region"`
|
|
||||||
SecretId string `json:"secretId"`
|
|
||||||
SecretKey string `json:"secretKey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TencentCloudSSLUploader struct {
|
|
||||||
config *TencentCloudSSLUploaderConfig
|
|
||||||
sdkClient *tcSsl.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentCloudSSLUploader(config *TencentCloudSSLUploaderConfig) (Uploader, error) {
|
|
||||||
client, err := (&TencentCloudSSLUploader{}).createSdkClient(
|
|
||||||
config.Region,
|
|
||||||
config.SecretId,
|
|
||||||
config.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentCloudSSLUploader{
|
|
||||||
config: config,
|
|
||||||
sdkClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *TencentCloudSSLUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
|
|
||||||
// 生成新证书名(需符合腾讯云命名规则)
|
|
||||||
var certId, certName string
|
|
||||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
|
||||||
|
|
||||||
// 上传新证书
|
|
||||||
// REF: https://cloud.tencent.com/document/product/400/41665
|
|
||||||
uploadCertificateReq := &tcSsl.UploadCertificateRequest{
|
|
||||||
Alias: cast.StringPtr(certName),
|
|
||||||
CertificatePublicKey: cast.StringPtr(certPem),
|
|
||||||
CertificatePrivateKey: cast.StringPtr(privkeyPem),
|
|
||||||
Repeatable: cast.BoolPtr(false),
|
|
||||||
}
|
|
||||||
uploadCertificateResp, err := u.sdkClient.UploadCertificate(uploadCertificateReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取证书详情
|
|
||||||
// REF: https://cloud.tencent.com/document/api/400/41673
|
|
||||||
//
|
|
||||||
// P.S. 上传重复证书会返回上一次的证书 ID,这里需要重新获取一遍证书名(https://github.com/usual2970/certimate/pull/227)
|
|
||||||
describeCertificateDetailReq := &tcSsl.DescribeCertificateDetailRequest{
|
|
||||||
CertificateId: uploadCertificateResp.Response.CertificateId,
|
|
||||||
}
|
|
||||||
describeCertificateDetailResp, err := u.sdkClient.DescribeCertificateDetail(describeCertificateDetailReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCertificateDetail': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certId = *describeCertificateDetailResp.Response.CertificateId
|
|
||||||
certName = *describeCertificateDetailResp.Response.Alias
|
|
||||||
return &UploadResult{
|
|
||||||
CertId: certId,
|
|
||||||
CertName: certName,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *TencentCloudSSLUploader) createSdkClient(region, secretId, secretKey string) (*tcSsl.Client, error) {
|
|
||||||
if region == "" {
|
|
||||||
region = "ap-guangzhou" // SSL 服务默认区域:广州
|
|
||||||
}
|
|
||||||
|
|
||||||
credential := common.NewCredential(secretId, secretKey)
|
|
||||||
client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 与 [WriteFile] 类似,但写入的是字符串内容。
|
// 与 [WriteFile] 类似,但写入的是字符串内容。
|
||||||
|
|
@ -33,18 +34,18 @@ func WriteFile(path string, data []byte) error {
|
||||||
|
|
||||||
err := os.MkdirAll(dir, os.ModePerm)
|
err := os.MkdirAll(dir, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create directory: %w", err)
|
return xerrors.Wrap(err, "failed to create directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(path)
|
file, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create file: %w", err)
|
return xerrors.Wrap(err, "failed to create file")
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
_, err = file.Write(data)
|
_, err = file.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write file: %w", err)
|
return xerrors.Wrap(err, "failed to write file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。
|
||||||
|
// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - a: 待比较的第一个 x509.Certificate 对象。
|
||||||
|
// - b: 待比较的第二个 x509.Certificate 对象。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 是否相同。
|
||||||
|
func EqualCertificate(a, b *x509.Certificate) bool {
|
||||||
|
return string(a.Signature) == string(b.Signature) &&
|
||||||
|
a.SignatureAlgorithm == b.SignatureAlgorithm &&
|
||||||
|
a.SerialNumber.String() == b.SerialNumber.String() &&
|
||||||
|
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
|
||||||
|
a.Subject.SerialNumber == b.Subject.SerialNumber
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkey: ecdsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
// - err: 错误。
|
||||||
|
func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) {
|
||||||
|
data, err := x509.MarshalECPrivateKey(privkey)
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Wrap(err, "failed to marshal EC private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
block := &pem.Block{
|
||||||
|
Type: "EC PRIVATE KEY",
|
||||||
|
Bytes: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pem.EncodeToMemory(block)), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - certPem: 证书 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - cert: x509.Certificate 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParseCertificateFromPEM(certPem string) (cert *x509.Certificate, err error) {
|
||||||
|
pemData := []byte(certPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 PEM 编码的私钥字符串解析并返回一个 ecdsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkey: ecdsa.PrivateKey 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParseECPrivateKeyFromPEM(privkeyPem string) (privkey *ecdsa.PrivateKey, err error) {
|
||||||
|
pemData := []byte(privkeyPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err = x509.ParseECPrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return privkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 PEM 编码的私钥字符串解析并返回一个 rsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkey: rsa.PrivateKey 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParsePKCS1PrivateKeyFromPEM(privkeyPem string) (privkey *rsa.PrivateKey, err error) {
|
||||||
|
pemData := []byte(privkeyPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return privkey, nil
|
||||||
|
}
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package x509
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。
|
|
||||||
// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。
|
|
||||||
//
|
|
||||||
// 入参:
|
|
||||||
// - a: 待比较的第一个 x509.Certificate 对象。
|
|
||||||
// - b: 待比较的第二个 x509.Certificate 对象。
|
|
||||||
//
|
|
||||||
// 出参:
|
|
||||||
// - 是否相同。
|
|
||||||
func EqualCertificate(a, b *x509.Certificate) bool {
|
|
||||||
return string(a.Signature) == string(b.Signature) &&
|
|
||||||
a.SignatureAlgorithm == b.SignatureAlgorithm &&
|
|
||||||
a.SerialNumber.String() == b.SerialNumber.String() &&
|
|
||||||
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
|
|
||||||
a.Subject.SerialNumber == b.Subject.SerialNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
|
|
||||||
//
|
|
||||||
// 入参:
|
|
||||||
// - certPem: 证书 PEM 内容。
|
|
||||||
//
|
|
||||||
// 出参:
|
|
||||||
// - cert: x509.Certificate 对象。
|
|
||||||
// - err: 错误。
|
|
||||||
func ParseCertificateFromPEM(certPem string) (cert *x509.Certificate, err error) {
|
|
||||||
pemData := []byte(certPem)
|
|
||||||
|
|
||||||
block, _ := pem.Decode(pemData)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode PEM block")
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err = x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 PEM 编码的私钥字符串解析并返回一个 ecdsa.PrivateKey 对象。
|
|
||||||
//
|
|
||||||
// 入参:
|
|
||||||
// - privkeyPem: 私钥 PEM 内容。
|
|
||||||
//
|
|
||||||
// 出参:
|
|
||||||
// - privkey: ecdsa.PrivateKey 对象。
|
|
||||||
// - err: 错误。
|
|
||||||
func ParseECPrivateKeyFromPEM(privkeyPem string) (privkey *ecdsa.PrivateKey, err error) {
|
|
||||||
pemData := []byte(privkeyPem)
|
|
||||||
|
|
||||||
block, _ := pem.Decode(pemData)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode PEM block")
|
|
||||||
}
|
|
||||||
|
|
||||||
privkey, err = x509.ParseECPrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return privkey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 PEM 编码的私钥字符串解析并返回一个 rsa.PrivateKey 对象。
|
|
||||||
//
|
|
||||||
// 入参:
|
|
||||||
// - privkeyPem: 私钥 PEM 内容。
|
|
||||||
//
|
|
||||||
// 出参:
|
|
||||||
// - privkey: rsa.PrivateKey 对象。
|
|
||||||
// - err: 错误。
|
|
||||||
func ParsePKCS1PrivateKeyFromPEM(privkeyPem string) (privkey *rsa.PrivateKey, err error) {
|
|
||||||
pemData := []byte(privkeyPem)
|
|
||||||
|
|
||||||
block, _ := pem.Decode(pemData)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode PEM block")
|
|
||||||
}
|
|
||||||
|
|
||||||
privkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return privkey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。
|
|
||||||
//
|
|
||||||
// 入参:
|
|
||||||
// - privkey: ecdsa.PrivateKey 对象。
|
|
||||||
//
|
|
||||||
// 出参:
|
|
||||||
// - privkeyPem: 私钥 PEM 内容。
|
|
||||||
// - err: 错误。
|
|
||||||
func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) {
|
|
||||||
data, err := x509.MarshalECPrivateKey(privkey)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to marshal EC private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
block := &pem.Block{
|
|
||||||
Type: "EC PRIVATE KEY",
|
|
||||||
Bytes: data,
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(pem.EncodeToMemory(block)), nil
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package huaweicloudcdnsdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core"
|
||||||
|
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
hcCdn.CdnClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(hcClient *core.HcHttpClient) *Client {
|
||||||
|
return &Client{
|
||||||
|
CdnClient: *hcCdn.NewCdnClient(hcClient),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UploadDomainMultiCertificatesEx(request *UpdateDomainMultiCertificatesExRequest) (*UpdateDomainMultiCertificatesExResponse, error) {
|
||||||
|
requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates()
|
||||||
|
|
||||||
|
if resp, err := c.HcClient.Sync(request, requestDef); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return resp.(*UpdateDomainMultiCertificatesExResponse), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package huaweicloudcdnsdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequestBodyContent struct {
|
||||||
|
hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
|
||||||
|
|
||||||
|
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求,可能需要等之后 SDK 更新。
|
||||||
|
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequestBody struct {
|
||||||
|
Https *UpdateDomainMultiCertificatesExRequestBodyContent `json:"https,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequest struct {
|
||||||
|
Body *UpdateDomainMultiCertificatesExRequestBody `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExResponse struct {
|
||||||
|
hcCdnModel.UpdateDomainMultiCertificatesResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UpdateDomainMultiCertificatesExRequestBodyContent) MergeConfig(src *hcCdnModel.ConfigsGetBody) *UpdateDomainMultiCertificatesExRequestBodyContent {
|
||||||
|
if src == nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去。
|
||||||
|
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化。
|
||||||
|
|
||||||
|
if *src.OriginProtocol == "follow" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(1)
|
||||||
|
} else if *src.OriginProtocol == "http" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(2)
|
||||||
|
} else if *src.OriginProtocol == "https" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.ForceRedirect != nil {
|
||||||
|
m.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
|
||||||
|
|
||||||
|
if src.ForceRedirect.Status == "on" {
|
||||||
|
m.ForceRedirectConfig.Switch = 1
|
||||||
|
m.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
|
||||||
|
} else {
|
||||||
|
m.ForceRedirectConfig.Switch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Https != nil {
|
||||||
|
if *src.Https.Http2Status == "on" {
|
||||||
|
m.Http2 = cast.Int32Ptr(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package qiniusdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const qiniuHost = "http://api.qiniu.com"
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
mac *auth.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(mac *auth.Credentials) *Client {
|
||||||
|
if mac == nil {
|
||||||
|
mac = auth.Default()
|
||||||
|
}
|
||||||
|
return &Client{mac: mac}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetDomainInfo(domain string) (*GetDomainInfoResponse, error) {
|
||||||
|
respBytes, err := c.sendReq(http.MethodGet, fmt.Sprintf("domain/%s", domain), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &GetDomainInfoResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) {
|
||||||
|
req := &ModifyDomainHttpsConfRequest{
|
||||||
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
|
CertID: certId,
|
||||||
|
ForceHttps: forceHttps,
|
||||||
|
Http2Enable: http2Enable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/httpsconf", domain), bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &ModifyDomainHttpsConfResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enable bool) (*EnableDomainHttpsResponse, error) {
|
||||||
|
req := &EnableDomainHttpsRequest{
|
||||||
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
|
CertID: certId,
|
||||||
|
ForceHttps: forceHttps,
|
||||||
|
Http2Enable: http2Enable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/sslize", domain), bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &EnableDomainHttpsResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UploadSslCert(name, commonName, pri, ca string) (*UploadSslCertResponse, error) {
|
||||||
|
req := &UploadSslCertRequest{
|
||||||
|
Name: name,
|
||||||
|
CommonName: commonName,
|
||||||
|
Pri: pri,
|
||||||
|
Ca: ca,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPost, "sslcert", bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &UploadSslCertResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendReq(method string, path string, body io.Reader) ([]byte, error) {
|
||||||
|
req := xhttp.BuildReq(fmt.Sprintf("%s/%s", qiniuHost, path), method, body, map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.mac.AddToken(auth.TokenQBox, req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := xhttp.ToRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer respBody.Close()
|
||||||
|
|
||||||
|
res, err := io.ReadAll(respBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package qiniusdk
|
||||||
|
|
||||||
|
type UploadSslCertRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CommonName string `json:"common_name"`
|
||||||
|
Pri string `json:"pri"`
|
||||||
|
Ca string `json:"ca"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadSslCertResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
CertID string `json:"certID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainInfoHttpsData struct {
|
||||||
|
CertID string `json:"certId"`
|
||||||
|
ForceHttps bool `json:"forceHttps"`
|
||||||
|
Http2Enable bool `json:"http2Enable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDomainInfoResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CName string `json:"cname"`
|
||||||
|
Https *DomainInfoHttpsData `json:"https"`
|
||||||
|
PareDomain string `json:"pareDomain"`
|
||||||
|
OperationType string `json:"operationType"`
|
||||||
|
OperatingState string `json:"operatingState"`
|
||||||
|
OperatingStateDesc string `json:"operatingStateDesc"`
|
||||||
|
CreateAt string `json:"createAt"`
|
||||||
|
ModifyAt string `json:"modifyAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyDomainHttpsConfRequest struct {
|
||||||
|
DomainInfoHttpsData
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyDomainHttpsConfResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableDomainHttpsRequest struct {
|
||||||
|
DomainInfoHttpsData
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableDomainHttpsResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RandStr 随机生成指定长度字符串
|
// Deprecated: this will be removed in the future.
|
||||||
|
// 随机生成指定长度字符串
|
||||||
func RandStr(n int) string {
|
func RandStr(n int) string {
|
||||||
seed := time.Now().UnixNano()
|
seed := time.Now().UnixNano()
|
||||||
source := rand.NewSource(seed)
|
source := rand.NewSource(seed)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext, type Context as ReactContext } from "react";
|
||||||
|
|
||||||
import { DeployConfig } from "@/domain/domain";
|
import { type DeployConfig } from "@/domain/domain";
|
||||||
|
|
||||||
type DeployEditContext = {
|
export type DeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]> = {
|
||||||
deploy: DeployConfig;
|
config: Omit<DeployConfig, "config"> & { config: T };
|
||||||
error: Record<string, string | undefined>;
|
setConfig: (config: Omit<DeployConfig, "config"> & { config: T }) => void;
|
||||||
setDeploy: (deploy: DeployConfig) => void;
|
|
||||||
setError: (error: Record<string, string | undefined>) => void;
|
errors: { [K in keyof T]?: string };
|
||||||
|
setErrors: (error: { [K in keyof T]?: string }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Context = createContext<DeployEditContext>({} as DeployEditContext);
|
export const Context = createContext<DeployEditContext>({} as DeployEditContext);
|
||||||
|
|
||||||
export const useDeployEditContext = () => {
|
export function useDeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]>() {
|
||||||
return useContext(Context);
|
return useContext<DeployEditContext<T>>(Context as unknown as ReactContext<DeployEditContext<T>>);
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import AccessEditDialog from "./AccessEditDialog";
|
import AccessEditDialog from "./AccessEditDialog";
|
||||||
import { Context as DeployEditContext } from "./DeployEdit";
|
import { Context as DeployEditContext, type DeployEditContext as DeployEditContextType } from "./DeployEdit";
|
||||||
import DeployToAliyunOSS from "./DeployToAliyunOSS";
|
import DeployToAliyunOSS from "./DeployToAliyunOSS";
|
||||||
import DeployToAliyunCDN from "./DeployToAliyunCDN";
|
import DeployToAliyunCDN from "./DeployToAliyunCDN";
|
||||||
import DeployToAliyunCLB from "./DeployToAliyunCLB";
|
import DeployToAliyunCLB from "./DeployToAliyunCLB";
|
||||||
|
|
@ -49,7 +49,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
type: "",
|
type: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [error, setError] = useState<Record<string, string | undefined>>({});
|
const [errors, setErrors] = useState<Record<string, string | undefined>>({});
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -66,10 +66,10 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDeployType(locDeployConfig.type);
|
setDeployType(locDeployConfig.type);
|
||||||
setError({});
|
setErrors({});
|
||||||
}, [locDeployConfig.type]);
|
}, [locDeployConfig.type]);
|
||||||
|
|
||||||
const setDeploy = useCallback(
|
const setConfig = useCallback(
|
||||||
(deploy: DeployConfig) => {
|
(deploy: DeployConfig) => {
|
||||||
if (deploy.type !== locDeployConfig.type) {
|
if (deploy.type !== locDeployConfig.type) {
|
||||||
setLocDeployConfig({ ...deploy, access: "", config: {} });
|
setLocDeployConfig({ ...deploy, access: "", config: {} });
|
||||||
|
|
@ -94,10 +94,10 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
|
|
||||||
const handleSaveClick = () => {
|
const handleSaveClick = () => {
|
||||||
// 验证数据
|
// 验证数据
|
||||||
const newError = { ...error };
|
const newError = { ...errors };
|
||||||
newError.type = locDeployConfig.type === "" ? t("domain.deployment.form.access.placeholder") : "";
|
newError.type = locDeployConfig.type === "" ? t("domain.deployment.form.access.placeholder") : "";
|
||||||
newError.access = locDeployConfig.access === "" ? t("domain.deployment.form.access.placeholder") : "";
|
newError.access = locDeployConfig.access === "" ? t("domain.deployment.form.access.placeholder") : "";
|
||||||
setError(newError);
|
setErrors(newError);
|
||||||
if (Object.values(newError).some((e) => !!e)) return;
|
if (Object.values(newError).some((e) => !!e)) return;
|
||||||
|
|
||||||
// 保存数据
|
// 保存数据
|
||||||
|
|
@ -108,7 +108,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
access: "",
|
access: "",
|
||||||
type: "",
|
type: "",
|
||||||
});
|
});
|
||||||
setError({});
|
setErrors({});
|
||||||
|
|
||||||
// 关闭弹框
|
// 关闭弹框
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
@ -171,10 +171,10 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
return (
|
return (
|
||||||
<DeployEditContext.Provider
|
<DeployEditContext.Provider
|
||||||
value={{
|
value={{
|
||||||
deploy: locDeployConfig,
|
config: locDeployConfig as DeployEditContextType["config"],
|
||||||
error: error,
|
setConfig: setConfig as DeployEditContextType["setConfig"],
|
||||||
setDeploy: setDeploy,
|
errors: errors as DeployEditContextType["errors"],
|
||||||
setError: setError,
|
setErrors: setErrors as DeployEditContextType["setErrors"],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
|
@ -199,7 +199,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
<Select
|
<Select
|
||||||
value={locDeployConfig.type}
|
value={locDeployConfig.type}
|
||||||
onValueChange={(val: string) => {
|
onValueChange={(val: string) => {
|
||||||
setDeploy({ ...locDeployConfig, type: val });
|
setConfig({ ...locDeployConfig, type: val });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="mt-2">
|
<SelectTrigger className="mt-2">
|
||||||
|
|
@ -220,7 +220,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<div className="text-red-500 text-sm mt-1">{error.type}</div>
|
<div className="text-red-500 text-sm mt-1">{errors.type}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 授权配置 */}
|
{/* 授权配置 */}
|
||||||
|
|
@ -241,7 +241,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
<Select
|
<Select
|
||||||
value={locDeployConfig.access}
|
value={locDeployConfig.access}
|
||||||
onValueChange={(val: string) => {
|
onValueChange={(val: string) => {
|
||||||
setDeploy({ ...locDeployConfig, access: val });
|
setConfig({ ...locDeployConfig, access: val });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="mt-2">
|
<SelectTrigger className="mt-2">
|
||||||
|
|
@ -262,7 +262,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<div className="text-red-500 text-sm mt-1">{error.access}</div>
|
<div className="text-red-500 text-sm mt-1">{errors.access}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 其他参数 */}
|
{/* 其他参数 */}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import DeployEditDialog from "./DeployEditDialog";
|
import DeployEditDialog from "./DeployEditDialog";
|
||||||
import { DeployConfig } from "@/domain/domain";
|
import { DeployConfig } from "@/domain/domain";
|
||||||
import { accessProvidersMap } from "@/domain/access";
|
import { accessProvidersMap } from "@/domain/access";
|
||||||
|
import { deployTargetsMap } from "@/domain/domain";
|
||||||
import { useConfigContext } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type DeployItemProps = {
|
type DeployItemProps = {
|
||||||
|
|
@ -18,10 +19,11 @@ type DeployItemProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
|
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
config: { accesses },
|
config: { accesses },
|
||||||
} = useConfigContext();
|
} = useConfigContext();
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const access = accesses.find((access) => access.id === item.access);
|
const access = accesses.find((access) => access.id === item.access);
|
||||||
|
|
||||||
|
|
@ -34,11 +36,7 @@ const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTypeName = () => {
|
const getTypeName = () => {
|
||||||
if (!access) {
|
return t(deployTargetsMap.get(item.type)?.name || "");
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return t(accessProvidersMap.get(access.configType)?.name || "");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,31 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToAliyunALBConfigParams = {
|
||||||
|
region?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
loadbalancerId?: string;
|
||||||
|
listenerId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToAliyunALB = () => {
|
const DeployToAliyunALB = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunALBConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
region: "cn-hangzhou",
|
region: "cn-hangzhou",
|
||||||
resourceType: "",
|
|
||||||
loadbalancerId: "",
|
|
||||||
listenerId: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
|
|
@ -50,25 +54,15 @@ const DeployToAliyunALB = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
region: res.error.errors.find((e) => e.path[0] === "region")?.message,
|
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
|
||||||
resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message,
|
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
|
||||||
loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message,
|
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
|
||||||
listenerId: res.error.errors.find((e) => e.path[0] === "listenerId")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: undefined,
|
|
||||||
resourceType: undefined,
|
|
||||||
loadbalancerId: undefined,
|
|
||||||
listenerId: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -77,28 +71,28 @@ const DeployToAliyunALB = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_alb_region.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_alb_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.region = e.target.value?.trim();
|
draft.config.region = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_alb_resource_type.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_alb_resource_type.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.resourceType}
|
value={config?.config?.resourceType}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.resourceType = value?.trim();
|
draft.config.resourceType = value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -111,46 +105,46 @@ const DeployToAliyunALB = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.resourceType}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.config?.resourceType === "loadbalancer" ? (
|
{config?.config?.resourceType === "loadbalancer" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_alb_loadbalancer_id.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_alb_loadbalancer_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.loadbalancerId}
|
value={config?.config?.loadbalancerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.loadbalancerId = e.target.value?.trim();
|
draft.config.loadbalancerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.loadbalancerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.config?.resourceType === "listener" ? (
|
{config?.config?.resourceType === "listener" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_alb_listener_id.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_alb_listener_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_alb_listener_id.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_alb_listener_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.listenerId}
|
value={config?.config?.listenerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.listenerId = e.target.value?.trim();
|
draft.config.listenerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.listenerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
||||||
|
|
@ -7,45 +7,42 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToAliyunCDNConfigParams = {
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToAliyunCDN = () => {
|
const DeployToAliyunCDN = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCDNConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {},
|
||||||
domain: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const formSchema = z.object({
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -53,33 +50,16 @@ const DeployToAliyunCDN = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.placeholder")}
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,24 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToAliyunCLBConfigParams = {
|
||||||
|
region?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
loadbalancerId?: string;
|
||||||
|
listenerPort?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToAliyunCLB = () => {
|
const DeployToAliyunCLB = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCLBConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
region: "cn-hangzhou",
|
region: "cn-hangzhou",
|
||||||
resourceType: "",
|
|
||||||
loadbalancerId: "",
|
|
||||||
listenerPort: "443",
|
listenerPort: "443",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -28,7 +33,7 @@ const DeployToAliyunCLB = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
|
|
@ -50,25 +55,15 @@ const DeployToAliyunCLB = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
region: res.error.errors.find((e) => e.path[0] === "region")?.message,
|
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
|
||||||
resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message,
|
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
|
||||||
loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message,
|
listenerPort: res.error?.errors?.find((e) => e.path[0] === "listenerPort")?.message,
|
||||||
listenerPort: res.error.errors.find((e) => e.path[0] === "listenerPort")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: undefined,
|
|
||||||
resourceType: undefined,
|
|
||||||
loadbalancerId: undefined,
|
|
||||||
listenerPort: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -77,28 +72,28 @@ const DeployToAliyunCLB = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_clb_region.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_clb_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.region = e.target.value?.trim();
|
draft.config.region = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_clb_resource_type.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_clb_resource_type.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.resourceType}
|
value={config?.config?.resourceType}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.resourceType = value?.trim();
|
draft.config.resourceType = value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -111,7 +106,7 @@ const DeployToAliyunCLB = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.resourceType}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -119,34 +114,34 @@ const DeployToAliyunCLB = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.loadbalancerId}
|
value={config?.config?.loadbalancerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.loadbalancerId = e.target.value?.trim();
|
draft.config.loadbalancerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.loadbalancerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.config?.resourceType === "listener" ? (
|
{config?.config?.resourceType === "listener" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_clb_listener_port.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_clb_listener_port.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_clb_listener_port.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_clb_listener_port.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.listenerPort}
|
value={config?.config?.listenerPort}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.listenerPort = e.target.value?.trim();
|
draft.config.listenerPort = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.listenerPort}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.listenerPort}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,31 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToAliyunNLBConfigParams = {
|
||||||
|
region?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
loadbalancerId?: string;
|
||||||
|
listenerId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToAliyunNLB = () => {
|
const DeployToAliyunNLB = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunNLBConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
region: "cn-hangzhou",
|
region: "cn-hangzhou",
|
||||||
resourceType: "",
|
|
||||||
loadbalancerId: "",
|
|
||||||
listenerId: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
|
|
@ -50,25 +54,15 @@ const DeployToAliyunNLB = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
region: res.error.errors.find((e) => e.path[0] === "region")?.message,
|
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
|
||||||
resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message,
|
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
|
||||||
loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message,
|
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
|
||||||
listenerId: res.error.errors.find((e) => e.path[0] === "listenerId")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: undefined,
|
|
||||||
resourceType: undefined,
|
|
||||||
loadbalancerId: undefined,
|
|
||||||
listenerId: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -77,28 +71,28 @@ const DeployToAliyunNLB = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_nlb_region.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_nlb_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.region = e.target.value?.trim();
|
draft.config.region = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_nlb_resource_type.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_nlb_resource_type.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.resourceType}
|
value={config?.config?.resourceType}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.resourceType = value?.trim();
|
draft.config.resourceType = value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -111,46 +105,46 @@ const DeployToAliyunNLB = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.resourceType}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.config?.resourceType === "loadbalancer" ? (
|
{config?.config?.resourceType === "loadbalancer" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_nlb_loadbalancer_id.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_nlb_loadbalancer_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.loadbalancerId}
|
value={config?.config?.loadbalancerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.loadbalancerId = e.target.value?.trim();
|
draft.config.loadbalancerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.loadbalancerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.config?.resourceType === "listener" ? (
|
{config?.config?.resourceType === "listener" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.aliyun_nlb_listener_id.label")}</Label>
|
<Label>{t("domain.deployment.form.aliyun_nlb_listener_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_nlb_listener_id.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_nlb_listener_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.listenerId}
|
value={config?.config?.listenerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.listenerId = e.target.value?.trim();
|
draft.config.listenerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.listenerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
||||||
|
|
@ -7,66 +7,54 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToAliyunOSSConfigParams = {
|
||||||
|
endpoint?: string;
|
||||||
|
bucket?: string;
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToAliyunOSS = () => {
|
const DeployToAliyunOSS = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunOSSConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
endpoint: "oss-cn-hangzhou.aliyuncs.com",
|
endpoint: "oss.aliyuncs.com",
|
||||||
bucket: "",
|
|
||||||
domain: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const formSchema = z.object({
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
endpoint: z.string().min(1, {
|
||||||
if (!resp.success) {
|
message: t("domain.deployment.form.aliyun_oss_endpoint.placeholder"),
|
||||||
setError({
|
}),
|
||||||
...error,
|
bucket: z.string().min(1, {
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const resp = bucketSchema.safeParse(data.config?.bucket);
|
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
|
||||||
message: t("common.errmsg.domain_invalid"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const bucketSchema = z.string().min(1, {
|
|
||||||
message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
|
message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
|
||||||
|
}),
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
endpoint: res.error?.errors?.find((e) => e.path[0] === "endpoint")?.message,
|
||||||
|
bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -74,20 +62,16 @@ const DeployToAliyunOSS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_oss_endpoint.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_oss_endpoint.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.endpoint}
|
value={config?.config?.endpoint}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const newData = produce(data, (draft) => {
|
draft.config.endpoint = e.target.value?.trim();
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.endpoint = temp;
|
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.endpoint}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.endpoint}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -95,33 +79,16 @@ const DeployToAliyunOSS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.aliyun_oss_bucket.placeholder")}
|
placeholder={t("domain.deployment.form.aliyun_oss_bucket.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.bucket}
|
value={config?.config?.bucket}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = bucketSchema.safeParse(temp);
|
draft.config.bucket = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.bucket = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.bucket}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -129,33 +96,16 @@ const DeployToAliyunOSS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.label")}
|
placeholder={t("domain.deployment.form.domain.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,63 +7,66 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToHuaweiCloudCDNConfigParams = {
|
||||||
|
region?: string;
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToHuaweiCloudCDN = () => {
|
const DeployToHuaweiCloudCDN = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudCDNConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
region: "cn-north-1",
|
region: "cn-north-1",
|
||||||
domain: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const formSchema = z.object({
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
region: z.string().min(1, {
|
||||||
if (!resp.success) {
|
message: t("domain.deployment.form.huaweicloud_cdn_region.placeholder"),
|
||||||
setError({
|
}),
|
||||||
...error,
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.huaweicloud_elb_region.label")}</Label>
|
<Label>{t("domain.deployment.form.huaweicloud_cdn_region.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
|
placeholder={t("domain.deployment.form.huaweicloud_cdn_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.region = e.target.value?.trim();
|
draft.config.region = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -71,16 +74,16 @@ const DeployToHuaweiCloudCDN = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.placeholder")}
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.domain = e.target.value?.trim();
|
draft.config.domain = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,34 +8,40 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
const DeployToHuaweiCloudCDN = () => {
|
type DeployToHuaweiCloudELBConfigParams = {
|
||||||
|
region?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
certificateId?: string;
|
||||||
|
loadbalancerId?: string;
|
||||||
|
listenerId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployToHuaweiCloudELB = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudELBConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
region: "cn-north-1",
|
region: "cn-north-1",
|
||||||
resourceType: "",
|
|
||||||
certificateId: "",
|
|
||||||
loadbalancerId: "",
|
|
||||||
listenerId: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")),
|
region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")),
|
||||||
resourceType: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder")),
|
resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], {
|
||||||
|
message: t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder"),
|
||||||
|
}),
|
||||||
certificateId: z.string().optional(),
|
certificateId: z.string().optional(),
|
||||||
loadbalancerId: z.string().optional(),
|
loadbalancerId: z.string().optional(),
|
||||||
listenerId: z.string().optional(),
|
listenerId: z.string().optional(),
|
||||||
|
|
@ -54,27 +60,16 @@ const DeployToHuaweiCloudCDN = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
region: res.error.errors.find((e) => e.path[0] === "region")?.message,
|
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
|
||||||
resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message,
|
certificateId: res.error?.errors?.find((e) => e.path[0] === "certificateId")?.message,
|
||||||
certificateId: res.error.errors.find((e) => e.path[0] === "certificateId")?.message,
|
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
|
||||||
loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message,
|
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
|
||||||
listenerId: res.error.errors.find((e) => e.path[0] === "listenerId")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: undefined,
|
|
||||||
resourceType: undefined,
|
|
||||||
certificateId: undefined,
|
|
||||||
loadbalancerId: undefined,
|
|
||||||
listenerId: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -83,28 +78,28 @@ const DeployToHuaweiCloudCDN = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
|
placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.region = e.target.value?.trim();
|
draft.config.region = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.huaweicloud_elb_resource_type.label")}</Label>
|
<Label>{t("domain.deployment.form.huaweicloud_elb_resource_type.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.resourceType}
|
value={config?.config?.resourceType}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.resourceType = value;
|
draft.config.resourceType = value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -118,67 +113,67 @@ const DeployToHuaweiCloudCDN = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.resourceType}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.config?.resourceType === "certificate" ? (
|
{config?.config?.resourceType === "certificate" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.huaweicloud_elb_certificate_id.label")}</Label>
|
<Label>{t("domain.deployment.form.huaweicloud_elb_certificate_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder")}
|
placeholder={t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.certificateId}
|
value={config?.config?.certificateId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certificateId = e.target.value?.trim();
|
draft.config.certificateId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.certificateId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.certificateId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.config?.resourceType === "loadbalancer" ? (
|
{config?.config?.resourceType === "loadbalancer" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")}</Label>
|
<Label>{t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder")}
|
placeholder={t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.loadbalancerId}
|
value={config?.config?.loadbalancerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.loadbalancerId = e.target.value?.trim();
|
draft.config.loadbalancerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.loadbalancerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.config?.resourceType === "listener" ? (
|
{config?.config?.resourceType === "listener" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.huaweicloud_elb_listener_id.label")}</Label>
|
<Label>{t("domain.deployment.form.huaweicloud_elb_listener_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder")}
|
placeholder={t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.listenerId}
|
value={config?.config?.listenerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.listenerId = e.target.value?.trim();
|
draft.config.listenerId = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.listenerId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
@ -187,4 +182,4 @@ const DeployToHuaweiCloudCDN = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeployToHuaweiCloudCDN;
|
export default DeployToHuaweiCloudELB;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { z } from "zod";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToKubernetesSecretConfigParams = {
|
||||||
|
namespace?: string;
|
||||||
|
secretName?: string;
|
||||||
|
secretDataKeyForCrt?: string;
|
||||||
|
secretDataKeyForKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToKubernetesSecret = () => {
|
const DeployToKubernetesSecret = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToKubernetesSecretConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
namespace: "default",
|
namespace: "default",
|
||||||
secretName: "",
|
|
||||||
secretDataKeyForCrt: "tls.crt",
|
secretDataKeyForCrt: "tls.crt",
|
||||||
secretDataKeyForKey: "tls.key",
|
secretDataKeyForKey: "tls.key",
|
||||||
},
|
},
|
||||||
|
|
@ -26,9 +33,35 @@ const DeployToKubernetesSecret = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
namespace: z.string().min(1, {
|
||||||
|
message: t("domain.deployment.form.k8s_namespace.placeholder"),
|
||||||
|
}),
|
||||||
|
secretName: z.string().min(1, {
|
||||||
|
message: t("domain.deployment.form.k8s_secret_name.placeholder"),
|
||||||
|
}),
|
||||||
|
secretDataKeyForCrt: z.string().min(1, {
|
||||||
|
message: t("domain.deployment.form.k8s_secret_data_key_for_crt.placeholder"),
|
||||||
|
}),
|
||||||
|
secretDataKeyForKey: z.string().min(1, {
|
||||||
|
message: t("domain.deployment.form.k8s_secret_data_key_for_key.placeholder"),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
namespace: res.error?.errors?.find((e) => e.path[0] === "namespace")?.message,
|
||||||
|
secretName: res.error?.errors?.find((e) => e.path[0] === "secretName")?.message,
|
||||||
|
secretDataKeyForCrt: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForCrt")?.message,
|
||||||
|
secretDataKeyForKey: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForKey")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -37,13 +70,13 @@ const DeployToKubernetesSecret = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.k8s_namespace.label")}
|
placeholder={t("domain.deployment.form.k8s_namespace.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.namespace}
|
value={config?.config?.namespace}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.namespace = e.target.value;
|
draft.config.namespace = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -53,13 +86,13 @@ const DeployToKubernetesSecret = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.k8s_secret_name.label")}
|
placeholder={t("domain.deployment.form.k8s_secret_name.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.secretName}
|
value={config?.config?.secretName}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.secretName = e.target.value;
|
draft.config.secretName = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -69,13 +102,13 @@ const DeployToKubernetesSecret = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}
|
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.secretDataKeyForCrt}
|
value={config?.config?.secretDataKeyForCrt}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.secretDataKeyForCrt = e.target.value;
|
draft.config.secretDataKeyForCrt = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -85,13 +118,13 @@ const DeployToKubernetesSecret = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_key.label")}
|
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_key.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.secretDataKeyForKey}
|
value={config?.config?.secretDataKeyForKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.secretDataKeyForKey = e.target.value;
|
draft.config.secretDataKeyForKey = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,40 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type DeployToLocalConfigParams = {
|
||||||
|
format?: string;
|
||||||
|
certPath?: string;
|
||||||
|
keyPath?: string;
|
||||||
|
pfxPassword?: string;
|
||||||
|
jksAlias?: string;
|
||||||
|
jksKeypass?: string;
|
||||||
|
jksStorepass?: string;
|
||||||
|
shell?: string;
|
||||||
|
preCommand?: string;
|
||||||
|
command?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToLocal = () => {
|
const DeployToLocal = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToLocalConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
format: "pem",
|
format: "pem",
|
||||||
certPath: "/etc/nginx/ssl/nginx.crt",
|
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||||
keyPath: "/etc/nginx/ssl/nginx.key",
|
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||||
pfxPassword: "",
|
|
||||||
jksAlias: "",
|
|
||||||
jksKeypass: "",
|
|
||||||
jksStorepass: "",
|
|
||||||
shell: "sh",
|
shell: "sh",
|
||||||
preCommand: "",
|
|
||||||
command: "sudo service nginx reload",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
|
|
@ -86,68 +93,55 @@ const DeployToLocal = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
|
||||||
format: res.error.errors.find((e) => e.path[0] === "format")?.message,
|
certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
|
||||||
certPath: res.error.errors.find((e) => e.path[0] === "certPath")?.message,
|
keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
|
||||||
keyPath: res.error.errors.find((e) => e.path[0] === "keyPath")?.message,
|
pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
|
||||||
pfxPassword: res.error.errors.find((e) => e.path[0] === "pfxPassword")?.message,
|
jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
|
||||||
jksAlias: res.error.errors.find((e) => e.path[0] === "jksAlias")?.message,
|
jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
|
||||||
jksKeypass: res.error.errors.find((e) => e.path[0] === "jksKeypass")?.message,
|
jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
|
||||||
jksStorepass: res.error.errors.find((e) => e.path[0] === "jksStorepass")?.message,
|
shell: res.error?.errors?.find((e) => e.path[0] === "shell")?.message,
|
||||||
shell: res.error.errors.find((e) => e.path[0] === "shell")?.message,
|
preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
|
||||||
preCommand: res.error.errors.find((e) => e.path[0] === "preCommand")?.message,
|
command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
|
||||||
command: res.error.errors.find((e) => e.path[0] === "command")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
format: undefined,
|
|
||||||
certPath: undefined,
|
|
||||||
keyPath: undefined,
|
|
||||||
pfxPassword: undefined,
|
|
||||||
jksAlias: undefined,
|
|
||||||
jksKeypass: undefined,
|
|
||||||
jksStorepass: undefined,
|
|
||||||
shell: undefined,
|
|
||||||
preCommand: undefined,
|
|
||||||
command: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.config?.format === "pem") {
|
if (config.config?.format === "pem") {
|
||||||
if (/(.pfx|.jks)$/.test(data.config.certPath)) {
|
if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.pfx|.jks)$/, ".crt");
|
draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.config?.format === "pfx") {
|
} else if (config.config?.format === "pfx") {
|
||||||
if (/(.crt|.jks)$/.test(data.config.certPath)) {
|
if (/(.crt|.jks)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.crt|.jks)$/, ".pfx");
|
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.config?.format === "jks") {
|
} else if (config.config?.format === "jks") {
|
||||||
if (/(.crt|.pfx)$/.test(data.config.certPath)) {
|
if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.crt|.pfx)$/, ".jks");
|
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data.config?.format]);
|
}, [config.config?.format]);
|
||||||
|
|
||||||
const getOptionCls = (val: string) => {
|
const getOptionCls = (val: string) => {
|
||||||
if (data.config?.shell === val) {
|
if (config.config?.shell === val) {
|
||||||
return "border-primary dark:border-primary";
|
return "border-primary dark:border-primary";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,18 +152,20 @@ const DeployToLocal = () => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "reload_nginx":
|
case "reload_nginx":
|
||||||
{
|
{
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.shell = "sh";
|
draft.config.shell = "sh";
|
||||||
draft.config.command = "sudo service nginx reload";
|
draft.config.command = "sudo service nginx reload";
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "binding_iis":
|
case "binding_iis":
|
||||||
{
|
{
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.shell = "powershell";
|
draft.config.shell = "powershell";
|
||||||
draft.config.command = `
|
draft.config.command = `
|
||||||
|
|
@ -201,14 +197,15 @@ $binding.AddSslCertificate($thumbprint, "My")
|
||||||
# 删除目录下的证书文件
|
# 删除目录下的证书文件
|
||||||
Remove-Item -Path "$pfxPath" -Force
|
Remove-Item -Path "$pfxPath" -Force
|
||||||
`.trim();
|
`.trim();
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "binding_netsh":
|
case "binding_netsh":
|
||||||
{
|
{
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.shell = "powershell";
|
draft.config.shell = "powershell";
|
||||||
draft.config.command = `
|
draft.config.command = `
|
||||||
|
|
@ -232,8 +229,8 @@ netsh http add sslcert ipport=$addr certhash=$thumbprint
|
||||||
# 删除目录下的证书文件
|
# 删除目录下的证书文件
|
||||||
Remove-Item -Path "$pfxPath" -Force
|
Remove-Item -Path "$pfxPath" -Force
|
||||||
`.trim();
|
`.trim();
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -245,13 +242,13 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.format}
|
value={config?.config?.format}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.format = value;
|
draft.config.format = value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -265,7 +262,7 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.format}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.format}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -273,77 +270,77 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_cert_path.label")}
|
placeholder={t("domain.deployment.form.file_cert_path.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.certPath}
|
value={config?.config?.certPath}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = e.target.value?.trim();
|
draft.config.certPath = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.certPath}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.config?.format === "pem" ? (
|
{config.config?.format === "pem" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.keyPath}
|
value={config?.config?.keyPath}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.keyPath = e.target.value?.trim();
|
draft.config.keyPath = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data.config?.format === "pfx" ? (
|
{config.config?.format === "pfx" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.pfxPassword}
|
value={config?.config?.pfxPassword}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.pfxPassword = e.target.value?.trim();
|
draft.config.pfxPassword = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.pfxPassword}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data.config?.format === "jks" ? (
|
{config.config?.format === "jks" ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
|
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksAlias}
|
value={config?.config?.jksAlias}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksAlias = e.target.value?.trim();
|
draft.config.jksAlias = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksAlias}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -351,16 +348,16 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksKeypass}
|
value={config?.config?.jksKeypass}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksKeypass = e.target.value?.trim();
|
draft.config.jksKeypass = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksKeypass}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -368,16 +365,16 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksStorepass}
|
value={config?.config?.jksStorepass}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksStorepass = e.target.value?.trim();
|
draft.config.jksStorepass = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksStorepass}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -388,13 +385,13 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
<Label>{t("domain.deployment.form.shell.label")}</Label>
|
<Label>{t("domain.deployment.form.shell.label")}</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
className="flex mt-1"
|
className="flex mt-1"
|
||||||
value={data?.config?.shell}
|
value={config?.config?.shell}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.shell = val;
|
draft.config.shell = val;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
@ -422,24 +419,24 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.shell}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.shell}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
|
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
value={data?.config?.preCommand}
|
value={config?.config?.preCommand}
|
||||||
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
|
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.preCommand = e.target.value;
|
draft.config.preCommand = e.target.value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.preCommand}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -464,17 +461,17 @@ Remove-Item -Path "$pfxPath" -Force
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
value={data?.config?.command}
|
value={config?.config?.command}
|
||||||
placeholder={t("domain.deployment.form.shell_command.placeholder")}
|
placeholder={t("domain.deployment.form.shell_command.placeholder")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.command = e.target.value;
|
draft.config.command = e.target.value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.command}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.command}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -7,45 +7,42 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToQiniuCDNConfigParams = {
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToQiniuCDN = () => {
|
const DeployToQiniuCDN = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToQiniuCDNConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {},
|
||||||
domain: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const formSchema = z.object({
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -53,33 +50,16 @@ const DeployToQiniuCDN = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.placeholder")}
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,32 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToSSHConfigParams = {
|
||||||
|
format?: string;
|
||||||
|
certPath?: string;
|
||||||
|
keyPath?: string;
|
||||||
|
pfxPassword?: string;
|
||||||
|
jksAlias?: string;
|
||||||
|
jksKeypass?: string;
|
||||||
|
jksStorepass?: string;
|
||||||
|
shell?: string;
|
||||||
|
preCommand?: string;
|
||||||
|
command?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToSSH = () => {
|
const DeployToSSH = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToSSHConfigParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data.id) {
|
if (!config.id) {
|
||||||
setDeploy({
|
setConfig({
|
||||||
...data,
|
...config,
|
||||||
config: {
|
config: {
|
||||||
format: "pem",
|
format: "pem",
|
||||||
certPath: "/etc/nginx/ssl/nginx.crt",
|
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||||
keyPath: "/etc/nginx/ssl/nginx.key",
|
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||||
pfxPassword: "",
|
|
||||||
jksAlias: "",
|
|
||||||
jksKeypass: "",
|
|
||||||
jksStorepass: "",
|
|
||||||
preCommand: "",
|
|
||||||
command: "sudo service nginx reload",
|
command: "sudo service nginx reload",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -34,7 +42,7 @@ const DeployToSSH = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
|
|
@ -79,63 +87,51 @@ const DeployToSSH = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(data.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!res.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
|
||||||
format: res.error.errors.find((e) => e.path[0] === "format")?.message,
|
certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
|
||||||
certPath: res.error.errors.find((e) => e.path[0] === "certPath")?.message,
|
keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
|
||||||
keyPath: res.error.errors.find((e) => e.path[0] === "keyPath")?.message,
|
pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
|
||||||
pfxPassword: res.error.errors.find((e) => e.path[0] === "pfxPassword")?.message,
|
jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
|
||||||
jksAlias: res.error.errors.find((e) => e.path[0] === "jksAlias")?.message,
|
jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
|
||||||
jksKeypass: res.error.errors.find((e) => e.path[0] === "jksKeypass")?.message,
|
jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
|
||||||
jksStorepass: res.error.errors.find((e) => e.path[0] === "jksStorepass")?.message,
|
preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
|
||||||
preCommand: res.error.errors.find((e) => e.path[0] === "preCommand")?.message,
|
command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
|
||||||
command: res.error.errors.find((e) => e.path[0] === "command")?.message,
|
|
||||||
});
|
});
|
||||||
} else {
|
}, [config]);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
format: undefined,
|
|
||||||
certPath: undefined,
|
|
||||||
keyPath: undefined,
|
|
||||||
pfxPassword: undefined,
|
|
||||||
jksAlias: undefined,
|
|
||||||
jksKeypass: undefined,
|
|
||||||
jksStorepass: undefined,
|
|
||||||
preCommand: undefined,
|
|
||||||
command: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.config?.format === "pem") {
|
if (config.config?.format === "pem") {
|
||||||
if (/(.pfx|.jks)$/.test(data.config.certPath)) {
|
if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.pfx|.jks)$/, ".crt");
|
draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.config?.format === "pfx") {
|
} else if (config.config?.format === "pfx") {
|
||||||
if (/(.crt|.jks)$/.test(data.config.certPath)) {
|
if (/(.crt|.jks)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.crt|.jks)$/, ".pfx");
|
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.config?.format === "jks") {
|
} else if (config.config?.format === "jks") {
|
||||||
if (/(.crt|.pfx)$/.test(data.config.certPath)) {
|
if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
|
||||||
const newData = produce(data, (draft) => {
|
setConfig(
|
||||||
|
produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = data.config!.certPath.replace(/(.crt|.pfx)$/, ".jks");
|
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
|
||||||
});
|
})
|
||||||
setDeploy(newData);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data.config?.format]);
|
}, [config.config?.format]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -143,13 +139,13 @@ const DeployToSSH = () => {
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={data?.config?.format}
|
value={config?.config?.format}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.format = value;
|
draft.config.format = value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
|
@ -163,7 +159,7 @@ const DeployToSSH = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.format}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.format}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -171,77 +167,77 @@ const DeployToSSH = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_cert_path.label")}
|
placeholder={t("domain.deployment.form.file_cert_path.label")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.certPath}
|
value={config?.config?.certPath}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.certPath = e.target.value?.trim();
|
draft.config.certPath = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.certPath}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.config?.format === "pem" ? (
|
{config.config?.format === "pem" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.keyPath}
|
value={config?.config?.keyPath}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.keyPath = e.target.value?.trim();
|
draft.config.keyPath = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data.config?.format === "pfx" ? (
|
{config.config?.format === "pfx" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.pfxPassword}
|
value={config?.config?.pfxPassword}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.pfxPassword = e.target.value?.trim();
|
draft.config.pfxPassword = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.pfxPassword}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data.config?.format === "jks" ? (
|
{config.config?.format === "jks" ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
|
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksAlias}
|
value={config?.config?.jksAlias}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksAlias = e.target.value?.trim();
|
draft.config.jksAlias = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksAlias}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -249,16 +245,16 @@ const DeployToSSH = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksKeypass}
|
value={config?.config?.jksKeypass}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksKeypass = e.target.value?.trim();
|
draft.config.jksKeypass = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksKeypass}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -266,16 +262,16 @@ const DeployToSSH = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
|
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.jksStorepass}
|
value={config?.config?.jksStorepass}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.jksStorepass = e.target.value?.trim();
|
draft.config.jksStorepass = e.target.value?.trim();
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.jksStorepass}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -286,34 +282,34 @@ const DeployToSSH = () => {
|
||||||
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
|
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
value={data?.config?.preCommand}
|
value={config?.config?.preCommand}
|
||||||
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
|
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.preCommand = e.target.value;
|
draft.config.preCommand = e.target.value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.preCommand}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.shell_command.label")}</Label>
|
<Label>{t("domain.deployment.form.shell_command.label")}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
value={data?.config?.command}
|
value={config?.config?.command}
|
||||||
placeholder={t("domain.deployment.form.shell_command.placeholder")}
|
placeholder={t("domain.deployment.form.shell_command.placeholder")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
draft.config ??= {};
|
draft.config ??= {};
|
||||||
draft.config.command = e.target.value;
|
draft.config.command = e.target.value;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.command}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.command}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,42 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToTencentCDNParams = {
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToTencentCDN = () => {
|
const DeployToTencentCDN = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCDNParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
if (!config.id) {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
config: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
setErrors({});
|
||||||
if (!resp.success) {
|
}, []);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
const formSchema = z.object({
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -42,33 +50,16 @@ const DeployToTencentCDN = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.placeholder")}
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,106 +5,80 @@ import { produce } from "immer";
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
const DeployToTencentCLB = () => {
|
type DeployToTencentCLBParams = {
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
region?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
loadbalancerId?: string;
|
||||||
|
listenerId?: string;
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployToTencentCLB = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCLBParams>();
|
||||||
setError({});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
if (!config.id) {
|
||||||
if (!resp.success) {
|
setConfig({
|
||||||
setError({
|
...config,
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const clbIdresp = clbIdSchema.safeParse(data.config?.clbId);
|
|
||||||
if (!clbIdresp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
clbId: JSON.parse(clbIdresp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
clbId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const lsnIdresp = lsnIdSchema.safeParse(data.config?.lsnId);
|
|
||||||
if (!lsnIdresp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
lsnId: JSON.parse(lsnIdresp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
lsnId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const regionResp = regionSchema.safeParse(data.config?.region);
|
|
||||||
if (!regionResp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: JSON.parse(regionResp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data.id) {
|
|
||||||
setDeploy({
|
|
||||||
...data,
|
|
||||||
config: {
|
config: {
|
||||||
lsnId: "",
|
region: "ap-guangzhou",
|
||||||
clbId: "",
|
|
||||||
domain: "",
|
|
||||||
region: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const regionSchema = z.string().regex(/^ap-[a-z]+$/, {
|
useEffect(() => {
|
||||||
message: t("domain.deployment.form.tencent_clb_region.placeholder"),
|
setErrors({});
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
const formSchema = z
|
||||||
|
.object({
|
||||||
|
region: z.string().min(1, t("domain.deployment.form.tencent_clb_region.placeholder")),
|
||||||
|
resourceType: z.union([z.literal("ssl-deploy"), z.literal("loadbalancer"), z.literal("listener"), z.literal("ruledomain")], {
|
||||||
|
message: t("domain.deployment.form.tencent_clb_resource_type.placeholder"),
|
||||||
|
}),
|
||||||
|
loadbalancerId: z.string().min(1, t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")),
|
||||||
|
listenerId: z.string().optional(),
|
||||||
|
domain: z.string().regex(/^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
switch (data.resourceType) {
|
||||||
|
case "ssl-deploy":
|
||||||
|
case "listener":
|
||||||
|
case "ruledomain":
|
||||||
|
return !!data.listenerId?.trim();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: t("domain.deployment.form.tencent_clb_listener_id.placeholder"),
|
||||||
|
path: ["listenerId"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine((data) => (data.resourceType === "ruledomain" ? !!data.domain?.trim() : true), {
|
||||||
|
message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
|
||||||
|
path: ["domain"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const clbIdSchema = z.string().regex(/^lb-[a-zA-Z0-9]{8}$/, {
|
useEffect(() => {
|
||||||
message: t("domain.deployment.form.tencent_clb_id.placeholder"),
|
const res = formSchema.safeParse(config.config);
|
||||||
});
|
setErrors({
|
||||||
|
...errors,
|
||||||
const lsnIdSchema = z.string().regex(/^lbl-.{8}$/, {
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
message: t("domain.deployment.form.tencent_clb_listener.placeholder"),
|
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
|
||||||
|
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
|
||||||
|
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
});
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -113,136 +87,124 @@ const DeployToTencentCLB = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_clb_region.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_clb_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = regionSchema.safeParse(temp);
|
draft.config.region = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.region = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.tencent_clb_id.label")}</Label>
|
<Label>{t("domain.deployment.form.tencent_clb_resource_type.label")}</Label>
|
||||||
<Input
|
<Select
|
||||||
placeholder={t("domain.deployment.form.tencent_clb_id.placeholder")}
|
value={config?.config?.resourceType}
|
||||||
className="w-full mt-1"
|
onValueChange={(value) => {
|
||||||
value={data?.config?.clbId}
|
const nv = produce(config, (draft) => {
|
||||||
onChange={(e) => {
|
draft.config ??= {};
|
||||||
const temp = e.target.value;
|
draft.config.resourceType = value;
|
||||||
|
|
||||||
const resp = clbIdSchema.safeParse(temp);
|
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
clbId: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
clbId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.clbId = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.clbId}</div>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("domain.deployment.form.tencent_clb_resource_type.placeholder")} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="ssl-deploy">{t("domain.deployment.form.tencent_clb_resource_type.option.ssl_deploy.label")}</SelectItem>
|
||||||
|
<SelectItem value="loadbalancer">{t("domain.deployment.form.tencent_clb_resource_type.option.loadbalancer.label")}</SelectItem>
|
||||||
|
<SelectItem value="listener">{t("domain.deployment.form.tencent_clb_resource_type.option.listener.label")}</SelectItem>
|
||||||
|
<SelectItem value="ruledomain">{t("domain.deployment.form.tencent_clb_resource_type.option.ruledomain.label")}</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.tencent_clb_listener.label")}</Label>
|
<Label>{t("domain.deployment.form.tencent_clb_loadbalancer_id.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_clb_listener.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.lsnId}
|
value={config?.config?.loadbalancerId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = lsnIdSchema.safeParse(temp);
|
draft.config.loadbalancerId = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
lsnId: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
lsnId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.lsnId = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.lsnId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{config?.config?.resourceType === "ssl-deploy" || config?.config?.resourceType === "listener" || config?.config?.resourceType === "ruledomain" ? (
|
||||||
|
<div>
|
||||||
|
<Label>{t("domain.deployment.form.tencent_clb_listener_id.label")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("domain.deployment.form.tencent_clb_listener_id.placeholder")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={config?.config?.listenerId}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
|
draft.config.listenerId = e.target.value?.trim();
|
||||||
|
});
|
||||||
|
setConfig(nv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config?.config?.resourceType === "ssl-deploy" ? (
|
||||||
<div>
|
<div>
|
||||||
<Label>{t("domain.deployment.form.tencent_clb_domain.label")}</Label>
|
<Label>{t("domain.deployment.form.tencent_clb_domain.label")}</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_clb_domain.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_clb_domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config?.config?.resourceType === "ruledomain" ? (
|
||||||
|
<div>
|
||||||
|
<Label>{t("domain.deployment.form.tencent_clb_ruledomain.label")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("domain.deployment.form.tencent_clb_ruledomain.placeholder")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={config?.config?.domain}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
|
draft.config.domain = e.target.value?.trim();
|
||||||
|
});
|
||||||
|
setConfig(nv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,84 +7,49 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
const DeployToTencentCOS = () => {
|
type DeployToTencentCOSParams = {
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
region?: string;
|
||||||
|
bucket?: string;
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployToTencentCOS = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCOSParams>();
|
||||||
setError({});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
if (!config.id) {
|
||||||
if (!resp.success) {
|
setConfig({
|
||||||
setError({
|
...config,
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const bucketResp = bucketSchema.safeParse(data.config?.bucket);
|
|
||||||
if (!bucketResp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: JSON.parse(bucketResp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const regionResp = regionSchema.safeParse(data.config?.region);
|
|
||||||
if (!regionResp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: JSON.parse(regionResp.error.message)[0].message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data.id) {
|
|
||||||
setDeploy({
|
|
||||||
...data,
|
|
||||||
config: {
|
config: {
|
||||||
region: "",
|
region: "ap-guangzhou",
|
||||||
bucket: "",
|
|
||||||
domain: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
useEffect(() => {
|
||||||
|
setErrors({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
region: z.string().min(1, t("domain.deployment.form.tencent_cos_region.placeholder")),
|
||||||
|
bucket: z.string().min(1, t("domain.deployment.form.tencent_cos_bucket.placeholder")),
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
message: t("common.errmsg.domain_invalid"),
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const regionSchema = z.string().regex(/^ap-[a-z]+$/, {
|
useEffect(() => {
|
||||||
message: t("domain.deployment.form.tencent_cos_region.placeholder"),
|
const res = formSchema.safeParse(config.config);
|
||||||
});
|
setErrors({
|
||||||
|
...errors,
|
||||||
const bucketSchema = z.string().regex(/^.+-\d+$/, {
|
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
|
||||||
message: t("domain.deployment.form.tencent_cos_bucket.placeholder"),
|
bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
});
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -93,33 +58,16 @@ const DeployToTencentCOS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_cos_region.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_cos_region.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.region}
|
value={config?.config?.region}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = regionSchema.safeParse(temp);
|
draft.config.region = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
region: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.region = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.region}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -127,33 +75,16 @@ const DeployToTencentCOS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_cos_bucket.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_cos_bucket.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.bucket}
|
value={config?.config?.bucket}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = bucketSchema.safeParse(temp);
|
draft.config.bucket = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
bucket: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.bucket = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.bucket}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -161,33 +92,16 @@ const DeployToTencentCOS = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.domain.placeholder")}
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,52 +8,44 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { useDeployEditContext } from "./DeployEdit";
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToTencentTEOParams = {
|
||||||
|
zoneId?: string;
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const DeployToTencentTEO = () => {
|
const DeployToTencentTEO = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentTEOParams>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
if (!config.id) {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
config: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resp = domainSchema.safeParse(data.config?.domain);
|
setErrors({});
|
||||||
if (!resp.success) {
|
}, []);
|
||||||
setError({
|
|
||||||
...error,
|
const formSchema = z.object({
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
zoneId: z.string().min(1, t("domain.deployment.form.tencent_teo_zone_id.placeholder")),
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resp = zoneIdSchema.safeParse(data.config?.zoneId);
|
const res = formSchema.safeParse(config.config);
|
||||||
if (!resp.success) {
|
setErrors({
|
||||||
setError({
|
...errors,
|
||||||
...error,
|
zoneId: res.error?.errors?.find((e) => e.path[0] === "zoneId")?.message,
|
||||||
zoneId: JSON.parse(resp.error.message)[0].message,
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
zoneId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
|
||||||
message: t("common.errmsg.domain_invalid"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const zoneIdSchema = z.string().regex(/^zone-[0-9a-zA-Z]{9}$/, {
|
|
||||||
message: t("common.errmsg.zoneid_invalid"),
|
|
||||||
});
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
|
|
@ -62,33 +54,16 @@ const DeployToTencentTEO = () => {
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("domain.deployment.form.tencent_teo_zone_id.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_teo_zone_id.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.zoneId}
|
value={config?.config?.zoneId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = zoneIdSchema.safeParse(temp);
|
draft.config.zoneId = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
zoneId: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
zoneId: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.zoneId = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.zoneId}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.zoneId}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -96,33 +71,16 @@ const DeployToTencentTEO = () => {
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={t("domain.deployment.form.tencent_teo_domain.placeholder")}
|
placeholder={t("domain.deployment.form.tencent_teo_domain.placeholder")}
|
||||||
className="w-full mt-1"
|
className="w-full mt-1"
|
||||||
value={data?.config?.domain}
|
value={config?.config?.domain}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const temp = e.target.value;
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
const resp = domainSchema.safeParse(temp);
|
draft.config.domain = e.target.value?.trim();
|
||||||
if (!resp.success) {
|
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: JSON.parse(resp.error.message)[0].message,
|
|
||||||
});
|
});
|
||||||
} else {
|
setConfig(nv);
|
||||||
setError({
|
|
||||||
...error,
|
|
||||||
domain: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
if (!draft.config) {
|
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.domain = temp;
|
|
||||||
});
|
|
||||||
setDeploy(newData);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,31 @@ import KVList from "./KVList";
|
||||||
import { type KVType } from "@/domain/domain";
|
import { type KVType } from "@/domain/domain";
|
||||||
|
|
||||||
const DeployToWebhook = () => {
|
const DeployToWebhook = () => {
|
||||||
const { deploy: data, setDeploy } = useDeployEditContext();
|
const { config, setConfig, setErrors } = useDeployEditContext();
|
||||||
|
|
||||||
const { setError } = useDeployEditContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError({});
|
if (!config.id) {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
config: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setErrors({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<KVList
|
<KVList
|
||||||
variables={data?.config?.variables}
|
variables={config?.config?.variables}
|
||||||
onValueChange={(variables: KVType[]) => {
|
onValueChange={(variables: KVType[]) => {
|
||||||
const newData = produce(data, (draft) => {
|
const nv = produce(config, (draft) => {
|
||||||
if (!draft.config) {
|
draft.config ??= {};
|
||||||
draft.config = {};
|
|
||||||
}
|
|
||||||
draft.config.variables = variables;
|
draft.config.variables = variables;
|
||||||
});
|
});
|
||||||
setDeploy(newData);
|
setConfig(nv);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@
|
||||||
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
|
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
|
||||||
"common.errmsg.ip_invalid": "Please enter IP",
|
"common.errmsg.ip_invalid": "Please enter IP",
|
||||||
"common.errmsg.url_invalid": "Please enter a valid URL",
|
"common.errmsg.url_invalid": "Please enter a valid URL",
|
||||||
"common.errmsg.zoneid_invalid": "Please enter Zone ID",
|
|
||||||
|
|
||||||
"common.provider.aliyun": "Alibaba Cloud",
|
"common.provider.aliyun": "Alibaba Cloud",
|
||||||
"common.provider.aliyun.oss": "Alibaba Cloud - OSS",
|
"common.provider.aliyun.oss": "Alibaba Cloud - OSS",
|
||||||
|
|
@ -65,7 +64,7 @@
|
||||||
"common.provider.tencent.ecdn": "Tencent Cloud - ECDN",
|
"common.provider.tencent.ecdn": "Tencent Cloud - ECDN",
|
||||||
"common.provider.tencent.clb": "Tencent Cloud - CLB",
|
"common.provider.tencent.clb": "Tencent Cloud - CLB",
|
||||||
"common.provider.tencent.cos": "Tencent Cloud - COS",
|
"common.provider.tencent.cos": "Tencent Cloud - COS",
|
||||||
"common.provider.tencent.teo": "Tencent Cloud - TEO",
|
"common.provider.tencent.teo": "Tencent Cloud - EdgeOne",
|
||||||
"common.provider.huaweicloud": "Huawei Cloud",
|
"common.provider.huaweicloud": "Huawei Cloud",
|
||||||
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
||||||
"common.provider.huaweicloud.elb": "Huawei Cloud - ELB",
|
"common.provider.huaweicloud.elb": "Huawei Cloud - ELB",
|
||||||
|
|
|
||||||
|
|
@ -95,14 +95,23 @@
|
||||||
"domain.deployment.form.tencent_cos_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
|
"domain.deployment.form.tencent_cos_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
|
||||||
"domain.deployment.form.tencent_cos_bucket.label": "Bucket",
|
"domain.deployment.form.tencent_cos_bucket.label": "Bucket",
|
||||||
"domain.deployment.form.tencent_cos_bucket.placeholder": "Please enter bucket",
|
"domain.deployment.form.tencent_cos_bucket.placeholder": "Please enter bucket",
|
||||||
|
"domain.deployment.form.tencent_cdn_region.label": "Region",
|
||||||
|
"domain.deployment.form.tencent_cdn_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
|
||||||
"domain.deployment.form.tencent_clb_region.label": "Region",
|
"domain.deployment.form.tencent_clb_region.label": "Region",
|
||||||
"domain.deployment.form.tencent_clb_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
|
"domain.deployment.form.tencent_clb_region.placeholder": "Please enter region (e.g. ap-guangzhou)",
|
||||||
"domain.deployment.form.tencent_clb_id.label": "CLB ID",
|
"domain.deployment.form.tencent_clb_resource_type.label": "Resource Type",
|
||||||
"domain.deployment.form.tencent_clb_id.placeholder": "Please enter CLB ID (e.g. lb-xxxxxxxx)",
|
"domain.deployment.form.tencent_clb_resource_type.placeholder": "Please select CLB resource type",
|
||||||
"domain.deployment.form.tencent_clb_listener.label": "Listener ID",
|
"domain.deployment.form.tencent_clb_resource_type.option.ssl_deploy.label": "Through SSL Deploy",
|
||||||
"domain.deployment.form.tencent_clb_listener.placeholder": "Please enter listener ID (e.g. lbl-xxxxxxxx). The specific listener should have set the corresponding domain HTTPS forwarding, and the original certificate domain should be consistent with the certificate to be deployed.",
|
"domain.deployment.form.tencent_clb_resource_type.option.loadbalancer.label": "CLB LoadBalancer",
|
||||||
|
"domain.deployment.form.tencent_clb_resource_type.option.listener.label": "CLB Listener",
|
||||||
|
"domain.deployment.form.tencent_clb_loadbalancer_id.label": "Loadbalancer ID",
|
||||||
|
"domain.deployment.form.tencent_clb_loadbalancer_id.placeholder": "Please enter Loadbalancer ID",
|
||||||
|
"domain.deployment.form.tencent_clb_listener_id.label": "Listener ID",
|
||||||
|
"domain.deployment.form.tencent_clb_listener_id.placeholder": "Please enter listener ID. The specific listener should have set the corresponding domain HTTPS forwarding, and the original certificate domain should be consistent with the certificate to be deployed.",
|
||||||
"domain.deployment.form.tencent_clb_domain.label": "Deploy to domain (Wildcard domain is also supported)",
|
"domain.deployment.form.tencent_clb_domain.label": "Deploy to domain (Wildcard domain is also supported)",
|
||||||
"domain.deployment.form.tencent_clb_domain.placeholder": "Please enter domain to be deployed. If SNI is not enabled, you can leave it blank.",
|
"domain.deployment.form.tencent_clb_domain.placeholder": "Please enter domain to be deployed. If SNI is not enabled, you can leave it blank.",
|
||||||
|
"domain.deployment.form.tencent_clb_ruledomain.label": "Rule Domain",
|
||||||
|
"domain.deployment.form.tencent_clb_ruledomain.placeholder": "Please enter rule domain",
|
||||||
"domain.deployment.form.tencent_teo_zone_id.label": "Zone ID",
|
"domain.deployment.form.tencent_teo_zone_id.label": "Zone ID",
|
||||||
"domain.deployment.form.tencent_teo_zone_id.placeholder": "Please enter zone id, e.g. zone-xxxxxxxxx",
|
"domain.deployment.form.tencent_teo_zone_id.placeholder": "Please enter zone id, e.g. zone-xxxxxxxxx",
|
||||||
"domain.deployment.form.tencent_teo_domain.label": "Deploy to domain (Wildcard domain is also supported, but should be same as the config on server, one domain each line)",
|
"domain.deployment.form.tencent_teo_domain.label": "Deploy to domain (Wildcard domain is also supported, but should be same as the config on server, one domain each line)",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@
|
||||||
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
|
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
|
||||||
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
|
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
|
||||||
"common.errmsg.url_invalid": "请输入正确的 URL",
|
"common.errmsg.url_invalid": "请输入正确的 URL",
|
||||||
"common.errmsg.zoneid_invalid": "请输入正确的 Zone ID",
|
|
||||||
|
|
||||||
"common.provider.aliyun": "阿里云",
|
"common.provider.aliyun": "阿里云",
|
||||||
"common.provider.aliyun.oss": "阿里云 - 对象存储 OSS",
|
"common.provider.aliyun.oss": "阿里云 - 对象存储 OSS",
|
||||||
|
|
@ -65,7 +64,7 @@
|
||||||
"common.provider.tencent.cdn": "腾讯云 - 内容分发网络 CDN",
|
"common.provider.tencent.cdn": "腾讯云 - 内容分发网络 CDN",
|
||||||
"common.provider.tencent.ecdn": "腾讯云 - 全站加速网络 ECDN",
|
"common.provider.tencent.ecdn": "腾讯云 - 全站加速网络 ECDN",
|
||||||
"common.provider.tencent.clb": "腾讯云 - 负载均衡 CLB",
|
"common.provider.tencent.clb": "腾讯云 - 负载均衡 CLB",
|
||||||
"common.provider.tencent.teo": "腾讯云 - 边缘安全加速平台 EO",
|
"common.provider.tencent.teo": "腾讯云 - 边缘安全加速平台 EdgeOne",
|
||||||
"common.provider.huaweicloud": "华为云",
|
"common.provider.huaweicloud": "华为云",
|
||||||
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
||||||
"common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
"common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
||||||
|
|
@ -88,4 +87,3 @@
|
||||||
"common.provider.lark": "飞书",
|
"common.provider.lark": "飞书",
|
||||||
"common.provider.mail": "电子邮件"
|
"common.provider.mail": "电子邮件"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
"domain.deployment.form.aliyun_clb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
"domain.deployment.form.aliyun_clb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
||||||
"domain.deployment.form.aliyun_clb_resource_type.label": "替换方式",
|
"domain.deployment.form.aliyun_clb_resource_type.label": "替换方式",
|
||||||
"domain.deployment.form.aliyun_clb_resource_type.placeholder": "请选择替换方式",
|
"domain.deployment.form.aliyun_clb_resource_type.placeholder": "请选择替换方式",
|
||||||
"domain.deployment.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 HTTPS 监听)",
|
"domain.deployment.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书",
|
||||||
"domain.deployment.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书",
|
"domain.deployment.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书",
|
||||||
"domain.deployment.form.aliyun_clb_loadbalancer_id.label": "负载均衡器 ID",
|
"domain.deployment.form.aliyun_clb_loadbalancer_id.label": "负载均衡器 ID",
|
||||||
"domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
"domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
||||||
|
|
@ -75,8 +75,8 @@
|
||||||
"domain.deployment.form.aliyun_alb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
"domain.deployment.form.aliyun_alb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
||||||
"domain.deployment.form.aliyun_alb_resource_type.label": "替换方式",
|
"domain.deployment.form.aliyun_alb_resource_type.label": "替换方式",
|
||||||
"domain.deployment.form.aliyun_alb_resource_type.placeholder": "请选择替换方式",
|
"domain.deployment.form.aliyun_alb_resource_type.placeholder": "请选择替换方式",
|
||||||
"domain.deployment.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 HTTPS/QUIC 监听)",
|
"domain.deployment.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书",
|
||||||
"domain.deployment.form.aliyun_alb_resource_type.option.listener.label": "替换指定监听器的证书",
|
"domain.deployment.form.aliyun_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书",
|
||||||
"domain.deployment.form.aliyun_alb_loadbalancer_id.label": "负载均衡器 ID",
|
"domain.deployment.form.aliyun_alb_loadbalancer_id.label": "负载均衡器 ID",
|
||||||
"domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
"domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
||||||
"domain.deployment.form.aliyun_alb_listener_id.label": "监听器 ID",
|
"domain.deployment.form.aliyun_alb_listener_id.label": "监听器 ID",
|
||||||
|
|
@ -85,8 +85,8 @@
|
||||||
"domain.deployment.form.aliyun_nlb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
"domain.deployment.form.aliyun_nlb_region.placeholder": "请输入地域(如 cn-hangzhou)",
|
||||||
"domain.deployment.form.aliyun_nlb_resource_type.label": "替换方式",
|
"domain.deployment.form.aliyun_nlb_resource_type.label": "替换方式",
|
||||||
"domain.deployment.form.aliyun_nlb_resource_type.placeholder": "请选择替换方式",
|
"domain.deployment.form.aliyun_nlb_resource_type.placeholder": "请选择替换方式",
|
||||||
"domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 TCPSSL 监听)",
|
"domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 TCPSSL 监听的证书",
|
||||||
"domain.deployment.form.aliyun_nlb_resource_type.option.listener.label": "替换指定监听器的证书",
|
"domain.deployment.form.aliyun_nlb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书",
|
||||||
"domain.deployment.form.aliyun_nlb_loadbalancer_id.label": "负载均衡器 ID",
|
"domain.deployment.form.aliyun_nlb_loadbalancer_id.label": "负载均衡器 ID",
|
||||||
"domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
"domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder": "请输入负载均衡器 ID",
|
||||||
"domain.deployment.form.aliyun_nlb_listener_id.label": "监听器 ID",
|
"domain.deployment.form.aliyun_nlb_listener_id.label": "监听器 ID",
|
||||||
|
|
@ -97,23 +97,33 @@
|
||||||
"domain.deployment.form.tencent_cos_bucket.placeholder": "请输入存储桶名",
|
"domain.deployment.form.tencent_cos_bucket.placeholder": "请输入存储桶名",
|
||||||
"domain.deployment.form.tencent_clb_region.label": "地域",
|
"domain.deployment.form.tencent_clb_region.label": "地域",
|
||||||
"domain.deployment.form.tencent_clb_region.placeholder": "请输入地域(如 ap-guangzhou)",
|
"domain.deployment.form.tencent_clb_region.placeholder": "请输入地域(如 ap-guangzhou)",
|
||||||
"domain.deployment.form.tencent_clb_id.label": "负载均衡器 ID",
|
"domain.deployment.form.tencent_clb_resource_type.label": "替换方式",
|
||||||
"domain.deployment.form.tencent_clb_id.placeholder": "请输入负载均衡器实例 ID(如 lb-xxxxxxxx)",
|
"domain.deployment.form.tencent_clb_resource_type.placeholder": "请选择替换方式",
|
||||||
"domain.deployment.form.tencent_clb_listener.label": "监听器 ID(对应监听器应已设置对应域名 HTTPS 转发, 且原证书对应域名应与待部署证书的一致)",
|
"domain.deployment.form.tencent_clb_resource_type.option.ssl_deploy.label": "通过 SSL 服务部署到云资源实例",
|
||||||
"domain.deployment.form.tencent_clb_listener.placeholder": "请输入监听器 ID(如 lb-xxxxxxxx)",
|
"domain.deployment.form.tencent_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/TCPSSL/QUIC 监听器的证书",
|
||||||
|
"domain.deployment.form.tencent_clb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书",
|
||||||
|
"domain.deployment.form.tencent_clb_resource_type.option.ruledomain.label": "替换指定七层监听转发规则域名的证书",
|
||||||
|
"domain.deployment.form.tencent_clb_loadbalancer_id.label": "负载均衡器 ID",
|
||||||
|
"domain.deployment.form.tencent_clb_loadbalancer_id.placeholder": "请输入负载均衡器实例 ID",
|
||||||
|
"domain.deployment.form.tencent_clb_listener_id.label": "监听器 ID",
|
||||||
|
"domain.deployment.form.tencent_clb_listener_id.placeholder": "请输入监听器 ID",
|
||||||
"domain.deployment.form.tencent_clb_domain.label": "部署到域名(支持泛域名)",
|
"domain.deployment.form.tencent_clb_domain.label": "部署到域名(支持泛域名)",
|
||||||
"domain.deployment.form.tencent_clb_domain.placeholder": "请输入部署到的域名, 如未开启 SNI, 可置空忽略此项",
|
"domain.deployment.form.tencent_clb_domain.placeholder": "请输入部署到的域名, 如未开启 SNI, 可置空忽略此项",
|
||||||
|
"domain.deployment.form.tencent_clb_ruledomain.label": "转发域名",
|
||||||
|
"domain.deployment.form.tencent_clb_ruledomain.placeholder": "请输入七层监听转发规则域名",
|
||||||
"domain.deployment.form.tencent_teo_zone_id.label": "Zone ID",
|
"domain.deployment.form.tencent_teo_zone_id.label": "Zone ID",
|
||||||
"domain.deployment.form.tencent_teo_zone_id.placeholder": "请输入 Zone ID",
|
"domain.deployment.form.tencent_teo_zone_id.placeholder": "请输入 Zone ID",
|
||||||
"domain.deployment.form.tencent_teo_domain.label": "部署到域名(支持泛域名, 应与服务器上配置的域名完全一致, 每行一个域名)",
|
"domain.deployment.form.tencent_teo_domain.label": "部署到域名(支持泛域名, 应与服务器上配置的域名完全一致, 每行一个域名)",
|
||||||
"domain.deployment.form.tencent_teo_domain.placeholder": "请输入部署到的域名",
|
"domain.deployment.form.tencent_teo_domain.placeholder": "请输入部署到的域名",
|
||||||
|
"domain.deployment.form.huaweicloud_cdn_region.label": "地域",
|
||||||
|
"domain.deployment.form.huaweicloud_cdn_region.placeholder": "请输入地域(如 cn-north-1)",
|
||||||
"domain.deployment.form.huaweicloud_elb_region.label": "地域",
|
"domain.deployment.form.huaweicloud_elb_region.label": "地域",
|
||||||
"domain.deployment.form.huaweicloud_elb_region.placeholder": "请输入地域(如 cn-north-1)",
|
"domain.deployment.form.huaweicloud_elb_region.placeholder": "请输入地域(如 cn-north-1)",
|
||||||
"domain.deployment.form.huaweicloud_elb_resource_type.label": "替换方式",
|
"domain.deployment.form.huaweicloud_elb_resource_type.label": "替换方式",
|
||||||
"domain.deployment.form.huaweicloud_elb_resource_type.placeholder": "请选择替换方式",
|
"domain.deployment.form.huaweicloud_elb_resource_type.placeholder": "请选择替换方式",
|
||||||
"domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label": "替换指定证书",
|
"domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label": "替换指定证书",
|
||||||
"domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听器的证书(仅支持 HTTPS 监听)",
|
"domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听器的证书",
|
||||||
"domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label": "替换指定监听器",
|
"domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label": "替换指定监听器的证书",
|
||||||
"domain.deployment.form.huaweicloud_elb_certificate_id.label": "证书 ID",
|
"domain.deployment.form.huaweicloud_elb_certificate_id.label": "证书 ID",
|
||||||
"domain.deployment.form.huaweicloud_elb_certificate_id.placeholder": "请输入证书 ID",
|
"domain.deployment.form.huaweicloud_elb_certificate_id.placeholder": "请输入证书 ID",
|
||||||
"domain.deployment.form.huaweicloud_elb_loadbalancer_id.label": "负载均衡器 ID",
|
"domain.deployment.form.huaweicloud_elb_loadbalancer_id.label": "负载均衡器 ID",
|
||||||
|
|
@ -143,7 +153,7 @@
|
||||||
"domain.deployment.form.shell_preset_scripts.trigger": "使用预设脚本",
|
"domain.deployment.form.shell_preset_scripts.trigger": "使用预设脚本",
|
||||||
"domain.deployment.form.shell_preset_scripts.option.reload_nginx.label": "Bash - 重启 nginx",
|
"domain.deployment.form.shell_preset_scripts.option.reload_nginx.label": "Bash - 重启 nginx",
|
||||||
"domain.deployment.form.shell_preset_scripts.option.binding_iis.label": "PowerShell - 导入并绑定到 IIS(需管理员权限)",
|
"domain.deployment.form.shell_preset_scripts.option.binding_iis.label": "PowerShell - 导入并绑定到 IIS(需管理员权限)",
|
||||||
"domain.deployment.form.shell_preset_scripts.option.binding_netsh.label": "PowerShell - 导入并绑定到netsh(需管理员权限)",
|
"domain.deployment.form.shell_preset_scripts.option.binding_netsh.label": "PowerShell - 导入并绑定到 netsh(需管理员权限)",
|
||||||
"domain.deployment.form.k8s_namespace.label": "命名空间",
|
"domain.deployment.form.k8s_namespace.label": "命名空间",
|
||||||
"domain.deployment.form.k8s_namespace.placeholder": "请输入 K8S 命名空间",
|
"domain.deployment.form.k8s_namespace.placeholder": "请输入 K8S 命名空间",
|
||||||
"domain.deployment.form.k8s_secret_name.label": "Secret 名称",
|
"domain.deployment.form.k8s_secret_name.label": "Secret 名称",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue