From 97f102533cd149d38fc0c5624854bbcb47ae9250 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 23 Apr 2025 19:32:21 +0800 Subject: [PATCH 01/17] feat: enhance context cancellation handling --- .../providers/aliyun-alb/aliyun_alb.go | 18 +++++++++++++ .../providers/aliyun-apigw/aliyun_apigw.go | 6 +++++ .../aliyun-cas-deploy/aliyun_cas_deploy.go | 4 ++- .../providers/aliyun-clb/aliyun_clb.go | 16 +++++++++-- .../providers/aliyun-nlb/aliyun_nlb.go | 16 +++++++++-- .../baiducloud-appblb/baiducloud_appblb.go | 20 +++++++++++--- .../baiducloud-blb/baiducloud_blb.go | 20 +++++++++++--- .../providers/byteplus-cdn/byteplus_cdn.go | 26 +++++++++++------- .../huaweicloud-elb/huaweicloud_elb.go | 16 +++++++++-- .../huaweicloud-waf/huaweicloud_waf.go | 12 +++++++++ .../providers/jdcloud-alb/jdcloud_alb.go | 15 +++++++++-- .../providers/jdcloud-vod/jdcloud_vod.go | 6 +++++ .../tencentcloud-clb/tencentcloud_clb.go | 9 +++++-- .../tencentcloud_ssl_deploy.go | 4 ++- .../volcengine-alb/volcengine_alb.go | 15 +++++++++-- .../volcengine-cdn/volcengine_cdn.go | 25 ++++++++++------- .../volcengine-clb/volcengine_clb.go | 15 +++++++++-- .../volcengine-live/volcengine_live.go | 27 +++++++++++-------- .../providers/wangsu-cdnpro/wangsu_cdnpro.go | 4 ++- .../providers/1panel-ssl/1panel_ssl.go | 6 +++++ .../providers/aliyun-cas/aliyun_cas.go | 6 +++++ .../uploader/providers/aws-acm/aws_acm.go | 6 +++++ .../providers/byteplus-cdn/byteplus_cdn.go | 6 +++++ .../huaweicloud-elb/huaweicloud_elb.go | 6 +++++ .../huaweicloud-scm/huaweicloud_scm.go | 6 +++++ .../huaweicloud-waf/huaweicloud_waf.go | 6 +++++ .../providers/jdcloud-ssl/jdcloud_ssl.go | 6 +++++ .../rainyun-sslcenter/rainyun_sslcenter.go | 6 +++++ .../providers/ucloud-ussl/ucloud_ussl.go | 6 +++++ .../volcengine-cdn/volcengine_cdn.go | 6 +++++ internal/workflow/dispatcher/invoker.go | 4 ++- 31 files changed, 287 insertions(+), 57 deletions(-) diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index ddf492185..1d60d3151 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -137,6 +137,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId listListenersLimit := int32(100) var listListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alialb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -166,6 +172,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners listListenersToken = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alialb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -262,6 +274,12 @@ func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudL listListenerCertificatesLimit := int32(100) var listListenerCertificatesToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenerCertificatesReq := &alialb.ListListenerCertificatesRequest{ NextToken: listListenerCertificatesToken, MaxResults: tea.Int32(listListenerCertificatesLimit), diff --git a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go index 2d85be7aa..82a05c33e 100644 --- a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -142,6 +142,12 @@ func (d *DeployerProvider) deployToCloudNative(ctx context.Context, certPEM stri listDomainsPageNumber := int32(1) listDomainsPageSize := int32(10) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listDomainsReq := &aliapig.ListDomainsRequest{ GatewayId: tea.String(d.config.GatewayId), NameLike: tea.String(d.config.Domain), diff --git a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index a819de13e..077dea5c2 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -126,8 +126,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 循环获取部署任务详情,等待任务状态变更 // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-describedeploymentjob for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } describeDeploymentJobReq := &alicas.DescribeDeploymentJobRequest{ diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 583eeabd6..6ff33049f 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeLoadBalancerListenersLimit := int32(100) var describeLoadBalancerListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeLoadBalancerListenersReq := &alislb.DescribeLoadBalancerListenersRequest{ RegionId: tea.String(d.config.Region), MaxResults: tea.Int32(describeLoadBalancerListenersLimit), @@ -166,8 +172,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerPort := range listenerPorts { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 326273f8d..d6879f61a 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -125,6 +125,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId listListenersLimit := int32(100) var listListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alinlb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -158,8 +164,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go b/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go index 90084f6bb..aabce14c3 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go +++ b/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go @@ -152,8 +152,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } @@ -209,8 +215,14 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go b/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go index 8c3504929..a16ea1027 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go +++ b/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go @@ -152,8 +152,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } @@ -209,8 +215,14 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go index d74f6c60e..e659c9a18 100644 --- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -117,16 +117,22 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 关联证书与加速域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert - batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + + default: + // 关联证书与加速域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert + batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index f0c8175ed..748111dd6 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -160,6 +160,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str listListenersLimit := int32(2000) var listListenersMarker *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &hcelbmodel.ListListenersRequest{ Limit: typeutil.ToPtr(listListenersLimit), Marker: listListenersMarker, @@ -201,8 +207,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str var errs []error for _, listenerId := range listenerIds { - if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go b/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go index ff208fe0e..8fe96ee0a 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go @@ -172,6 +172,12 @@ func (d *DeployerProvider) deployToCloudServer(ctx context.Context, certPEM stri listHostPage := int32(1) listHostPageSize := int32(100) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listHostReq := &hcwafmodel.ListHostRequest{ Hostname: typeutil.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), Page: typeutil.ToPtr(listHostPage), @@ -239,6 +245,12 @@ func (d *DeployerProvider) deployToPremiumHost(ctx context.Context, certPEM stri listPremiumHostPage := int32(1) listPremiumHostPageSize := int32(100) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listPremiumHostReq := &hcwafmodel.ListPremiumHostRequest{ Hostname: typeutil.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), Page: typeutil.ToPtr(fmt.Sprintf("%d", listPremiumHostPage)), diff --git a/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go b/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go index 339bacc89..ca42126e3 100644 --- a/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go +++ b/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageNumber := 1 describeListenersPageSize := 100 for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := jdlbapi.NewDescribeListenersRequest(d.config.RegionId) describeListenersReq.SetFilters([]jdcommon.Filter{{Name: "loadBalancerId", Values: []string{d.config.LoadbalancerId}}}) describeListenersReq.SetPageSize(describeListenersPageNumber) @@ -164,8 +170,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go b/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go index 61c5a6f68..6f61625d9 100644 --- a/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -65,6 +65,12 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE listDomainsPageNumber := 1 listDomainsPageSize := 100 for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listDomainsReq := jdvodapi.NewListDomainsRequest() listDomainsReq.SetPageNumber(1) listDomainsReq.SetPageSize(100) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index 7625d6aec..0c2f8902a 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -188,8 +188,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index f60901900..5f13660dc 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -108,8 +108,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 循环获取部署任务详情,等待任务状态变更 // REF: https://cloud.tencent.com.cn/document/api/400/91658 for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() diff --git a/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go b/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go index c3d0a2d65..b17ae7298 100644 --- a/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go +++ b/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageSize := int64(100) describeListenersPageNumber := int64(1) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := &vealb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), @@ -163,8 +169,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go index f8642b7f1..e9b2c3250 100644 --- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -117,16 +117,21 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 关联证书与加速域名 - // REF: https://www.volcengine.com/docs/6454/125712 - batchDeployCertReq := &vecdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // 关联证书与加速域名 + // REF: https://www.volcengine.com/docs/6454/125712 + batchDeployCertReq := &vecdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go index e95aac0ea..3b6a37bf9 100644 --- a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go +++ b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go @@ -128,6 +128,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageSize := int64(100) describeListenersPageNumber := int64(1) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := &veclb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), @@ -159,8 +165,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go index 1b1336b6c..46c0b9dcb 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -125,17 +125,22 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 绑定证书 - // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 - bindCertReq := &velive.BindCertBody{ - ChainID: upres.CertId, - Domain: domain, - HTTPS: ve.Bool(true), - } - bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) - d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // 绑定证书 + // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 + bindCertReq := &velive.BindCertBody{ + ChainID: upres.CertId, + Domain: domain, + HTTPS: ve.Bool(true), + } + bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) + d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index f09cfb920..2faf1b03f 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -199,8 +199,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE wangsuTaskId = wangsuTaskMatches[1] } for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId) diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go index e4817ff88..e5a0b0bab 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -93,6 +93,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, searchWebsiteSSLPageNumber := int32(1) searchWebsiteSSLPageSize := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{ Page: searchWebsiteSSLPageNumber, PageSize: searchWebsiteSSLPageSize, diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index 487ee8b5c..9d7be2237 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -71,6 +71,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listUserCertificateOrderPage := int64(1) listUserCertificateOrderLimit := int64(50) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listUserCertificateOrderReq := &alicas.ListUserCertificateOrderRequest{ CurrentPage: tea.Int64(listUserCertificateOrderPage), ShowSize: tea.Int64(listUserCertificateOrderLimit), diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go index 8babb6717..f808083c6 100644 --- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -74,6 +74,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE var listCertificatesNextToken *string = nil listCertificatesMaxItems := int32(1000) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &awsacm.ListCertificatesInput{ NextToken: listCertificatesNextToken, MaxItems: aws.Int32(listCertificatesMaxItems), diff --git a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go index e94655dfd..1235893c1 100644 --- a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go @@ -74,6 +74,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE Source: bytepluscdn.GetStrPtr("cert_center"), } for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) u.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index 9a7d74b14..9369144e6 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -76,6 +76,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesLimit := int32(2000) var listCertificatesMarker *string = nil for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcelbmodel.ListCertificatesRequest{ Limit: typeutil.ToPtr(listCertificatesLimit), Marker: listCertificatesMarker, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index a26b471a8..f84357333 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -72,6 +72,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesLimit := int32(50) listCertificatesOffset := int32(0) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcscmmodel.ListCertificatesRequest{ Limit: typeutil.ToPtr(listCertificatesLimit), Offset: typeutil.ToPtr(listCertificatesOffset), diff --git a/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go b/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go index 9e3bbd597..d0c617753 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go @@ -77,6 +77,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesPage := int32(1) listCertificatesPageSize := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcwafmodel.ListCertificatesRequest{ Page: typeutil.ToPtr(listCertificatesPage), Pagesize: typeutil.ToPtr(listCertificatesPageSize), diff --git a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go index a0cbb1d9d..b26755a63 100644 --- a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go +++ b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go @@ -77,6 +77,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE describeCertsPageNumber := 1 describeCertsPageSize := 10 for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + describeCertsReq := jdsslapi.NewDescribeCertsRequest() describeCertsReq.SetDomainName(certX509.Subject.CommonName) describeCertsReq.SetPageNumber(describeCertsPageNumber) diff --git a/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go b/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go index 1cfdecc7c..cb4931107 100644 --- a/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -93,6 +93,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string) sslCenterListPage := int32(1) sslCenterListPerPage := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + sslCenterListReq := &rainyunsdk.SslCenterListRequest{ Filters: &rainyunsdk.SslCenterListFilters{ Domain: &certX509.Subject.CommonName, diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go index eb4ce1bfc..90eb1683b 100644 --- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go @@ -124,6 +124,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string) getCertificateListPage := int(1) getCertificateListLimit := int(1000) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + getCertificateListReq := u.sdkClient.NewGetCertificateListRequest() getCertificateListReq.Mode = ucloud.String("trust") getCertificateListReq.Domain = ucloud.String(certX509.Subject.CommonName) diff --git a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go index 48e037222..b529e84ad 100644 --- a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go @@ -75,6 +75,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE Source: "volc_cert_center", } for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) u.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { diff --git a/internal/workflow/dispatcher/invoker.go b/internal/workflow/dispatcher/invoker.go index 5f3444583..c644b26bc 100644 --- a/internal/workflow/dispatcher/invoker.go +++ b/internal/workflow/dispatcher/invoker.go @@ -47,8 +47,10 @@ func (w *workflowInvoker) GetLogs() domain.WorkflowLogs { func (w *workflowInvoker) processNode(ctx context.Context, node *domain.WorkflowNode) error { current := node for current != nil { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return ctx.Err() + default: } if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch { From 034bb71b10ca2620c5f267c12f99ceb87f3ccd8c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 24 Apr 2025 09:13:49 +0800 Subject: [PATCH 02/17] feat(ui): show ca provider global settings button only when not specified ca provider --- .../components/provider/ApplyCAProviderSelect.tsx | 2 +- .../workflow/node/ApplyNodeConfigForm.tsx | 15 +++++++++------ ui/src/i18n/locales/zh/nls.workflow.nodes.json | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ui/src/components/provider/ApplyCAProviderSelect.tsx b/ui/src/components/provider/ApplyCAProviderSelect.tsx index fdedac9b4..6c805bae2 100644 --- a/ui/src/components/provider/ApplyCAProviderSelect.tsx +++ b/ui/src/components/provider/ApplyCAProviderSelect.tsx @@ -22,7 +22,7 @@ const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps) { key: "", value: "", - label: "provider.default_ca_provider.label", + label: t("provider.default_ca_provider.label"), data: {} as ApplyCAProvider, }, ...filteredItems.map((item) => ({ diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index 6ea959790..98bfcef2f 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -33,6 +33,7 @@ import ModalForm from "@/components/ModalForm"; import MultipleInput from "@/components/MultipleInput"; import ApplyCAProviderSelect from "@/components/provider/ApplyCAProviderSelect"; import ApplyDNSProviderSelect from "@/components/provider/ApplyDNSProviderSelect"; +import Show from "@/components/Show"; import { ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyCAProvidersMap, applyDNSProvidersMap } from "@/domain/provider"; import { type WorkflowNodeConfigForApply } from "@/domain/workflow"; import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks"; @@ -400,12 +401,14 @@ const ApplyNodeConfigForm = forwardRef
{t("workflow_node.apply.form.ca_provider.label")}
- - - + + + + +
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 1e1387f8c..1db3a1d5c 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -57,12 +57,12 @@ "workflow_node.apply.form.advanced_config.label": "高级设置", "workflow_node.apply.form.ca_provider.label": "证书颁发机构(可选)", "workflow_node.apply.form.ca_provider.placeholder": "请选择证书颁发机构", - "workflow_node.apply.form.ca_provider.button": "去配置", + "workflow_node.apply.form.ca_provider.button": "设置", "workflow_node.apply.form.ca_provider_access.label": "证书颁发机构授权", "workflow_node.apply.form.ca_provider_access.placeholder": "请选择证书颁发机构授权", "workflow_node.apply.form.ca_provider_access.button": "新建", - "workflow_node.apply.form.key_algorithm.label": "数字证书算法", - "workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法", + "workflow_node.apply.form.key_algorithm.label": "证书算法", + "workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法", "workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)", "workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)", "workflow_node.apply.form.nameservers.tooltip": "在 ACME DNS-01 质询时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。点此了解更多。", From 2d17501072ec590fc1fba8590ec4aa2a6ca9be58 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 24 Apr 2025 10:46:16 +0800 Subject: [PATCH 03/17] refactor: clean code --- internal/applicant/applicant.go | 130 +++++++++++++++----------------- internal/applicant/providers.go | 20 ++++- internal/deployer/deployer.go | 57 ++++++-------- internal/deployer/providers.go | 8 +- 4 files changed, 111 insertions(+), 104 deletions(-) diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index 7b8b94f96..9aeede3c5 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" "strconv" "strings" @@ -22,7 +23,7 @@ import ( "github.com/usual2970/certimate/internal/repository" ) -type ApplyCertResult struct { +type ApplyResult struct { CertificateFullChain string IssuerCertificate string PrivateKey string @@ -33,34 +34,24 @@ type ApplyCertResult struct { } type Applicant interface { - Apply() (*ApplyCertResult, error) + Apply(ctx context.Context) (*ApplyResult, error) } -type applicantOptions struct { - Domains []string - ContactEmail string - Provider domain.ApplyDNSProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any - CAProvider domain.ApplyCAProviderType - CAProviderAccessConfig map[string]any - CAProviderExtendedConfig map[string]any - KeyAlgorithm string - Nameservers []string - DnsPropagationTimeout int32 - DnsTTL int32 - DisableFollowCNAME bool - ReplacedARIAcct string - ReplacedARICert string +type ApplicantWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger } -func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { - if node.Type != domain.WorkflowNodeTypeApply { +func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeApply { return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeApply)) } - nodeConfig := node.GetConfigForApply() - options := &applicantOptions{ + nodeConfig := config.Node.GetConfigForApply() + options := &applicantProviderOptions{ Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), ContactEmail: nodeConfig.ContactEmail, Provider: domain.ApplyDNSProviderType(nodeConfig.Provider), @@ -113,7 +104,7 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { } certRepo := repository.NewCertificateRepository() - lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id) + lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id) if lastCertificate != nil { newCertSan := slices.Clone(options.Domains) oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";") @@ -130,18 +121,46 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { } } - applicant, err := createApplicant(options) + applicant, err := createApplicantProvider(options) if err != nil { return nil, err } - return &proxyApplicant{ + return &applicantImpl{ applicant: applicant, options: options, }, nil } -func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) { +type applicantImpl struct { + applicant challenge.Provider + options *applicantProviderOptions +} + +var _ Applicant = (*applicantImpl)(nil) + +func (d *applicantImpl) Apply(ctx context.Context) (*ApplyResult, error) { + limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail)) + if err := limiter.Wait(ctx); err != nil { + return nil, err + } + + return applyUseLego(d.applicant, d.options) +} + +const ( + limitBurst = 300 + limitRate float64 = float64(1) / float64(36) +) + +var limiters sync.Map + +func getLimiter(key string) *rate.Limiter { + limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300)) + return limiter.(*rate.Limiter) +} + +func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) { user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail) if err != nil { return nil, err @@ -153,7 +172,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap // Create an ACME client config config := lego.NewConfig(user) - config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) + config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) config.CADirURL = sslProviderUrls[user.CA] if user.CA == sslProviderSSLCom { if strings.HasPrefix(options.KeyAlgorithm, "RSA") { @@ -175,7 +194,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers))) challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement()) } - client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...) + client.Challenge.SetDNS01Provider(legoProvider, challengeOptions...) // New users need to register first if !user.hasRegistration() { @@ -199,7 +218,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap return nil, err } - return &ApplyCertResult{ + return &ApplyResult{ CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), @@ -210,47 +229,20 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap }, nil } -func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType { - switch algo { - case domain.CertificateKeyAlgorithmTypeRSA2048: - return certcrypto.RSA2048 - case domain.CertificateKeyAlgorithmTypeRSA3072: - return certcrypto.RSA3072 - case domain.CertificateKeyAlgorithmTypeRSA4096: - return certcrypto.RSA4096 - case domain.CertificateKeyAlgorithmTypeRSA8192: - return certcrypto.RSA8192 - case domain.CertificateKeyAlgorithmTypeEC256: - return certcrypto.EC256 - case domain.CertificateKeyAlgorithmTypeEC384: - return certcrypto.EC384 - case domain.CertificateKeyAlgorithmTypeEC512: - return certcrypto.KeyType("P512") +func parseLegoKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType { + alogMap := map[domain.CertificateKeyAlgorithmType]certcrypto.KeyType{ + domain.CertificateKeyAlgorithmTypeRSA2048: certcrypto.RSA2048, + domain.CertificateKeyAlgorithmTypeRSA3072: certcrypto.RSA3072, + domain.CertificateKeyAlgorithmTypeRSA4096: certcrypto.RSA4096, + domain.CertificateKeyAlgorithmTypeRSA8192: certcrypto.RSA8192, + domain.CertificateKeyAlgorithmTypeEC256: certcrypto.EC256, + domain.CertificateKeyAlgorithmTypeEC384: certcrypto.EC384, + domain.CertificateKeyAlgorithmTypeEC512: certcrypto.KeyType("P512"), + } + + if keyType, ok := alogMap[algo]; ok { + return keyType } return certcrypto.RSA2048 } - -// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑 -type proxyApplicant struct { - applicant challenge.Provider - options *applicantOptions -} - -var limiters sync.Map - -const ( - limitBurst = 300 - limitRate float64 = float64(1) / float64(36) -) - -func getLimiter(key string) *rate.Limiter { - limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300)) - return limiter.(*rate.Limiter) -} - -func (d *proxyApplicant) Apply() (*ApplyCertResult, error) { - limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail)) - limiter.Wait(context.Background()) - return apply(d.applicant, d.options) -} diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index d4ad473f4..9c90cba7e 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -38,7 +38,25 @@ import ( maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) -func createApplicant(options *applicantOptions) (challenge.Provider, error) { +type applicantProviderOptions struct { + Domains []string + ContactEmail string + Provider domain.ApplyDNSProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any + CAProvider domain.ApplyCAProviderType + CAProviderAccessConfig map[string]any + CAProviderExtendedConfig map[string]any + KeyAlgorithm string + Nameservers []string + DnsPropagationTimeout int32 + DnsTTL int32 + DisableFollowCNAME bool + ReplacedARIAcct string + ReplacedARICert string +} + +func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 972e7aa38..752bda4e3 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -11,28 +11,26 @@ import ( ) type Deployer interface { - SetLogger(*slog.Logger) - Deploy(ctx context.Context) error } -type deployerOptions struct { - Provider domain.DeployProviderType - ProviderAccessConfig map[string]any - ProviderDeployConfig map[string]any +type DeployerWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger + CertificatePEM string + PrivateKeyPEM string } -func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { - Certificate string - PrivateKey string -}, -) (Deployer, error) { - if node.Type != domain.WorkflowNodeTypeDeploy { +func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeDeploy { return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeDeploy)) } - nodeConfig := node.GetConfigForDeploy() - options := &deployerOptions{ + nodeConfig := config.Node.GetConfigForDeploy() + options := &deployerProviderOptions{ Provider: domain.DeployProviderType(nodeConfig.Provider), ProviderAccessConfig: make(map[string]any), ProviderDeployConfig: nodeConfig.ProviderConfig, @@ -48,34 +46,27 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { } } - deployer, err := createDeployer(options) + deployerProvider, err := createDeployerProvider(options) if err != nil { return nil, err } - return &proxyDeployer{ - deployer: deployer, - deployCertificate: certdata.Certificate, - deployPrivateKey: certdata.PrivateKey, + return &deployerImpl{ + provider: deployerProvider.WithLogger(config.Logger), + certPEM: config.CertificatePEM, + privkeyPEM: config.PrivateKeyPEM, }, nil } -// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑 -type proxyDeployer struct { - deployer deployer.Deployer - deployCertificate string - deployPrivateKey string +type deployerImpl struct { + provider deployer.Deployer + certPEM string + privkeyPEM string } -func (d *proxyDeployer) SetLogger(logger *slog.Logger) { - if logger == nil { - panic("logger is nil") - } +var _ Deployer = (*deployerImpl)(nil) - d.deployer.WithLogger(logger) -} - -func (d *proxyDeployer) Deploy(ctx context.Context) error { - _, err := d.deployer.Deploy(ctx, d.deployCertificate, d.deployPrivateKey) +func (d *deployerImpl) Deploy(ctx context.Context) error { + _, err := d.provider.Deploy(ctx, d.certPEM, d.privkeyPEM) return err } diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 6b6daea1d..e9868278d 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -82,7 +82,13 @@ import ( sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" ) -func createDeployer(options *deployerOptions) (deployer.Deployer, error) { +type deployerProviderOptions struct { + Provider domain.DeployProviderType + ProviderAccessConfig map[string]any + ProviderDeployConfig map[string]any +} + +func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. From 7478dd7f471b69eeca0a3cd499389dd48473acb4 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 24 Apr 2025 20:27:20 +0800 Subject: [PATCH 04/17] feat: deprecate old notification module and introduce new notifier module --- internal/domain/notify.go | 1 + internal/domain/provider.go | 250 +++++++++--------- internal/domain/settings.go | 3 + internal/domain/workflow.go | 26 +- internal/notify/notifier.go | 72 +++++ internal/notify/notify.go | 7 +- internal/notify/providers.go | 98 +------ internal/notify/providers_deprecated.go | 108 ++++++++ internal/notify/service.go | 5 + internal/pkg/utils/map/getter.go | 2 +- .../workflow/node-processor/apply_node.go | 7 +- .../workflow/node-processor/deploy_node.go | 11 +- .../workflow/node-processor/notify_node.go | 51 +++- .../provider/NotifyProviderSelect.tsx | 67 +++++ .../components/workflow/WorkflowRunDetail.tsx | 6 +- .../components/workflow/node/NotifyNode.tsx | 7 +- .../workflow/node/NotifyNodeConfigForm.tsx | 118 ++++++++- ui/src/domain/provider.ts | 53 +++- ui/src/domain/settings.ts | 17 +- ui/src/domain/workflow.ts | 8 +- .../i18n/locales/en/nls.workflow.nodes.json | 7 +- .../i18n/locales/zh/nls.workflow.nodes.json | 9 +- ui/src/pages/accesses/AccessList.tsx | 10 +- ui/src/pages/certificates/CertificateList.tsx | 1 + .../pages/settings/SettingsNotification.tsx | 6 +- ui/src/pages/workflows/WorkflowList.tsx | 1 + ui/src/stores/notify/index.ts | 6 + 27 files changed, 692 insertions(+), 265 deletions(-) create mode 100644 internal/notify/notifier.go create mode 100644 internal/notify/providers_deprecated.go create mode 100644 ui/src/components/provider/NotifyProviderSelect.tsx diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 6142dae54..1a244e9b1 100644 --- a/internal/domain/notify.go +++ b/internal/domain/notify.go @@ -8,6 +8,7 @@ type NotifyChannelType string 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ +// Deprecated: v0.4.x 将废弃 const ( NotifyChannelTypeBark = NotifyChannelType("bark") NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk") diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 267adc121..5345215a1 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -19,6 +19,7 @@ const ( AccessProviderTypeBaishan = AccessProviderType("baishan") AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") AccessProviderTypeBytePlus = AccessProviderType("byteplus") + AccessProviderTypeBunny = AccessProviderType("bunny") AccessProviderTypeBuypass = AccessProviderType("buypass") AccessProviderTypeCacheFly = AccessProviderType("cachefly") AccessProviderTypeCdnfly = AccessProviderType("cdnfly") @@ -71,18 +72,18 @@ type ApplyCAProviderType string /* 申请证书 CA 提供商常量值。 -始终等于授权提供商类型。 +短横线前的部分始终等于授权提供商类型。 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ const ( - ApplyCAProviderTypeBuypass = ApplyCAProviderType(string(AccessProviderTypeBuypass)) - ApplyCAProviderTypeGoogleTrustServices = ApplyCAProviderType(string(AccessProviderTypeGoogleTrustServices)) - ApplyCAProviderTypeLetsEncrypt = ApplyCAProviderType(string(AccessProviderTypeLetsEncrypt)) - ApplyCAProviderTypeLetsEncryptStaging = ApplyCAProviderType(string(AccessProviderTypeLetsEncryptStaging)) - ApplyCAProviderTypeSSLCom = ApplyCAProviderType(string(AccessProviderTypeSSLCOM)) - ApplyCAProviderTypeZeroSSL = ApplyCAProviderType(string(AccessProviderTypeZeroSSL)) + ApplyCAProviderTypeBuypass = ApplyCAProviderType(AccessProviderTypeBuypass) + ApplyCAProviderTypeGoogleTrustServices = ApplyCAProviderType(AccessProviderTypeGoogleTrustServices) + ApplyCAProviderTypeLetsEncrypt = ApplyCAProviderType(AccessProviderTypeLetsEncrypt) + ApplyCAProviderTypeLetsEncryptStaging = ApplyCAProviderType(AccessProviderTypeLetsEncryptStaging) + ApplyCAProviderTypeSSLCom = ApplyCAProviderType(AccessProviderTypeSSLCOM) + ApplyCAProviderTypeZeroSSL = ApplyCAProviderType(AccessProviderTypeZeroSSL) ) type ApplyDNSProviderType string @@ -95,43 +96,43 @@ type ApplyDNSProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( - ApplyDNSProviderTypeACMEHttpReq = ApplyDNSProviderType("acmehttpreq") - ApplyDNSProviderTypeAliyun = ApplyDNSProviderType("aliyun") // 兼容旧值,等同于 [ApplyDNSProviderTypeAliyunDNS] - ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType("aliyun-dns") - ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53] - ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53") - ApplyDNSProviderTypeAzure = ApplyDNSProviderType("azure") // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure] - ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") - ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS] - ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns") - ApplyDNSProviderTypeBunny = ApplyDNSProviderType("bunny") - ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") - ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") - ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud") - ApplyDNSProviderTypeDeSEC = ApplyDNSProviderType("desec") - ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla") - ApplyDNSProviderTypeDynv6 = ApplyDNSProviderType("dynv6") - ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore") - ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname") - ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") - ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] - ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") - ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS] - ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns") - ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType("namecheap") - ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom") - ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo") - ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1") - ApplyDNSProviderTypePorkbun = ApplyDNSProviderType("porkbun") - ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns") - ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun") - ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS] - ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns") - ApplyDNSProviderTypeTencentCloudEO = ApplyDNSProviderType("tencentcloud-eo") - ApplyDNSProviderTypeVercel = ApplyDNSProviderType("vercel") - ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS] - ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns") - ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn") + ApplyDNSProviderTypeACMEHttpReq = ApplyDNSProviderType(AccessProviderTypeACMEHttpReq) + ApplyDNSProviderTypeAliyun = ApplyDNSProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ApplyDNSProviderTypeAliyunDNS] + ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType(AccessProviderTypeAliyun + "-dns") + ApplyDNSProviderTypeAWS = ApplyDNSProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53] + ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType(AccessProviderTypeAWS + "-route53") + ApplyDNSProviderTypeAzure = ApplyDNSProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure] + ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType(AccessProviderTypeAzure + "-dns") + ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS] + ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType(AccessProviderTypeBaiduCloud + "-dns") + ApplyDNSProviderTypeBunny = ApplyDNSProviderType(AccessProviderTypeBunny) + ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType(AccessProviderTypeCloudflare) + ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType(AccessProviderTypeClouDNS) + ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType(AccessProviderTypeCMCCCloud) + ApplyDNSProviderTypeDeSEC = ApplyDNSProviderType(AccessProviderTypeDeSEC) + ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType(AccessProviderTypeDNSLA) + ApplyDNSProviderTypeDynv6 = ApplyDNSProviderType(AccessProviderTypeDynv6) + ApplyDNSProviderTypeGcore = ApplyDNSProviderType(AccessProviderTypeGcore) + ApplyDNSProviderTypeGname = ApplyDNSProviderType(AccessProviderTypeGname) + ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType(AccessProviderTypeGoDaddy) + ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] + ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType(AccessProviderTypeHuaweiCloud + "-dns") + ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS] + ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType(AccessProviderTypeJDCloud + "-dns") + ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType(AccessProviderTypeNamecheap) + ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType(AccessProviderTypeNameDotCom) + ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType(AccessProviderTypeNameSilo) + ApplyDNSProviderTypeNS1 = ApplyDNSProviderType(AccessProviderTypeNS1) + ApplyDNSProviderTypePorkbun = ApplyDNSProviderType(AccessProviderTypePorkbun) + ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType(AccessProviderTypePowerDNS) + ApplyDNSProviderTypeRainYun = ApplyDNSProviderType(AccessProviderTypeRainYun) + ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS] + ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType(AccessProviderTypeTencentCloud + "-dns") + ApplyDNSProviderTypeTencentCloudEO = ApplyDNSProviderType(AccessProviderTypeTencentCloud + "-eo") + ApplyDNSProviderTypeVercel = ApplyDNSProviderType(AccessProviderTypeVercel) + ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS] + ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType(AccessProviderTypeVolcEngine + "-dns") + ApplyDNSProviderTypeWestcn = ApplyDNSProviderType(AccessProviderTypeWestcn) ) type DeployProviderType string @@ -144,78 +145,91 @@ type DeployProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( - DeployProviderType1PanelConsole = DeployProviderType("1panel-console") - DeployProviderType1PanelSite = DeployProviderType("1panel-site") - DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") - DeployProviderTypeAliyunAPIGW = DeployProviderType("aliyun-apigw") - DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas") - DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") - DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") - DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") - DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") - DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") - DeployProviderTypeAliyunFC = DeployProviderType("aliyun-fc") - DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") - DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") - DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") - DeployProviderTypeAliyunVOD = DeployProviderType("aliyun-vod") - DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") - DeployProviderTypeAWSACM = DeployProviderType("aws-acm") - DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") - DeployProviderTypeAzureKeyVault = DeployProviderType("azure-keyvault") - DeployProviderTypeBaiduCloudAppBLB = DeployProviderType("baiducloud-appblb") - DeployProviderTypeBaiduCloudBLB = DeployProviderType("baiducloud-blb") - DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") - DeployProviderTypeBaiduCloudCert = DeployProviderType("baiducloud-cert") - DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn") - DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console") - DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site") - DeployProviderTypeBunnyCDN = DeployProviderType("bunny-cdn") - DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") - DeployProviderTypeCacheFly = DeployProviderType("cachefly") - DeployProviderTypeCdnfly = DeployProviderType("cdnfly") - DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") - DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications") - DeployProviderTypeGcoreCDN = DeployProviderType("gcore-cdn") - DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn") - DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb") - DeployProviderTypeHuaweiCloudSCM = DeployProviderType("huaweicloud-scm") - DeployProviderTypeHuaweiCloudWAF = DeployProviderType("huaweicloud-waf") - DeployProviderTypeJDCloudALB = DeployProviderType("jdcloud-alb") - DeployProviderTypeJDCloudCDN = DeployProviderType("jdcloud-cdn") - DeployProviderTypeJDCloudLive = DeployProviderType("jdcloud-live") - DeployProviderTypeJDCloudVOD = DeployProviderType("jdcloud-vod") - DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") - DeployProviderTypeLocal = DeployProviderType("local") - DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") - DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo") - DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") - DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn") - DeployProviderTypeSafeLine = DeployProviderType("safeline") - DeployProviderTypeSSH = DeployProviderType("ssh") - DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") - DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb") - DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos") - DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") - DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") - DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") - DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf") - DeployProviderTypeTencentCloudSSL = DeployProviderType("tencentcloud-ssl") - DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy") - DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod") - DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") - DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") - DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") - DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn") - DeployProviderTypeUpyunFile = DeployProviderType("upyun-file") - DeployProviderTypeVolcEngineALB = DeployProviderType("volcengine-alb") - DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") - DeployProviderTypeVolcEngineCertCenter = DeployProviderType("volcengine-certcenter") - DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") - DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") - DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex") - DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") - DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") - DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro") - DeployProviderTypeWebhook = DeployProviderType("webhook") + DeployProviderType1PanelConsole = DeployProviderType(AccessProviderType1Panel + "-console") + DeployProviderType1PanelSite = DeployProviderType(AccessProviderType1Panel + "-site") + DeployProviderTypeAliyunALB = DeployProviderType(AccessProviderTypeAliyun + "-alb") + DeployProviderTypeAliyunAPIGW = DeployProviderType(AccessProviderTypeAliyun + "-apigw") + DeployProviderTypeAliyunCAS = DeployProviderType(AccessProviderTypeAliyun + "-cas") + DeployProviderTypeAliyunCASDeploy = DeployProviderType(AccessProviderTypeAliyun + "-casdeploy") + DeployProviderTypeAliyunCDN = DeployProviderType(AccessProviderTypeAliyun + "-cdn") + DeployProviderTypeAliyunCLB = DeployProviderType(AccessProviderTypeAliyun + "-clb") + DeployProviderTypeAliyunDCDN = DeployProviderType(AccessProviderTypeAliyun + "-dcdn") + DeployProviderTypeAliyunESA = DeployProviderType(AccessProviderTypeAliyun + "-esa") + DeployProviderTypeAliyunFC = DeployProviderType(AccessProviderTypeAliyun + "-fc") + DeployProviderTypeAliyunLive = DeployProviderType(AccessProviderTypeAliyun + "-live") + DeployProviderTypeAliyunNLB = DeployProviderType(AccessProviderTypeAliyun + "-nlb") + DeployProviderTypeAliyunOSS = DeployProviderType(AccessProviderTypeAliyun + "-oss") + DeployProviderTypeAliyunVOD = DeployProviderType(AccessProviderTypeAliyun + "-vod") + DeployProviderTypeAliyunWAF = DeployProviderType(AccessProviderTypeAliyun + "-waf") + DeployProviderTypeAWSACM = DeployProviderType(AccessProviderTypeAWS + "-acm") + DeployProviderTypeAWSCloudFront = DeployProviderType(AccessProviderTypeAWS + "-cloudfront") + DeployProviderTypeAzureKeyVault = DeployProviderType(AccessProviderTypeAzure + "-keyvault") + DeployProviderTypeBaiduCloudAppBLB = DeployProviderType(AccessProviderTypeBaiduCloud + "-appblb") + DeployProviderTypeBaiduCloudBLB = DeployProviderType(AccessProviderTypeBaiduCloud + "-blb") + DeployProviderTypeBaiduCloudCDN = DeployProviderType(AccessProviderTypeBaiduCloud + "-cdn") + DeployProviderTypeBaiduCloudCert = DeployProviderType(AccessProviderTypeBaiduCloud + "-cert") + DeployProviderTypeBaishanCDN = DeployProviderType(AccessProviderTypeBaishan + "-cdn") + DeployProviderTypeBaotaPanelConsole = DeployProviderType(AccessProviderTypeBaotaPanel + "-console") + DeployProviderTypeBaotaPanelSite = DeployProviderType(AccessProviderTypeBaotaPanel + "-site") + DeployProviderTypeBunnyCDN = DeployProviderType(AccessProviderTypeBunny + "-cdn") + DeployProviderTypeBytePlusCDN = DeployProviderType(AccessProviderTypeBytePlus + "-cdn") + DeployProviderTypeCacheFly = DeployProviderType(AccessProviderTypeCacheFly) + DeployProviderTypeCdnfly = DeployProviderType(AccessProviderTypeCdnfly) + DeployProviderTypeDogeCloudCDN = DeployProviderType(AccessProviderTypeDogeCloud + "-cdn") + DeployProviderTypeEdgioApplications = DeployProviderType(AccessProviderTypeEdgio + "-applications") + DeployProviderTypeGcoreCDN = DeployProviderType(AccessProviderTypeGcore + "-cdn") + DeployProviderTypeHuaweiCloudCDN = DeployProviderType(AccessProviderTypeHuaweiCloud + "-cdn") + DeployProviderTypeHuaweiCloudELB = DeployProviderType(AccessProviderTypeHuaweiCloud + "-elb") + DeployProviderTypeHuaweiCloudSCM = DeployProviderType(AccessProviderTypeHuaweiCloud + "-scm") + DeployProviderTypeHuaweiCloudWAF = DeployProviderType(AccessProviderTypeHuaweiCloud + "-waf") + DeployProviderTypeJDCloudALB = DeployProviderType(AccessProviderTypeJDCloud + "-alb") + DeployProviderTypeJDCloudCDN = DeployProviderType(AccessProviderTypeJDCloud + "-cdn") + DeployProviderTypeJDCloudLive = DeployProviderType(AccessProviderTypeJDCloud + "-live") + DeployProviderTypeJDCloudVOD = DeployProviderType(AccessProviderTypeJDCloud + "-vod") + DeployProviderTypeKubernetesSecret = DeployProviderType(AccessProviderTypeKubernetes + "-secret") + DeployProviderTypeLocal = DeployProviderType(AccessProviderTypeLocal) + DeployProviderTypeQiniuCDN = DeployProviderType(AccessProviderTypeQiniu + "-cdn") + DeployProviderTypeQiniuKodo = DeployProviderType(AccessProviderTypeQiniu + "-kodo") + DeployProviderTypeQiniuPili = DeployProviderType(AccessProviderTypeQiniu + "-pili") + DeployProviderTypeRainYunRCDN = DeployProviderType(AccessProviderTypeRainYun + "-rcdn") + DeployProviderTypeSafeLine = DeployProviderType(AccessProviderTypeSafeLine) + DeployProviderTypeSSH = DeployProviderType(AccessProviderTypeSSH) + DeployProviderTypeTencentCloudCDN = DeployProviderType(AccessProviderTypeTencentCloud + "-cdn") + DeployProviderTypeTencentCloudCLB = DeployProviderType(AccessProviderTypeTencentCloud + "-clb") + DeployProviderTypeTencentCloudCOS = DeployProviderType(AccessProviderTypeTencentCloud + "-cos") + DeployProviderTypeTencentCloudCSS = DeployProviderType(AccessProviderTypeTencentCloud + "-css") + DeployProviderTypeTencentCloudECDN = DeployProviderType(AccessProviderTypeTencentCloud + "-ecdn") + DeployProviderTypeTencentCloudEO = DeployProviderType(AccessProviderTypeTencentCloud + "-eo") + DeployProviderTypeTencentCloudSCF = DeployProviderType(AccessProviderTypeTencentCloud + "-scf") + DeployProviderTypeTencentCloudSSL = DeployProviderType(AccessProviderTypeTencentCloud + "-ssl") + DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType(AccessProviderTypeTencentCloud + "-ssldeploy") + DeployProviderTypeTencentCloudVOD = DeployProviderType(AccessProviderTypeTencentCloud + "-vod") + DeployProviderTypeTencentCloudWAF = DeployProviderType(AccessProviderTypeTencentCloud + "-waf") + DeployProviderTypeUCloudUCDN = DeployProviderType(AccessProviderTypeUCloud + "-ucdn") + DeployProviderTypeUCloudUS3 = DeployProviderType(AccessProviderTypeUCloud + "-us3") + DeployProviderTypeUpyunCDN = DeployProviderType(AccessProviderTypeUpyun + "-cdn") + DeployProviderTypeUpyunFile = DeployProviderType(AccessProviderTypeUpyun + "-file") + DeployProviderTypeVolcEngineALB = DeployProviderType(AccessProviderTypeVolcEngine + "-alb") + DeployProviderTypeVolcEngineCDN = DeployProviderType(AccessProviderTypeVolcEngine + "-cdn") + DeployProviderTypeVolcEngineCertCenter = DeployProviderType(AccessProviderTypeVolcEngine + "-certcenter") + DeployProviderTypeVolcEngineCLB = DeployProviderType(AccessProviderTypeVolcEngine + "-clb") + DeployProviderTypeVolcEngineDCDN = DeployProviderType(AccessProviderTypeVolcEngine + "-dcdn") + DeployProviderTypeVolcEngineImageX = DeployProviderType(AccessProviderTypeVolcEngine + "-imagex") + DeployProviderTypeVolcEngineLive = DeployProviderType(AccessProviderTypeVolcEngine + "-live") + DeployProviderTypeVolcEngineTOS = DeployProviderType(AccessProviderTypeVolcEngine + "-tos") + DeployProviderTypeWangsuCDNPro = DeployProviderType(AccessProviderTypeWangsu + "-cdnpro") + DeployProviderTypeWebhook = DeployProviderType(AccessProviderTypeWebhook) +) + +type NotifyProviderType string + +/* +消息通知提供商常量值。 +短横线前的部分始终等于授权提供商类型。 + + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. +*/ +const ( + NotifyProviderTypeWebhook = NotifyProviderType(AccessProviderTypeWebhook) ) diff --git a/internal/domain/settings.go b/internal/domain/settings.go index ebe6b9d79..7063ed83e 100644 --- a/internal/domain/settings.go +++ b/internal/domain/settings.go @@ -13,6 +13,7 @@ type Settings struct { Content string `json:"content" db:"content"` } +// Deprecated: v0.4.x 将废弃 type NotifyTemplatesSettingsContent struct { NotifyTemplates []struct { Subject string `json:"subject"` @@ -20,8 +21,10 @@ type NotifyTemplatesSettingsContent struct { } `json:"notifyTemplates"` } +// Deprecated: v0.4.x 将废弃 type NotifyChannelsSettingsContent map[string]map[string]any +// Deprecated: v0.4.x 将废弃 func (s *Settings) GetNotifyChannelConfig(channel string) (map[string]any, error) { conf := &NotifyChannelsSettingsContent{} if err := json.Unmarshal([]byte(s.Content), conf); err != nil { diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index cfa1c8cc3..639a31ca0 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -71,7 +71,7 @@ type WorkflowNodeConfigForApply struct { CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值将使用全局配置) CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 - KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法 + KeyAlgorithm string `json:"keyAlgorithm"` // 证书算法 Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` // DNS 传播超时时间(零值取决于提供商的默认值) DnsTTL int32 `json:"dnsTTL,omitempty"` // DNS TTL(零值取决于提供商的默认值) @@ -95,9 +95,12 @@ type WorkflowNodeConfigForDeploy struct { } type WorkflowNodeConfigForNotify struct { - Channel string `json:"channel"` // 通知渠道 - Subject string `json:"subject"` // 通知主题 - Message string `json:"message"` // 通知内容 + Channel string `json:"channel,omitempty"` // Deprecated: v0.4.x 将废弃 + Provider string `json:"provider"` // 通知提供商 + ProviderAccessId string `json:"providerAccessId"` // 通知提供商授权记录 ID + ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 通知提供商额外配置 + Subject string `json:"subject"` // 通知主题 + Message string `json:"message"` // 通知内容 } func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { @@ -111,10 +114,10 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { ContactEmail: maputil.GetString(n.Config, "contactEmail"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), CAProvider: maputil.GetString(n.Config, "caProvider"), CAProviderAccessId: maputil.GetString(n.Config, "caProviderAccessId"), - CAProviderConfig: maputil.GetAnyMap(n.Config, "caProviderConfig"), + CAProviderConfig: maputil.GetMap(n.Config, "caProviderConfig"), KeyAlgorithm: maputil.GetString(n.Config, "keyAlgorithm"), Nameservers: maputil.GetString(n.Config, "nameservers"), DnsPropagationTimeout: maputil.GetInt32(n.Config, "dnsPropagationTimeout"), @@ -138,16 +141,19 @@ func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy { Certificate: maputil.GetString(n.Config, "certificate"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), SkipOnLastSucceeded: maputil.GetBool(n.Config, "skipOnLastSucceeded"), } } func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify { return WorkflowNodeConfigForNotify{ - Channel: maputil.GetString(n.Config, "channel"), - Subject: maputil.GetString(n.Config, "subject"), - Message: maputil.GetString(n.Config, "message"), + Channel: maputil.GetString(n.Config, "channel"), + Provider: maputil.GetString(n.Config, "provider"), + ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), + ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), + Subject: maputil.GetString(n.Config, "subject"), + Message: maputil.GetString(n.Config, "message"), } } diff --git a/internal/notify/notifier.go b/internal/notify/notifier.go new file mode 100644 index 000000000..b8547e94a --- /dev/null +++ b/internal/notify/notifier.go @@ -0,0 +1,72 @@ +package notify + +import ( + "context" + "fmt" + "log/slog" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/notifier" + "github.com/usual2970/certimate/internal/repository" +) + +type Notifier interface { + Notify(ctx context.Context) error +} + +type NotifierWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger + Subject string + Message string +} + +func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeNotify { + return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeNotify)) + } + + nodeConfig := config.Node.GetConfigForNotify() + options := ¬ifierProviderOptions{ + Provider: domain.NotifyProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderNotifyConfig: nodeConfig.ProviderConfig, + } + + accessRepo := repository.NewAccessRepository() + if nodeConfig.ProviderAccessId != "" { + access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId) + if err != nil { + return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err) + } else { + options.ProviderAccessConfig = access.Config + } + } + + notifierProvider, err := createNotifierProvider(options) + if err != nil { + return nil, err + } + + return ¬ifierImpl{ + provider: notifierProvider.WithLogger(config.Logger), + subject: config.Subject, + message: config.Message, + }, nil +} + +type notifierImpl struct { + provider notifier.Notifier + subject string + message string +} + +var _ Notifier = (*notifierImpl)(nil) + +func (n *notifierImpl) Notify(ctx context.Context) error { + _, err := n.provider.Notify(ctx, n.subject, n.message) + return err +} diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 3d447c050..929703415 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -13,6 +13,7 @@ import ( "github.com/usual2970/certimate/internal/repository" ) +// Deprecated: v0.4.x 将废弃 func SendToAllChannels(subject, message string) error { notifiers, err := getEnabledNotifiers() if err != nil { @@ -38,8 +39,9 @@ func SendToAllChannels(subject, message string) error { return err } +// Deprecated: v0.4.x 将废弃 func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error { - notifier, err := createNotifier(domain.NotifyChannelType(channel), channelConfig) + notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(channel), channelConfig) if err != nil { return err } @@ -48,6 +50,7 @@ func SendToChannel(subject, message string, channel string, channelConfig map[st return err } +// Deprecated: v0.4.x 将废弃 func getEnabledNotifiers() ([]notifier.Notifier, error) { settingsRepo := repository.NewSettingsRepository() settings, err := settingsRepo.GetByName(context.Background(), "notifyChannels") @@ -66,7 +69,7 @@ func getEnabledNotifiers() ([]notifier.Notifier, error) { continue } - notifier, err := createNotifier(domain.NotifyChannelType(k), v) + notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(k), v) if err != nil { continue } diff --git a/internal/notify/providers.go b/internal/notify/providers.go index bae9ba722..dde875871 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -5,102 +5,28 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" - pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" - pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" - pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" - pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" - pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" - pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" - pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" - pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" - pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" - pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" - pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) -func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) { +type notifierProviderOptions struct { + Provider domain.NotifyProviderType + ProviderAccessConfig map[string]any + ProviderNotifyConfig map[string]any +} + +func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ - switch channel { - case domain.NotifyChannelTypeBark: - return pBark.NewNotifier(&pBark.NotifierConfig{ - DeviceKey: maputil.GetString(channelConfig, "deviceKey"), - ServerUrl: maputil.GetString(channelConfig, "serverUrl"), - }) - - case domain.NotifyChannelTypeDingTalk: - return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ - AccessToken: maputil.GetString(channelConfig, "accessToken"), - Secret: maputil.GetString(channelConfig, "secret"), - }) - - case domain.NotifyChannelTypeEmail: - return pEmail.NewNotifier(&pEmail.NotifierConfig{ - SmtpHost: maputil.GetString(channelConfig, "smtpHost"), - SmtpPort: maputil.GetInt32(channelConfig, "smtpPort"), - SmtpTLS: maputil.GetOrDefaultBool(channelConfig, "smtpTLS", true), - Username: maputil.GetOrDefaultString(channelConfig, "username", maputil.GetString(channelConfig, "senderAddress")), - Password: maputil.GetString(channelConfig, "password"), - SenderAddress: maputil.GetString(channelConfig, "senderAddress"), - ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"), - }) - - case domain.NotifyChannelTypeGotify: - return pGotify.NewNotifier(&pGotify.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - Token: maputil.GetString(channelConfig, "token"), - Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), - }) - - case domain.NotifyChannelTypeLark: - return pLark.NewNotifier(&pLark.NotifierConfig{ - WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), - }) - - case domain.NotifyChannelTypeMattermost: - return pMattermost.NewNotifier(&pMattermost.NotifierConfig{ - ServerUrl: maputil.GetString(channelConfig, "serverUrl"), - ChannelId: maputil.GetString(channelConfig, "channelId"), - Username: maputil.GetString(channelConfig, "username"), - Password: maputil.GetString(channelConfig, "password"), - }) - case domain.NotifyChannelTypePushover: - return pPushover.NewNotifier(&pPushover.NotifierConfig{ - Token: maputil.GetString(channelConfig, "token"), - User: maputil.GetString(channelConfig, "user"), - }) - - case domain.NotifyChannelTypePushPlus: - return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ - Token: maputil.GetString(channelConfig, "token"), - }) - - case domain.NotifyChannelTypeServerChan: - return pServerChan.NewNotifier(&pServerChan.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - }) - - case domain.NotifyChannelTypeTelegram: - return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ - ApiToken: maputil.GetString(channelConfig, "apiToken"), - ChatId: maputil.GetInt64(channelConfig, "chatId"), - }) - - case domain.NotifyChannelTypeWebhook: + switch options.Provider { + case domain.NotifyProviderTypeWebhook: return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"), - }) - - case domain.NotifyChannelTypeWeCom: - return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ - WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), + Url: maputil.GetString(options.ProviderAccessConfig, "url"), + AllowInsecureConnections: maputil.GetBool(options.ProviderAccessConfig, "allowInsecureConnections"), }) } - return nil, fmt.Errorf("unsupported notifier channel '%s'", channelConfig) + return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider) } diff --git a/internal/notify/providers_deprecated.go b/internal/notify/providers_deprecated.go new file mode 100644 index 000000000..31e0071bd --- /dev/null +++ b/internal/notify/providers_deprecated.go @@ -0,0 +1,108 @@ +package notify + +import ( + "fmt" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/notifier" + pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" + pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" + pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" + pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" + pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" + pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" + pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + maputil "github.com/usual2970/certimate/internal/pkg/utils/map" +) + +// Deprecated: v0.4.x 将废弃 +func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) { + /* + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. + */ + switch channel { + case domain.NotifyChannelTypeBark: + return pBark.NewNotifier(&pBark.NotifierConfig{ + DeviceKey: maputil.GetString(channelConfig, "deviceKey"), + ServerUrl: maputil.GetString(channelConfig, "serverUrl"), + }) + + case domain.NotifyChannelTypeDingTalk: + return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ + AccessToken: maputil.GetString(channelConfig, "accessToken"), + Secret: maputil.GetString(channelConfig, "secret"), + }) + + case domain.NotifyChannelTypeEmail: + return pEmail.NewNotifier(&pEmail.NotifierConfig{ + SmtpHost: maputil.GetString(channelConfig, "smtpHost"), + SmtpPort: maputil.GetInt32(channelConfig, "smtpPort"), + SmtpTLS: maputil.GetOrDefaultBool(channelConfig, "smtpTLS", true), + Username: maputil.GetOrDefaultString(channelConfig, "username", maputil.GetString(channelConfig, "senderAddress")), + Password: maputil.GetString(channelConfig, "password"), + SenderAddress: maputil.GetString(channelConfig, "senderAddress"), + ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"), + }) + + case domain.NotifyChannelTypeGotify: + return pGotify.NewNotifier(&pGotify.NotifierConfig{ + Url: maputil.GetString(channelConfig, "url"), + Token: maputil.GetString(channelConfig, "token"), + Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), + }) + + case domain.NotifyChannelTypeLark: + return pLark.NewNotifier(&pLark.NotifierConfig{ + WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), + }) + + case domain.NotifyChannelTypeMattermost: + return pMattermost.NewNotifier(&pMattermost.NotifierConfig{ + ServerUrl: maputil.GetString(channelConfig, "serverUrl"), + ChannelId: maputil.GetString(channelConfig, "channelId"), + Username: maputil.GetString(channelConfig, "username"), + Password: maputil.GetString(channelConfig, "password"), + }) + + case domain.NotifyChannelTypePushover: + return pPushover.NewNotifier(&pPushover.NotifierConfig{ + Token: maputil.GetString(channelConfig, "token"), + User: maputil.GetString(channelConfig, "user"), + }) + + case domain.NotifyChannelTypePushPlus: + return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ + Token: maputil.GetString(channelConfig, "token"), + }) + + case domain.NotifyChannelTypeServerChan: + return pServerChan.NewNotifier(&pServerChan.NotifierConfig{ + Url: maputil.GetString(channelConfig, "url"), + }) + + case domain.NotifyChannelTypeTelegram: + return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ + ApiToken: maputil.GetString(channelConfig, "apiToken"), + ChatId: maputil.GetInt64(channelConfig, "chatId"), + }) + + case domain.NotifyChannelTypeWebhook: + return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ + Url: maputil.GetString(channelConfig, "url"), + AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"), + }) + + case domain.NotifyChannelTypeWeCom: + return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ + WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), + }) + } + + return nil, fmt.Errorf("unsupported notifier channel '%s'", channelConfig) +} diff --git a/internal/notify/service.go b/internal/notify/service.go index 9b7cc4168..1d1f6c252 100644 --- a/internal/notify/service.go +++ b/internal/notify/service.go @@ -8,25 +8,30 @@ import ( "github.com/usual2970/certimate/internal/domain/dtos" ) +// Deprecated: v0.4.x 将废弃 const ( notifyTestTitle = "测试通知" notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。" ) +// Deprecated: v0.4.x 将废弃 type settingsRepository interface { GetByName(ctx context.Context, name string) (*domain.Settings, error) } +// Deprecated: v0.4.x 将废弃 type NotifyService struct { settingsRepo settingsRepository } +// Deprecated: v0.4.x 将废弃 func NewNotifyService(settingsRepo settingsRepository) *NotifyService { return &NotifyService{ settingsRepo: settingsRepo, } } +// Deprecated: v0.4.x 将废弃 func (n *NotifyService) Test(ctx context.Context, req *dtos.NotifyTestPushReq) error { settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") if err != nil { diff --git a/internal/pkg/utils/map/getter.go b/internal/pkg/utils/map/getter.go index c4e6fbe17..b4b654e34 100644 --- a/internal/pkg/utils/map/getter.go +++ b/internal/pkg/utils/map/getter.go @@ -207,7 +207,7 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool { // // 出参: // - 字典中键对应的 `map[string]any` 对象。 -func GetAnyMap(dict map[string]any, key string) map[string]any { +func GetMap(dict map[string]any, key string) map[string]any { if dict == nil { return make(map[string]any) } diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index d9415cb98..265437ea9 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -49,14 +49,17 @@ func (n *applyNode) Process(ctx context.Context) error { } // 初始化申请器 - applicant, err := applicant.NewWithApplyNode(n.node) + applicant, err := applicant.NewWithWorkflowNode(applicant.ApplicantWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + }) if err != nil { n.logger.Warn("failed to create applicant provider") return err } // 申请证书 - applyResult, err := applicant.Apply() + applyResult, err := applicant.Apply(ctx) if err != nil { n.logger.Warn("failed to apply") return err diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go index edb3c53dd..01d899a4a 100644 --- a/internal/workflow/node-processor/deploy_node.go +++ b/internal/workflow/node-processor/deploy_node.go @@ -63,17 +63,18 @@ func (n *deployNode) Process(ctx context.Context) error { } // 初始化部署器 - deployer, err := deployer.NewWithDeployNode(n.node, struct { - Certificate string - PrivateKey string - }{Certificate: certificate.Certificate, PrivateKey: certificate.PrivateKey}) + deployer, err := deployer.NewWithWorkflowNode(deployer.DeployerWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + CertificatePEM: certificate.Certificate, + PrivateKeyPEM: certificate.PrivateKey, + }) if err != nil { n.logger.Warn("failed to create deployer provider") return err } // 部署证书 - deployer.SetLogger(n.logger) if err := deployer.Deploy(ctx); err != nil { n.logger.Warn("failed to deploy") return err diff --git a/internal/workflow/node-processor/notify_node.go b/internal/workflow/node-processor/notify_node.go index 1c2b49d8a..1840938b6 100644 --- a/internal/workflow/node-processor/notify_node.go +++ b/internal/workflow/node-processor/notify_node.go @@ -30,25 +30,50 @@ func (n *notifyNode) Process(ctx context.Context) error { nodeConfig := n.node.GetConfigForNotify() - // 获取通知配置 - settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") + if nodeConfig.Provider == "" { + // Deprecated: v0.4.x 将废弃 + // 兼容旧版本的通知渠道 + n.logger.Warn("WARNING! you are using the notification channel from global settings, which will be deprecated in the future") + + // 获取通知配置 + settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") + if err != nil { + return err + } + + // 获取通知渠道 + channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel) + if err != nil { + return err + } + + // 发送通知 + if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.Channel, channelConfig); err != nil { + n.logger.Warn("failed to notify", slog.String("channel", nodeConfig.Channel)) + return err + } + + n.logger.Info("notify completed") + return nil + } + + // 初始化通知器 + deployer, err := notify.NewWithWorkflowNode(notify.NotifierWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + Subject: nodeConfig.Subject, + Message: nodeConfig.Message, + }) if err != nil { + n.logger.Warn("failed to create notifier provider") return err } - // 获取通知渠道 - channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel) - if err != nil { + // 推送通知 + if err := deployer.Notify(ctx); err != nil { + n.logger.Warn("failed to notify") return err } - // 发送通知 - if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.Channel, channelConfig); err != nil { - n.logger.Warn("failed to notify", slog.String("channel", nodeConfig.Channel)) - return err - } - - n.logger.Info("notify completed") - return nil } diff --git a/ui/src/components/provider/NotifyProviderSelect.tsx b/ui/src/components/provider/NotifyProviderSelect.tsx new file mode 100644 index 000000000..409126f43 --- /dev/null +++ b/ui/src/components/provider/NotifyProviderSelect.tsx @@ -0,0 +1,67 @@ +import { memo, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Avatar, Select, type SelectProps, Space, Typography } from "antd"; + +import { type NotifyProvider, notifyProvidersMap } from "@/domain/provider"; + +export type NotifyProviderSelectProps = Omit< + SelectProps, + "filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender" +> & { + filter?: (record: NotifyProvider) => boolean; +}; + +const NotifyProviderSelect = ({ filter, ...props }: NotifyProviderSelectProps) => { + const { t } = useTranslation(); + + const [options, setOptions] = useState>([]); + useEffect(() => { + const allItems = Array.from(notifyProvidersMap.values()); + const filteredItems = filter != null ? allItems.filter(filter) : allItems; + setOptions( + filteredItems.map((item) => ({ + key: item.type, + value: item.type, + label: t(item.name), + data: item, + })) + ); + }, [filter]); + + const renderOption = (key: string) => { + const provider = notifyProvidersMap.get(key); + return ( + + + + {t(provider?.name ?? "")} + + + ); + }; + + return ( + + + + +
+ + + +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + ); +}; + +export default AccessFormEmailConfig; diff --git a/ui/src/components/access/AccessFormMattermostConfig.tsx b/ui/src/components/access/AccessFormMattermostConfig.tsx new file mode 100644 index 000000000..f4a99ecef --- /dev/null +++ b/ui/src/components/access/AccessFormMattermostConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForMattermost } from "@/domain/access"; + +type AccessFormMattermostConfigFieldValues = Nullish; + +export type AccessFormMattermostConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormMattermostConfigFieldValues; + onValuesChange?: (values: AccessFormMattermostConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormMattermostConfigFieldValues => { + return { + serverUrl: "http://:8065/", + username: "", + password: "", + }; +}; + +const AccessFormMattermostConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormMattermostConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serverUrl: z.string().url(t("common.errmsg.url_invalid")), + username: z.string().nonempty(t("access.form.mattermost_username.placeholder")), + password: z.string().nonempty(t("access.form.mattermost_password.placeholder")), + defaultChannelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + + + + + + + + } + > + + +
+ ); +}; + +export default AccessFormMattermostConfig; diff --git a/ui/src/components/access/AccessFormSSHConfig.tsx b/ui/src/components/access/AccessFormSSHConfig.tsx index 6d3615cf6..ebaeba909 100644 --- a/ui/src/components/access/AccessFormSSHConfig.tsx +++ b/ui/src/components/access/AccessFormSSHConfig.tsx @@ -31,13 +31,11 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues const { t } = useTranslation(); const formSchema = z.object({ - host: z - .string({ message: t("access.form.ssh_host.placeholder") }) - .refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), port: z.preprocess( (v) => Number(v), z - .number({ message: t("access.form.ssh_port.placeholder") }) + .number() .int(t("access.form.ssh_port.placeholder")) .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) ), diff --git a/ui/src/components/access/AccessFormTelegramConfig.tsx b/ui/src/components/access/AccessFormTelegramConfig.tsx new file mode 100644 index 000000000..19ceaee23 --- /dev/null +++ b/ui/src/components/access/AccessFormTelegramConfig.tsx @@ -0,0 +1,81 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForTelegram } from "@/domain/access"; + +type AccessFormTelegramConfigFieldValues = Nullish; + +export type AccessFormTelegramConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormTelegramConfigFieldValues; + onValuesChange?: (values: AccessFormTelegramConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormTelegramConfigFieldValues => { + return { + botToken: "", + }; +}; + +const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormTelegramConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + botToken: z + .string({ message: t("access.form.telegram_bot_token.placeholder") }) + .min(1, t("access.form.telegram_bot_token.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + defaultChatId: z + .preprocess( + (v) => Number(v), + z + .number() + .nullish() + .refine((v) => { + if (v == null || v + "" === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("access.form.telegram_default_chat_id.placeholder")) + ) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormTelegramConfig; diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 89280d797..f15fe221e 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -25,7 +25,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa const { t } = useTranslation(); const formSchema = z.object({ - url: z.string({ message: t("access.form.webhook_url.placeholder") }).url(t("common.errmsg.url_invalid")), + url: z.string().url(t("common.errmsg.url_invalid")), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/notification/NotifyChannelEditFormMattermostFields.tsx b/ui/src/components/notification/NotifyChannelEditFormMattermostFields.tsx index a847be757..829a2a0db 100644 --- a/ui/src/components/notification/NotifyChannelEditFormMattermostFields.tsx +++ b/ui/src/components/notification/NotifyChannelEditFormMattermostFields.tsx @@ -7,9 +7,7 @@ const NotifyChannelEditFormMattermostFields = () => { const { t } = useTranslation(); const formSchema = z.object({ - serverUrl: z - .string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") }) - .url(t("common.errmsg.url_invalid")), + serverUrl: z.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") }).url(t("common.errmsg.url_invalid")), channelId: z .string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") }) .nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")), @@ -42,19 +40,11 @@ const NotifyChannelEditFormMattermostFields = () => { - + - + diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 2959a9b6b..49f9d8fbd 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -166,6 +166,19 @@ "access.form.edgio_client_secret.label": "Edgio ClientSecret", "access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret", "access.form.edgio_client_secret.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.email_smtp_host.label": "SMTP host", + "access.form.email_smtp_host.placeholder": "Please enter SMTP host", + "access.form.email_smtp_port.label": "SMTP port", + "access.form.email_smtp_port.placeholder": "Please enter SMTP port", + "access.form.email_smtp_tls.label": "Use SSL/TLS", + "access.form.email_username.label": "Username", + "access.form.email_username.placeholder": "please enter username", + "access.form.email_password.label": "Password", + "access.form.email_password.placeholder": "please enter password", + "access.form.email_default_sender_address.label": "Default sender email address (Optional)", + "access.form.email_default_sender_address.placeholder": "Please enter default sender email address", + "access.form.email_default_receiver_address.label": "Default receiver email address (Optional)", + "access.form.email_default_receiver_address.placeholder": "Please enter default receiver email address", "access.form.gcore_api_token.label": "Gcore API token", "access.form.gcore_api_token.placeholder": "Please enter Gcore API token", "access.form.gcore_api_token.tooltip": "For more information, see https://api.gcore.com/docs/iam#section/Authentication", @@ -203,6 +216,15 @@ "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.upload": "Choose File ...", "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", + "access.form.mattermost_server_url.label": "Mattermost server URL", + "access.form.mattermost_server_url.placeholder": "Please enter Mattermost server URL", + "access.form.mattermost_username.label": "Mattermost username", + "access.form.mattermost_username.placeholder": "Please enter Mattermost username", + "access.form.mattermost_password.label": "Mattermost password", + "access.form.mattermost_password.placeholder": "Please enter Mattermost password", + "access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID", + "access.form.mattermost_default_channel_id.placeholder": "Please enter default Mattermost channel ID", + "access.form.mattermost_default_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.", "access.form.namecheap_username.label": "Namecheap username", "access.form.namecheap_username.placeholder": "Please enter Namecheap username", "access.form.namecheap_username.tooltip": "For more information, see https://www.namecheap.com/support/api/intro/", @@ -274,6 +296,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", + "access.form.telegram_bot_token.label": "Telegram bot token", + "access.form.telegram_bot_token.placeholder": "Please enter Telegram bot token", + "access.form.telegram_bot_token.tooltip": "How to get the bot token? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_default_chat_id.label": "Default Telegram chat ID (Optional)", + "access.form.telegram_default_chat_id.placeholder": "Please enter default Telegram chat ID", + "access.form.telegram_default_chat_id.tooltip": "How to get the chat ID? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index ce6ad588f..85eb0645b 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -160,6 +160,19 @@ "access.form.edgio_client_secret.label": "Edgio 客户端密码", "access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码", "access.form.edgio_client_secret.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.email_smtp_host.label": "SMTP 服务器地址", + "access.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址", + "access.form.email_smtp_port.label": "SMTP 服务器端口", + "access.form.email_smtp_port.placeholder": "请输入 SMTP 服务器端口", + "access.form.email_smtp_tls.label": "SSL/TLS 连接", + "access.form.email_username.label": "用户名", + "access.form.email_username.placeholder": "请输入用户名", + "access.form.email_password.label": "密码", + "access.form.email_password.placeholder": "请输入密码", + "access.form.email_default_sender_address.label": "默认的发送邮箱地址(可选)", + "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", + "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", + "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", "access.form.gcore_api_token.label": "Gcore API Token", "access.form.gcore_api_token.placeholder": "请输入 Gcore API Token", "access.form.gcore_api_token.tooltip": "这是什么?请参阅 https://api.gcore.com/docs/iam#section/Authentication", @@ -197,6 +210,15 @@ "access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件", "access.form.k8s_kubeconfig.upload": "选择文件", "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

为空时,将使用 Pod 的 ServiceAccount 作为凭证。", + "access.form.mattermost_server_url.label": "Mattermost 服务地址", + "access.form.mattermost_server_url.placeholder": "请输入 Mattermost 服务地址", + "access.form.mattermost_username.label": "Mattermost 用户名", + "access.form.mattermost_username.placeholder": "请输入 Mattermost 用户名", + "access.form.mattermost_password.label": "Mattermost 密码", + "access.form.mattermost_password.placeholder": "请输入 Mattermost 密码", + "access.form.mattermost_default_channel_id.label": "默认的 Mattermost 频道 ID(可选)", + "access.form.mattermost_default_channel_id.placeholder": "请输入默认的 Mattermost 频道 ID", + "access.form.mattermost_default_channel_id.tooltip": "如何获取频道 ID?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道 ID。", "access.form.namecheap_username.label": "Namecheap 用户名", "access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名", "access.form.namecheap_username.tooltip": "这是什么?请参阅 https://www.namecheap.com/support/api/intro/", @@ -268,6 +290,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", + "access.form.telegram_bot_token.label": "Telegram 机器人 API Token", + "access.form.telegram_bot_token.placeholder": "请输入 Telegram 机器人 API Token", + "access.form.telegram_bot_token.tooltip": "如何获取机器人 API Token?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", + "access.form.telegram_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", + "access.form.telegram_default_chat_id.tooltip": "如何获取会话 ID?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "腾讯云 SecretId", "access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId", "access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488", From 3c2fbd720fd25d806ded4115fec8f361e7e44597 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 25 Apr 2025 11:51:19 +0800 Subject: [PATCH 08/17] feat: support overwriting the default config of notifiers --- .../access/AccessFormEmailConfig.tsx | 12 ++- .../access/AccessFormTelegramConfig.tsx | 2 +- .../notification/NotifyChannelEditForm.tsx | 3 + .../workflow/node/ApplyNodeConfigForm.tsx | 5 +- .../workflow/node/DeployNodeConfigForm.tsx | 2 - .../workflow/node/NotifyNodeConfigForm.tsx | 56 ++++++++++--- .../node/NotifyNodeConfigFormEmailConfig.tsx | 80 +++++++++++++++++++ .../NotifyNodeConfigFormMattermostConfig.tsx | 61 ++++++++++++++ .../NotifyNodeConfigFormTelegramConfig.tsx | 66 +++++++++++++++ ui/src/i18n/locales/en/nls.access.json | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 12 +++ .../i18n/locales/zh/nls.workflow.nodes.json | 12 +++ 12 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx create mode 100644 ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx create mode 100644 ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx diff --git a/ui/src/components/access/AccessFormEmailConfig.tsx b/ui/src/components/access/AccessFormEmailConfig.tsx index ee939b9a2..65023f68e 100644 --- a/ui/src/components/access/AccessFormEmailConfig.tsx +++ b/ui/src/components/access/AccessFormEmailConfig.tsx @@ -86,7 +86,7 @@ const AccessFormEmailConfig = ({ form: formInst, formName, disabled, initialValu onValuesChange={handleFormChange} >
-
+
@@ -97,14 +97,12 @@ const AccessFormEmailConfig = ({ form: formInst, formName, disabled, initialValu
- -
- - - -
+ + + + diff --git a/ui/src/components/access/AccessFormTelegramConfig.tsx b/ui/src/components/access/AccessFormTelegramConfig.tsx index 19ceaee23..43b20f00c 100644 --- a/ui/src/components/access/AccessFormTelegramConfig.tsx +++ b/ui/src/components/access/AccessFormTelegramConfig.tsx @@ -31,7 +31,7 @@ const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialV .max(256, t("common.errmsg.string_max", { max: 256 })), defaultChatId: z .preprocess( - (v) => Number(v), + (v) => (v == null || v === "" ? undefined : Number(v)), z .number() .nullish() diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx index c9af4acd4..5d613ea5d 100644 --- a/ui/src/components/notification/NotifyChannelEditForm.tsx +++ b/ui/src/components/notification/NotifyChannelEditForm.tsx @@ -34,6 +34,9 @@ export type NotifyChannelEditFormInstance = { validateFields: FormInstance["validateFields"]; }; +/** + * @deprecated + */ const NotifyChannelEditForm = forwardRef( ({ className, style, channel, disabled, initialValues, onValuesChange }, ref) => { const { form: formInst, formProps } = useAntdForm({ diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index 08d2ffc57..fc76f3022 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -218,8 +218,6 @@ const ApplyNodeConfigForm = forwardRef { - if (fieldProviderAccessId === value) return; - // 切换授权信息时联动 DNS 提供商 const access = accesses.find((access) => access.id === value); const provider = Array.from(acmeDns01ProvidersMap.values()).find((provider) => provider.provider === access?.provider); @@ -230,8 +228,6 @@ const ApplyNodeConfigForm = forwardRef { - if (fieldCAProvider === value) return; - // 切换 CA 提供商时联动授权信息 if (value === "") { setTimeout(() => { @@ -368,6 +364,7 @@ const ApplyNodeConfigForm = forwardRef diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index c86f94eee..54f855c55 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -322,8 +322,6 @@ const DeployNodeConfigForm = forwardRef { - if (fieldProvider === value) return; - // 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标 if (initialValues?.provider === value) { formInst.resetFields(); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx index 295a0533e..6e308fadd 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx @@ -1,4 +1,4 @@ -import { forwardRef, memo, useEffect, useImperativeHandle, useState } from "react"; +import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router"; import { PlusOutlined as PlusOutlinedIcon, RightOutlined as RightOutlinedIcon } from "@ant-design/icons"; @@ -9,13 +9,17 @@ import { z } from "zod"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; import NotificationProviderSelect from "@/components/provider/NotificationProviderSelect"; -import { ACCESS_USAGES, accessProvidersMap, notificationProvidersMap } from "@/domain/provider"; +import { ACCESS_USAGES, NOTIFICATION_PROVIDERS, accessProvidersMap, notificationProvidersMap } from "@/domain/provider"; import { notifyChannelsMap } from "@/domain/settings"; import { type WorkflowNodeConfigForNotify } from "@/domain/workflow"; -import { useAntdForm, useZustandShallowSelector } from "@/hooks"; +import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks"; import { useAccessesStore } from "@/stores/access"; import { useNotifyChannelsStore } from "@/stores/notify"; +import NotifyNodeConfigFormEmailConfig from "./NotifyNodeConfigFormEmailConfig"; +import NotifyNodeConfigFormMattermostConfig from "./NotifyNodeConfigFormMattermostConfig"; +import NotifyNodeConfigFormTelegramConfig from "./NotifyNodeConfigFormTelegramConfig"; + type NotifyNodeConfigFormFieldValues = Partial; export type NotifyNodeConfigFormProps = { @@ -65,6 +69,7 @@ const NotifyNodeConfigForm = forwardRef { - if (fieldProvider === value) return; + const [nestedFormInst] = Form.useForm(); + const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeNotifyConfigFormProviderConfigForm" }); + const nestedFormEl = useMemo(() => { + const nestedFormProps = { + form: nestedFormInst, + formName: nestedFormName, + disabled: disabled, + initialValues: initialValues?.providerConfig, + }; + /* + 注意:如果追加新的子组件,请保持以 ASCII 排序。 + NOTICE: If you add new child component, please keep ASCII order. + */ + switch (fieldProvider) { + case NOTIFICATION_PROVIDERS.EMAIL: + return ; + case NOTIFICATION_PROVIDERS.MATTERMOST: + return ; + case NOTIFICATION_PROVIDERS.TELEGRAM: + return ; + } + }, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]); + + const handleProviderSelect = (value: string) => { // 切换消息通知提供商时联动授权信息 if (initialValues?.provider === value) { formInst.setFieldValue("providerAccessId", initialValues?.providerAccessId); @@ -104,8 +131,6 @@ const NotifyNodeConfigForm = forwardRef { - if (fieldProviderAccessId === value) return; - // 切换授权信息时联动消息通知提供商 const access = accesses.find((access) => access.id === value); const provider = Array.from(notificationProvidersMap.values()).find((provider) => provider.provider === access?.provider); @@ -122,13 +147,21 @@ const NotifyNodeConfigForm = forwardRef { return { getFieldsValue: () => { - return formInst.getFieldsValue(true); + const values = formInst.getFieldsValue(true); + values.providerConfig = nestedFormInst.getFieldsValue(); + return values; }, resetFields: (fields) => { - return formInst.resetFields(fields as (keyof NotifyNodeConfigFormFieldValues)[]); + formInst.resetFields(fields); + + if (!!fields && fields.includes("providerConfig")) { + nestedFormInst.resetFields(fields); + } }, validateFields: (nameList, config) => { - return formInst.validateFields(nameList, config); + const t1 = formInst.validateFields(nameList, config); + const t2 = nestedFormInst.validateFields(undefined, config); + return Promise.all([t1, t2]).then(() => t1); }, } as NotifyNodeConfigFormInstance; }); @@ -207,6 +240,7 @@ const NotifyNodeConfigForm = forwardRef @@ -224,6 +258,8 @@ const NotifyNodeConfigForm = forwardRef + + {nestedFormEl} ); } diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx new file mode 100644 index 000000000..f7129a10c --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx @@ -0,0 +1,80 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validEmailAddress } from "@/utils/validators"; + +type NotifyNodeConfigFormEmailConfigFieldValues = Nullish<{ + senderAddress?: string; + receiverAddress?: string; +}>; + +export type NotifyNodeConfigFormEmailConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormEmailConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormEmailConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormEmailConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormEmailConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormEmailConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + senderAddress: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validEmailAddress(v); + }, t("common.errmsg.email_invalid")), + receiverAddress: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validEmailAddress(v); + }, t("common.errmsg.email_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormEmailConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx new file mode 100644 index 000000000..75f72c3c3 --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormMattermostConfigFieldValues = Nullish<{ + channelId?: string; +}>; + +export type NotifyNodeConfigFormMattermostConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormMattermostConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormMattermostConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormMattermostConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormMattermostConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: NotifyNodeConfigFormMattermostConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + channelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormMattermostConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx new file mode 100644 index 000000000..b3cd6788c --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx @@ -0,0 +1,66 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormTelegramConfigFieldValues = Nullish<{ + chatId?: string | number; +}>; + +export type NotifyNodeConfigFormTelegramConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormTelegramConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormTelegramConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormTelegramConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormTelegramConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + chatId: z + .preprocess( + (v) => (v == null || v === "" ? undefined : Number(v)), + z + .number() + .nullish() + .refine((v) => { + if (v == null || v + "" === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.notify.form.telegram_chat_id.placeholder")) + ) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormTelegramConfig; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 49f9d8fbd..d76120999 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -222,7 +222,7 @@ "access.form.mattermost_username.placeholder": "Please enter Mattermost username", "access.form.mattermost_password.label": "Mattermost password", "access.form.mattermost_password.placeholder": "Please enter Mattermost password", - "access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID", + "access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID (Optional)", "access.form.mattermost_default_channel_id.placeholder": "Please enter default Mattermost channel ID", "access.form.mattermost_default_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.", "access.form.namecheap_username.label": "Namecheap username", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 326d58b47..aa91b8af6 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -720,6 +720,18 @@ "workflow_node.notify.form.provider_access.label": "Notification provider authorization", "workflow_node.notify.form.provider_access.placeholder": "Please select an authorization of notification provider", "workflow_node.notify.form.provider_access.button": "Create", + "workflow_node.notify.form.email_sender_address.label": "Sender email address (Optional)", + "workflow_node.notify.form.email_sender_address.placeholder": "Please enter sender email address", + "workflow_node.notify.form.email_sender_address.tooltip": "Leave it blank to use the default sender email address provided by the authorization.", + "workflow_node.notify.form.email_receiver_address.label": "Receiver email address (Optional)", + "workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address", + "workflow_node.notify.form.email_receiver_address.tooltip": "Leave it blank to use the default receiver email address provided by the selected authorization.", + "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID", + "workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", + "workflow_node.notify.form.telegram_chat_id.label": "Telegram chat ID (Optional)", + "workflow_node.notify.form.telegram_chat_id.placeholder": "Please enter Telegram chat ID", + "workflow_node.notify.form.telegram_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", "workflow_node.end.label": "End", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 623a9070d..31bcf762c 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -719,6 +719,18 @@ "workflow_node.notify.form.provider_access.label": "通知渠道授权", "workflow_node.notify.form.provider_access.placeholder": "请选择通知渠道授权", "workflow_node.notify.form.provider_access.button": "新建", + "workflow_node.notify.form.email_sender_address.label": "发送邮箱地址(可选)", + "workflow_node.notify.form.email_sender_address.placeholder": "请输入发送邮箱地址", + "workflow_node.notify.form.email_sender_address.tooltip": "不填写时,将使用所选通知渠道授权的默认发送邮箱地址。", + "workflow_node.notify.form.email_receiver_address.label": "接收邮箱地址(可选)", + "workflow_node.notify.form.email_receiver_address.placeholder": "请输入接收邮箱地址", + "workflow_node.notify.form.email_receiver_address.tooltip": "不填写时,将使用所选通知渠道授权的默认接收邮箱地址。", + "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID(可选)", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID", + "workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", + "workflow_node.notify.form.telegram_chat_id.label": "Telegram 会话 ID(可选)", + "workflow_node.notify.form.telegram_chat_id.placeholder": "请输入 Telegram 会话 ID", + "workflow_node.notify.form.telegram_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", "workflow_node.end.label": "结束", From 3be70c3696dfd2d7f042f4c8adbcf0ff52c535a9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 26 Apr 2025 21:17:09 +0800 Subject: [PATCH 09/17] feat: support configuring method and headers in webhook --- internal/deployer/deployer.go | 6 +- internal/deployer/providers.go | 386 ++++++++++-------- internal/domain/access.go | 8 +- internal/domain/workflow.go | 8 +- internal/notify/notifier.go | 6 +- internal/notify/providers.go | 41 +- internal/notify/providers_deprecated.go | 2 +- .../deployer/providers/webhook/webhook.go | 48 ++- .../notifier/providers/webhook/webhook.go | 88 +++- .../providers/webhook/webhook_test.go | 2 +- internal/pkg/utils/http/parser.go | 33 ++ internal/pkg/utils/map/getter.go | 36 +- .../access/AccessFormWebhookConfig.tsx | 45 +- ui/src/domain/access.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 + ui/src/i18n/locales/zh/nls.access.json | 6 + 16 files changed, 482 insertions(+), 243 deletions(-) create mode 100644 internal/pkg/utils/http/parser.go diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 458308922..bdacf08e1 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error nodeConfig := config.Node.GetConfigForDeploy() options := &deployerProviderOptions{ - Provider: domain.DeploymentProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderDeployConfig: nodeConfig.ProviderConfig, + Provider: domain.DeploymentProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderExtendedConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index c49e43b40..ca47b18cc 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -2,6 +2,7 @@ package deployer import ( "fmt" + "net/http" "strings" "github.com/usual2970/certimate/internal/domain" @@ -78,14 +79,15 @@ import ( pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro" pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" + httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" ) type deployerProviderOptions struct { - Provider domain.DeploymentProviderType - ProviderAccessConfig map[string]any - ProviderDeployConfig map[string]any + Provider domain.DeploymentProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any } func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer, error) { @@ -107,7 +109,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderDeployConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), }) return deployer, err @@ -116,9 +118,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), - WebsiteId: maputil.GetInt64(options.ProviderDeployConfig, "websiteId"), - CertificateId: maputil.GetInt64(options.ProviderDeployConfig, "certificateId"), + ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), + WebsiteId: maputil.GetInt64(options.ProviderExtendedConfig, "websiteId"), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err @@ -139,11 +141,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunALB.NewDeployer(&pAliyunALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -151,11 +153,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderDeployConfig, "serviceType")), - GatewayId: maputil.GetString(options.ProviderDeployConfig, "gatewayId"), - GroupId: maputil.GetString(options.ProviderDeployConfig, "groupId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderExtendedConfig, "serviceType")), + GatewayId: maputil.GetString(options.ProviderExtendedConfig, "gatewayId"), + GroupId: maputil.GetString(options.ProviderExtendedConfig, "groupId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -163,7 +165,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err @@ -171,9 +173,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), - ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -181,7 +183,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCDN.NewDeployer(&pAliyunCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -189,11 +191,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCLB.NewDeployer(&pAliyunCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetOrDefaultInt32(options.ProviderDeployConfig, "listenerPort", 443), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetOrDefaultInt32(options.ProviderExtendedConfig, "listenerPort", 443), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -201,7 +203,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunDCDN.NewDeployer(&pAliyunDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -209,8 +211,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunESA.NewDeployer(&pAliyunESA.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - SiteId: maputil.GetInt64(options.ProviderDeployConfig, "siteId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + SiteId: maputil.GetInt64(options.ProviderExtendedConfig, "siteId"), }) return deployer, err @@ -218,9 +220,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -228,8 +230,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -237,10 +239,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunNLB.NewDeployer(&pAliyunNLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err @@ -248,9 +250,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunOSS.NewDeployer(&pAliyunOSS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -258,8 +260,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunVOD.NewDeployer(&pAliyunVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -267,10 +269,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunWAF.NewDeployer(&pAliyunWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), - InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -291,7 +293,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSACM.NewDeployer(&pAWSACM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err @@ -299,8 +301,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - DistributionId: maputil.GetString(options.ProviderDeployConfig, "distributionId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + DistributionId: maputil.GetString(options.ProviderExtendedConfig, "distributionId"), }) return deployer, err @@ -323,8 +325,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ClientId: access.ClientId, ClientSecret: access.ClientSecret, CloudName: access.CloudName, - KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"), - CertificateName: maputil.GetString(options.ProviderDeployConfig, "certificateName"), + KeyVaultName: maputil.GetString(options.ProviderExtendedConfig, "keyvaultName"), + CertificateName: maputil.GetString(options.ProviderExtendedConfig, "certificateName"), }) return deployer, err @@ -345,11 +347,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudAppBLB.NewDeployer(&pBaiduCloudAppBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderDeployConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -357,11 +359,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudBLB.NewDeployer(&pBaiduCloudBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderDeployConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -369,7 +371,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudCDN.NewDeployer(&pBaiduCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -396,8 +398,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeBaishanCDN: deployer, err := pBaishanCDN.NewDeployer(&pBaishanCDN.DeployerConfig{ ApiToken: access.ApiToken, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err @@ -419,7 +421,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderDeployConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), }) return deployer, err @@ -428,9 +430,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - SiteType: maputil.GetOrDefaultString(options.ProviderDeployConfig, "siteType", "other"), - SiteName: maputil.GetString(options.ProviderDeployConfig, "siteName"), - SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + SiteType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "siteType", "other"), + SiteName: maputil.GetString(options.ProviderExtendedConfig, "siteName"), + SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -448,8 +450,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBunnyCDN.NewDeployer(&pBunnyCDN.DeployerConfig{ ApiKey: access.ApiKey, - PullZoneId: maputil.GetString(options.ProviderDeployConfig, "pullZoneId"), - Hostname: maputil.GetString(options.ProviderDeployConfig, "hostname"), + PullZoneId: maputil.GetString(options.ProviderExtendedConfig, "pullZoneId"), + Hostname: maputil.GetString(options.ProviderExtendedConfig, "hostname"), }) return deployer, err } @@ -466,7 +468,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBytePlusCDN.NewDeployer(&pBytePlusCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -499,9 +501,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, - ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), - SiteId: maputil.GetString(options.ProviderDeployConfig, "siteId"), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), + ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), + SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err } @@ -516,7 +518,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pDogeCDN.NewDeployer(&pDogeCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err } @@ -531,7 +533,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pEdgioApplications.NewDeployer(&pEdgioApplications.DeployerConfig{ ClientId: access.ClientId, ClientSecret: access.ClientSecret, - EnvironmentId: maputil.GetString(options.ProviderDeployConfig, "environmentId"), + EnvironmentId: maputil.GetString(options.ProviderExtendedConfig, "environmentId"), }) return deployer, err } @@ -547,7 +549,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeGcoreCDN: deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{ ApiToken: access.ApiToken, - ResourceId: maputil.GetInt64(options.ProviderDeployConfig, "resourceId"), + ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), }) return deployer, err @@ -568,8 +570,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudCDN.NewDeployer(&pHuaweiCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -577,11 +579,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudELB.NewDeployer(&pHuaweiCloudELB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err @@ -596,10 +598,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudWAF.NewDeployer(&pHuaweiCloudWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -620,10 +622,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudALB.NewDeployer(&pJDCloudALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderDeployConfig, "regionId"), - ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), + ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err @@ -631,7 +633,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudCDN.NewDeployer(&pJDCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -639,7 +641,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudLive.NewDeployer(&pJDCloudLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -647,7 +649,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudVOD.NewDeployer(&pJDCloudVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -659,16 +661,16 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ - ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderDeployConfig, "shellEnv")), - PreCommand: maputil.GetString(options.ProviderDeployConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderDeployConfig, "postCommand"), - OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderDeployConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderDeployConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderDeployConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderDeployConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderDeployConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderDeployConfig, "jksStorepass"), + ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } @@ -682,11 +684,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pK8sSecret.NewDeployer(&pK8sSecret.DeployerConfig{ KubeConfig: access.KubeConfig, - Namespace: maputil.GetOrDefaultString(options.ProviderDeployConfig, "namespace", "default"), - SecretName: maputil.GetString(options.ProviderDeployConfig, "secretName"), - SecretType: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretType", "kubernetes.io/tls"), - SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretDataKeyForCrt", "tls.crt"), - SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretDataKeyForKey", "tls.key"), + Namespace: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), + SecretName: maputil.GetString(options.ProviderExtendedConfig, "secretName"), + SecretType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), + SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), }) return deployer, err } @@ -703,7 +705,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -711,8 +713,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuPili.NewDeployer(&pQiniuPili.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Hub: maputil.GetString(options.ProviderDeployConfig, "hub"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Hub: maputil.GetString(options.ProviderExtendedConfig, "hub"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -732,8 +734,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeTencentCloudCDN: deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{ ApiKey: access.ApiKey, - InstanceId: maputil.GetInt32(options.ProviderDeployConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + InstanceId: maputil.GetInt32(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -753,8 +755,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiToken: access.ApiToken, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetInt32(options.ProviderDeployConfig, "certificateId"), + ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetInt32(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err } @@ -773,16 +775,16 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - UseSCP: maputil.GetBool(options.ProviderDeployConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderDeployConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderDeployConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderDeployConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderDeployConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderDeployConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderDeployConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderDeployConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderDeployConfig, "jksStorepass"), + UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } @@ -799,7 +801,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCDN.NewDeployer(&pTencentCloudCDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -807,11 +809,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCLB.NewDeployer(&pTencentCloudCLB.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -819,9 +821,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCOS.NewDeployer(&pTencentCloudCOS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -829,7 +831,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCSS.NewDeployer(&pTencentCloudCSS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -837,7 +839,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudECDN.NewDeployer(&pTencentCloudECDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -845,8 +847,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudEO.NewDeployer(&pTencentCloudEO.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderDeployConfig, "zoneId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -854,8 +856,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -870,9 +872,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: maputil.GetString(options.ProviderDeployConfig, "resourceType"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: maputil.GetString(options.ProviderExtendedConfig, "resourceType"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -880,8 +882,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudVOD.NewDeployer(&pTencentCloudVOD.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - SubAppId: maputil.GetInt64(options.ProviderDeployConfig, "subAppId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + SubAppId: maputil.GetInt64(options.ProviderExtendedConfig, "subAppId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -889,9 +891,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudWAF.NewDeployer(&pTencentCloudWAF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), - DomainId: maputil.GetString(options.ProviderDeployConfig, "domainId"), - InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), + InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), }) return deployer, err @@ -913,7 +915,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - DomainId: maputil.GetString(options.ProviderDeployConfig, "domainId"), + DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), }) return deployer, err @@ -922,9 +924,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -945,7 +947,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ Username: access.Username, Password: access.Password, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -966,11 +968,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineALB.NewDeployer(&pVolcEngineALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -978,7 +980,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCDN.NewDeployer(&pVolcEngineCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -986,7 +988,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCertCenter.NewDeployer(&pVolcEngineCertCenter.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err @@ -994,10 +996,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCLB.NewDeployer(&pVolcEngineCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err @@ -1005,7 +1007,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineDCDN.NewDeployer(&pVolcEngineDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -1013,9 +1015,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineImageX.NewDeployer(&pVolcEngineImageX.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceId: maputil.GetString(options.ProviderDeployConfig, "serviceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceId: maputil.GetString(options.ProviderExtendedConfig, "serviceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -1023,7 +1025,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineLive.NewDeployer(&pVolcEngineLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -1031,9 +1033,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineTOS.NewDeployer(&pVolcEngineTOS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -1055,10 +1057,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, ApiKey: access.ApiKey, - Environment: maputil.GetOrDefaultString(options.ProviderDeployConfig, "environment", "production"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), - WebhookId: maputil.GetString(options.ProviderDeployConfig, "webhookId"), + Environment: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + WebhookId: maputil.GetString(options.ProviderExtendedConfig, "webhookId"), }) return deployer, err @@ -1074,9 +1076,31 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + mergedHeaders := make(map[string]string) + if defaultHeadersString := access.HeadersString; defaultHeadersString != "" { + h, err := httputil.ParseHeaders(defaultHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + h, err := httputil.ParseHeaders(extendedHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetString(options.ProviderDeployConfig, "webhookData"), + WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.TemplateDataForDeployment), + Method: access.Method, + Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, }) return deployer, err diff --git a/internal/domain/access.go b/internal/domain/access.go index 1fdea54dc..b9f4de6b5 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -261,8 +261,12 @@ type AccessConfigForWangsu struct { } type AccessConfigForWebhook struct { - Url string `json:"url"` - AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + Url string `json:"url"` + Method string `json:"method,omitempty"` + HeadersString string `json:"headers,omitempty"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + TemplateDataForDeployment string `json:"templateDataForDeployment,omitempty"` // TODO: + TemplateDataForNotification string `json:"templateDataForNotification,omitempty"` // TODO: } type AccessConfigForWestcn struct { diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 639a31ca0..65bc35d0f 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -114,10 +114,10 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { ContactEmail: maputil.GetString(n.Config, "contactEmail"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), CAProvider: maputil.GetString(n.Config, "caProvider"), CAProviderAccessId: maputil.GetString(n.Config, "caProviderAccessId"), - CAProviderConfig: maputil.GetMap(n.Config, "caProviderConfig"), + CAProviderConfig: maputil.GetKVMapAny(n.Config, "caProviderConfig"), KeyAlgorithm: maputil.GetString(n.Config, "keyAlgorithm"), Nameservers: maputil.GetString(n.Config, "nameservers"), DnsPropagationTimeout: maputil.GetInt32(n.Config, "dnsPropagationTimeout"), @@ -141,7 +141,7 @@ func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy { Certificate: maputil.GetString(n.Config, "certificate"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), SkipOnLastSucceeded: maputil.GetBool(n.Config, "skipOnLastSucceeded"), } } @@ -151,7 +151,7 @@ func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify { Channel: maputil.GetString(n.Config, "channel"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), Subject: maputil.GetString(n.Config, "subject"), Message: maputil.GetString(n.Config, "message"), } diff --git a/internal/notify/notifier.go b/internal/notify/notifier.go index 59d6d4757..955e88c35 100644 --- a/internal/notify/notifier.go +++ b/internal/notify/notifier.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error nodeConfig := config.Node.GetConfigForNotify() options := ¬ifierProviderOptions{ - Provider: domain.NotificationProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderNotifyConfig: nodeConfig.ProviderConfig, + Provider: domain.NotificationProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderExtendedConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/notify/providers.go b/internal/notify/providers.go index 7f06908e6..da22b12a2 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -2,6 +2,7 @@ package notify import ( "fmt" + "net/http" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" @@ -9,13 +10,14 @@ import ( pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) type notifierProviderOptions struct { - Provider domain.NotificationProviderType - ProviderAccessConfig map[string]any - ProviderNotifyConfig map[string]any + Provider domain.NotificationProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any } func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) { @@ -37,8 +39,8 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier SmtpTls: access.SmtpTls, Username: access.Username, Password: access.Password, - SenderAddress: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "senderAddress", access.DefaultSenderAddress), - ReceiverAddress: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "receiverAddress", access.DefaultReceiverAddress), + SenderAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "senderAddress", access.DefaultSenderAddress), + ReceiverAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", access.DefaultReceiverAddress), }) } @@ -53,7 +55,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier ServerUrl: access.ServerUrl, Username: access.Username, Password: access.Password, - ChannelId: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "channelId", access.DefaultChannelId), + ChannelId: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "channelId", access.DefaultChannelId), }) } @@ -66,7 +68,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ BotToken: access.BotToken, - ChatId: maputil.GetOrDefaultInt64(options.ProviderNotifyConfig, "chatId", access.DefaultChatId), + ChatId: maputil.GetOrDefaultInt64(options.ProviderExtendedConfig, "chatId", access.DefaultChatId), }) } @@ -77,8 +79,31 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + mergedHeaders := make(map[string]string) + if defaultHeadersString := access.HeadersString; defaultHeadersString != "" { + h, err := httputil.ParseHeaders(defaultHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + h, err := httputil.ParseHeaders(extendedHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ - Url: access.Url, + WebhookUrl: access.Url, + WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.TemplateDataForNotification), + Method: access.Method, + Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, }) } diff --git a/internal/notify/providers_deprecated.go b/internal/notify/providers_deprecated.go index d554da0d9..43eadf713 100644 --- a/internal/notify/providers_deprecated.go +++ b/internal/notify/providers_deprecated.go @@ -94,7 +94,7 @@ func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, c case domain.NotifyChannelTypeWebhook: return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), + WebhookUrl: maputil.GetString(channelConfig, "url"), AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"), }) diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 28e0bd7d4..f405ff96c 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log/slog" + "net/http" "strings" "time" @@ -18,8 +19,13 @@ import ( type DeployerConfig struct { // Webhook URL。 WebhookUrl string `json:"webhookUrl"` - // Webhook 回调数据(JSON 格式)。 + // Webhook 回调数据(application/json 或 application/x-www-form-urlencoded 格式)。 WebhookData string `json:"webhookData,omitempty"` + // 请求谓词。 + // 零值时默认为 "POST"。 + Method string `json:"method,omitempty"` + // 请求标头。 + Headers map[string]string `json:"headers,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } @@ -68,25 +74,41 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } var webhookData interface{} - err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData) - if err != nil { - return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err) + if d.config.WebhookData == "" { + webhookData = map[string]any{ + "name": strings.Join(certX509.DNSNames, ";"), + "cert": certPEM, + "privkey": privkeyPEM, + } + } else { + err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err) + } + + replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) + replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) + replaceJsonValueRecursively(webhookData, "${SUBJECT_ALT_NAMES}", strings.Join(certX509.DNSNames, ";")) + replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) + replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) } - replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) - replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) - replaceJsonValueRecursively(webhookData, "${SUBJECT_ALT_NAMES}", strings.Join(certX509.DNSNames, ";")) - replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) - replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) - - resp, err := d.httpClient.R(). + req := d.httpClient.R(). SetContext(ctx). + SetHeaders(d.config.Headers) + req.URL = d.config.WebhookUrl + req.Method = d.config.Method + if req.Method == "" { + req.Method = http.MethodPost + } + + resp, err := req. SetHeader("Content-Type", "application/json"). SetBody(webhookData). - Post(d.config.WebhookUrl) + Send() if err != nil { return nil, fmt.Errorf("failed to send webhook request: %w", err) - } else if resp.StatusCode() != 200 { + } else if resp.IsError() { return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode()) } diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go index f2dfb4b6e..df3b8d494 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -3,24 +3,36 @@ package webhook import ( "context" "crypto/tls" + "encoding/json" + "fmt" "log/slog" "net/http" + "strings" + "time" - webhook "github.com/nikoksr/notify/service/http" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { // Webhook URL。 - Url string `json:"url"` + WebhookUrl string `json:"webhookUrl"` + // Webhook 回调数据(application/json 或 application/x-www-form-urlencoded 格式)。 + WebhookData string `json:"webhookData,omitempty"` + // 请求谓词。 + // 零值时默认为 "POST"。 + Method string `json:"method,omitempty"` + // 请求标头。 + Headers map[string]string `json:"headers,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -30,8 +42,18 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New(). + SetTimeout(30 * time.Second). + SetRetryCount(3). + SetRetryWaitTime(5 * time.Second) + if config.AllowInsecureConnections { + client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + } + return &NotifierProvider{ - config: config, + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -45,20 +67,58 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := webhook.New() - srv.AddReceiversURLs(n.config.Url) + var webhookData interface{} + if n.config.WebhookData == "" { + webhookData = map[string]any{ + "subject": subject, + "message": message, + } + } else { + err = json.Unmarshal([]byte(n.config.WebhookData), &webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err) + } - if n.config.AllowInsecureConnections { - tlsConfig := &tls.Config{InsecureSkipVerify: true} - transport := &http.Transport{TLSClientConfig: tlsConfig} - client := &http.Client{Transport: transport} - srv.WithClient(client) + replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) + replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) } - err = srv.Send(ctx, subject, message) + req := n.httpClient.R(). + SetContext(ctx). + SetHeaders(n.config.Headers) + req.URL = n.config.WebhookUrl + req.Method = n.config.Method + if req.Method == "" { + req.Method = http.MethodPost + } + + resp, err := req. + SetHeader("Content-Type", "application/json"). + SetBody(webhookData). + Send() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to send webhook request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode()) } + n.logger.Debug("webhook responded", slog.Any("response", resp.String())) + return ¬ifier.NotifyResult{}, nil } + +func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { + switch v := data.(type) { + case map[string]any: + for k, val := range v { + v[k] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case []any: + for i, val := range v { + v[i] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case string: + return strings.ReplaceAll(v, oldStr, newStr) + } + return data +} diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index ffe255930..3655d76b0 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -39,7 +39,7 @@ func TestNotify(t *testing.T) { }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - Url: fUrl, + WebhookUrl: fUrl, AllowInsecureConnections: true, }) if err != nil { diff --git a/internal/pkg/utils/http/parser.go b/internal/pkg/utils/http/parser.go new file mode 100644 index 000000000..872fb6b5d --- /dev/null +++ b/internal/pkg/utils/http/parser.go @@ -0,0 +1,33 @@ +package httputil + +import ( + "bufio" + "net/http" + "net/textproto" + "strings" +) + +// 从表示 HTTP 标头的字符串解析并返回一个 http.Header 对象。 +// +// 入参: +// - headers: 表示 HTTP 标头的字符串。 +// +// 出参: +// - header: http.Header 对象。 +// - err: 错误。 +func ParseHeaders(headers string) (http.Header, error) { + str := strings.TrimSpace(headers) + "\r\n\r\n" + if len(str) == 4 { + return make(http.Header), nil + } + + br := bufio.NewReader(strings.NewReader(str)) + tp := textproto.NewReader(br) + + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil { + return nil, err + } + + return http.Header(mimeHeader), err +} diff --git a/internal/pkg/utils/map/getter.go b/internal/pkg/utils/map/getter.go index b4b654e34..f30f6d332 100644 --- a/internal/pkg/utils/map/getter.go +++ b/internal/pkg/utils/map/getter.go @@ -199,6 +199,28 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool { return defaultValue } +// 以 `map[string]V` 形式从字典中获取指定键的值。 +// +// 入参: +// - dict: 字典。 +// - key: 键。 +// +// 出参: +// - 字典中键对应的 `map[string]V` 对象。 +func GetKVMap[V any](dict map[string]any, key string) map[string]V { + if dict == nil { + return make(map[string]V) + } + + if val, ok := dict[key]; ok { + if result, ok := val.(map[string]V); ok { + return result + } + } + + return make(map[string]V) +} + // 以 `map[string]any` 形式从字典中获取指定键的值。 // // 入参: @@ -207,16 +229,6 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool { // // 出参: // - 字典中键对应的 `map[string]any` 对象。 -func GetMap(dict map[string]any, key string) map[string]any { - if dict == nil { - return make(map[string]any) - } - - if val, ok := dict[key]; ok { - if result, ok := val.(map[string]any); ok { - return result - } - } - - return make(map[string]any) +func GetKVMapAny(dict map[string]any, key string) map[string]any { + return GetKVMap[any](dict, key) } diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index f15fe221e..0a22d93e0 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Switch } from "antd"; +import { Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -18,6 +18,9 @@ export type AccessFormWebhookConfigProps = { const initFormModel = (): AccessFormWebhookConfigFieldValues => { return { url: "", + method: "POST", + headers: "Content-Type: application/json", + allowInsecureConnections: false, }; }; @@ -26,10 +29,34 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa const formSchema = z.object({ url: z.string().url(t("common.errmsg.url_invalid")), + method: z.union([z.literal("GET"), z.literal("POST"), z.literal("PUT"), z.literal("PATCH"), z.literal("DELETE")], { + message: t("access.form.webhook_method.placeholder"), + }), + headers: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + const lines = v.split(/\r?\n/); + for (const line of lines) { + if (line.split(":").length < 2) { + return false; + } + } + return true; + }, t("access.form.webhook_headers.errmsg.invalid")), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); + const handleWebhookHeadersBlur = (e: React.FocusEvent) => { + let value = e.target.value; + value = value.trim(); + value = value.replace(/(?) => { onValuesChange?.(values); }; @@ -47,6 +74,22 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa + + + - + ); diff --git a/ui/src/components/access/AccessFormMattermostConfig.tsx b/ui/src/components/access/AccessFormMattermostConfig.tsx index f4a99ecef..a583cc19e 100644 --- a/ui/src/components/access/AccessFormMattermostConfig.tsx +++ b/ui/src/components/access/AccessFormMattermostConfig.tsx @@ -70,7 +70,7 @@ const AccessFormMattermostConfig = ({ form: formInst, formName, disabled, initia rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormTelegramConfig.tsx b/ui/src/components/access/AccessFormTelegramConfig.tsx index 43b20f00c..a4eccafb1 100644 --- a/ui/src/components/access/AccessFormTelegramConfig.tsx +++ b/ui/src/components/access/AccessFormTelegramConfig.tsx @@ -72,7 +72,7 @@ const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialV rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 0a22d93e0..f05d3097f 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -1,8 +1,9 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Select, Switch } from "antd"; +import { Alert, Button, Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import Show from "@/components/Show"; import { type AccessConfigForWebhook } from "@/domain/access"; type AccessFormWebhookConfigFieldValues = Nullish; @@ -12,6 +13,7 @@ export type AccessFormWebhookConfigProps = { formName: string; disabled?: boolean; initialValues?: AccessFormWebhookConfigFieldValues; + usage?: "deployment" | "notification" | "none"; onValuesChange?: (values: AccessFormWebhookConfigFieldValues) => void; }; @@ -21,10 +23,27 @@ const initFormModel = (): AccessFormWebhookConfigFieldValues => { method: "POST", headers: "Content-Type: application/json", allowInsecureConnections: false, + defaultDataForDeployment: JSON.stringify( + { + name: "${DOMAINS}", + cert: "${CERTIFICATE}", + privkey: "${PRIVATE_KEY}", + }, + null, + 2 + ), + defaultDataForNotification: JSON.stringify( + { + subject: "${SUBJECT}", + message: "${MESSAGE}", + }, + null, + 2 + ), }; }; -const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormWebhookConfigProps) => { +const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, usage, onValuesChange }: AccessFormWebhookConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -47,6 +66,34 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa return true; }, t("access.form.webhook_headers.errmsg.invalid")), allowInsecureConnections: z.boolean().nullish(), + defaultDataForDeployment: z + .string() + .nullish() + .refine((v) => { + if (usage && usage !== "deployment") return true; + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("access.form.webhook_default_data.errmsg.json_invalid")), + defaultDataForNotification: z + .string() + .nullish() + .refine((v) => { + if (usage && usage !== "notification") return true; + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("access.form.webhook_default_data.errmsg.json_invalid")), }); const formRule = createSchemaFieldRule(formSchema); @@ -57,6 +104,34 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa formInst.setFieldValue("headers", value); }; + const handleWebhookDataForDeploymentBlur = (e: React.FocusEvent) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("defaultDataForDeployment", json); + } catch { + return; + } + }; + + const handleWebhookDataForNotificationBlur = (e: React.FocusEvent) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("defaultDataForNotification", json); + } catch { + return; + } + }; + + const handlePresetDataForDeploymentClick = () => { + formInst.setFieldValue("defaultDataForDeployment", initFormModel().defaultDataForDeployment); + }; + + const handlePresetDataForNotificationClick = () => { + formInst.setFieldValue("defaultDataForNotification", initFormModel().defaultDataForNotification); + }; + const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -90,6 +165,60 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa + + + + + + + + + + } /> + + + + + + + + + + + + + } /> + + + { - if (!!record.reserve && record.reserve !== "ca") return false; + if (record.reserve !== "ca") return false; if (fieldCAProvider) return caProvidersMap.get(fieldCAProvider)?.provider === record.provider; const provider = accessProvidersMap.get(record.provider); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWebhookConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWebhookConfig.tsx index b916abf10..8305da8c7 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormWebhookConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWebhookConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Alert, Button, Form, type FormInstance, Input } from "antd"; +import { Alert, Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -16,31 +16,26 @@ export type DeployNodeConfigFormWebhookConfigProps = { }; const initFormModel = (): DeployNodeConfigFormWebhookConfigFieldValues => { - return { - webhookData: JSON.stringify( - { - name: "${DOMAINS}", - cert: "${CERTIFICATE}", - privkey: "${PRIVATE_KEY}", - }, - null, - 2 - ), - }; + return {}; }; const DeployNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormWebhookConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ - webhookData: z.string({ message: t("workflow_node.deploy.form.webhook_data.placeholder") }).refine((v) => { - try { - JSON.parse(v); - return true; - } catch { - return false; - } - }, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")), + webhookData: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")), }); const formRule = createSchemaFieldRule(formSchema); @@ -54,10 +49,6 @@ const DeployNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled, } }; - const handlePresetDataClick = () => { - formInst.setFieldValue("webhookData", initFormModel().webhookData); - }; - const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -71,24 +62,18 @@ const DeployNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled, name={formName} onValuesChange={handleFormChange} > - - - - - + } + > + diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx index 71110e320..13dc10192 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx @@ -2,13 +2,14 @@ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } f import { useTranslation } from "react-i18next"; import { Link } from "react-router"; import { PlusOutlined as PlusOutlinedIcon, RightOutlined as RightOutlinedIcon } from "@ant-design/icons"; -import { Button, Form, type FormInstance, Input, Select } from "antd"; +import { Button, Divider, Form, type FormInstance, Input, Select, Typography } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; import NotificationProviderSelect from "@/components/provider/NotificationProviderSelect"; +import Show from "@/components/Show"; import { ACCESS_USAGES, NOTIFICATION_PROVIDERS, accessProvidersMap, notificationProvidersMap } from "@/domain/provider"; import { notifyChannelsMap } from "@/domain/settings"; import { type WorkflowNodeConfigForNotify } from "@/domain/workflow"; @@ -19,6 +20,7 @@ import { useNotifyChannelsStore } from "@/stores/notify"; import NotifyNodeConfigFormEmailConfig from "./NotifyNodeConfigFormEmailConfig"; import NotifyNodeConfigFormMattermostConfig from "./NotifyNodeConfigFormMattermostConfig"; import NotifyNodeConfigFormTelegramConfig from "./NotifyNodeConfigFormTelegramConfig"; +import NotifyNodeConfigFormWebhookConfig from "./NotifyNodeConfigFormWebhookConfig"; type NotifyNodeConfigFormFieldValues = Partial; @@ -114,6 +116,8 @@ const NotifyNodeConfigForm = forwardRef; case NOTIFICATION_PROVIDERS.TELEGRAM: return ; + case NOTIFICATION_PROVIDERS.WEBHOOK: + return ; } }, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]); @@ -250,7 +254,7 @@ const NotifyNodeConfigForm = forwardRef { - if (!!record.reserve && record.reserve !== "notification") return false; + if (record.reserve !== "notification") return false; const provider = accessProvidersMap.get(record.provider); return !!provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION); @@ -261,7 +265,15 @@ const NotifyNodeConfigForm = forwardRef - {nestedFormEl} + + + + {t("workflow_node.notify.form.params_config.label")} + + + + {nestedFormEl} + ); } diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx index f7129a10c..b6bfed17a 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx @@ -62,7 +62,7 @@ const NotifyNodeConfigFormEmailConfig = ({ form: formInst, formName, disabled, i rules={[formRule]} tooltip={} > - + } > - + ); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx index 75f72c3c3..c091298b2 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx @@ -52,7 +52,7 @@ const NotifyNodeConfigFormMattermostConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx index b3cd6788c..077744134 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx @@ -57,7 +57,7 @@ const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx new file mode 100644 index 000000000..acaf00c25 --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx @@ -0,0 +1,86 @@ +import { useTranslation } from "react-i18next"; +import { Alert, Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormWebhookConfigFieldValues = Nullish<{ + webhookData: string; +}>; + +export type NotifyNodeConfigFormWebhookConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormWebhookConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormWebhookConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormWebhookConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormWebhookConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookData: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("workflow_node.notify.form.webhook_data.errmsg.json_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleWebhookDataBlur = (e: React.FocusEvent) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("webhookData", json); + } catch { + return; + } + }; + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + } /> + +
+ ); +}; + +export default NotifyNodeConfigFormWebhookConfig; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 201444814..a9930db30 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -28,7 +28,7 @@ "access.form.name.placeholder": "Please enter authorization name", "access.form.provider.label": "Provider", "access.form.provider.placeholder": "Please select a provider", - "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Host provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", + "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", "access.form.certificate_authority.label": "Certificate authority", "access.form.certificate_authority.placeholder": "Please select a certificate authority", "access.form.notification_channel.label": "Notification channel", @@ -346,6 +346,14 @@ "access.form.webhook_headers.placeholder": "Please enter Webhook request headers", "access.form.webhook_headers.errmsg.invalid": "Please enter a valid request headers", "access.form.webhook_headers.tooltip": "Format:
key1: val2
key2: val2


Example:
Content-Type: application/json
User-Agent: certimate
", + "access.form.webhook_default_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "access.form.webhook_default_data_for_deployment.label": "Webhook data for deployment (Optional)", + "access.form.webhook_default_data_for_deployment.placeholder": "Please enter Webhook data", + "access.form.webhook_default_data_for_deployment.guide": "Tips: The Webhook data should be in JSON format.

The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
  1. ${DOMAIN}: The primary domain of the certificate (CommonName).
  2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
  3. ${CERTIFICATE}: The PEM format content of the certificate file.
  4. ${PRIVATE_KEY}: The PEM format content of the private key file.

When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
  1. application/json (default).
  2. application/x-www-form-urlencoded: Nested data is not supported.
  3. multipart/form-data: Nested data is not supported.
  4. ", + "access.form.webhook_default_data_for_notification.label": "Webhook data for notification (Optional)", + "access.form.webhook_default_data_for_notification.placeholder": "Please enter Webhook data", + "access.form.webhook_default_data_for_notification.guide": "Tips: The Webhook data should be in JSON format.

    The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
    1. ${SUBJECT}: The subject of notification.
    2. ${MESSAGE}: The message of notification.

    When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
    1. application/json (default).
    2. application/x-www-form-urlencoded: Nested data is not supported.
    3. multipart/form-data: Nested data is not supported.
    4. ", + "access.form.webhook_default_data_preset.button": "Use preset template", "access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.webhook_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", "access.form.webhook_allow_insecure_conns.switch.on": "Allow", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index aa91b8af6..8a353e51f 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -93,8 +93,8 @@ "workflow_node.deploy.search.provider.placeholder": "Search deploy target ...", "workflow_node.deploy.form.provider.label": "Deploy target", "workflow_node.deploy.form.provider.placeholder": "Please select deploy target", - "workflow_node.deploy.form.provider_access.label": "Host provider authorization", - "workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider", + "workflow_node.deploy.form.provider_access.label": "Hosting provider authorization", + "workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of Hosting provider", "workflow_node.deploy.form.provider_access.tooltip": "Used to invoke API during deployment.", "workflow_node.deploy.form.provider_access.button": "Create", "workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", @@ -685,11 +685,11 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "Wangsu Cloud CDN Webhook ID (Optional)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "Please enter Wangsu Cloud CDN Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "For more information, see https://cdnpro.console.wangsu.com/v2/index/#/certificate", - "workflow_node.deploy.form.webhook_data.label": "Webhook data (JSON format)", - "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", - "workflow_node.deploy.form.webhook_data.guide": "Tips: The Webhook data should be a key-value pair in JSON format. The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL.

      Supported variables:
      ${DOMAIN}: The primary domain of the certificate (CommonName).
      ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
      ${CERTIFICATE}: The PEM format content of the certificate file.
      ${PRIVATE_KEY}: The PEM format content of the private key file.", + "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", + "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", + "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", + "workflow_node.deploy.form.webhook_data.guide": "Supported variables:
      1. ${DOMAIN}: The primary domain of the certificate (CommonName).
      2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
      3. ${CERTIFICATE}: The PEM format content of the certificate file.
      4. ${PRIVATE_KEY}: The PEM format content of the private key file.

      Please visit the authorization management page for addtional notes.", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", - "workflow_node.deploy.form.webhook_data_preset.button": "Use preset template", "workflow_node.deploy.form.strategy_config.label": "Strategy settings", "workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ", @@ -720,18 +720,24 @@ "workflow_node.notify.form.provider_access.label": "Notification provider authorization", "workflow_node.notify.form.provider_access.placeholder": "Please select an authorization of notification provider", "workflow_node.notify.form.provider_access.button": "Create", + "workflow_node.notify.form.params_config.label": "Parameter settings", "workflow_node.notify.form.email_sender_address.label": "Sender email address (Optional)", - "workflow_node.notify.form.email_sender_address.placeholder": "Please enter sender email address", + "workflow_node.notify.form.email_sender_address.placeholder": "Please enter sender email address to override the default value", "workflow_node.notify.form.email_sender_address.tooltip": "Leave it blank to use the default sender email address provided by the authorization.", "workflow_node.notify.form.email_receiver_address.label": "Receiver email address (Optional)", - "workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address", + "workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address to override the default value", "workflow_node.notify.form.email_receiver_address.tooltip": "Leave it blank to use the default receiver email address provided by the selected authorization.", "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)", - "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value", "workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", "workflow_node.notify.form.telegram_chat_id.label": "Telegram chat ID (Optional)", - "workflow_node.notify.form.telegram_chat_id.placeholder": "Please enter Telegram chat ID", + "workflow_node.notify.form.telegram_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", "workflow_node.notify.form.telegram_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", + "workflow_node.notify.form.webhook_data.label": "Webhook data (Optional)", + "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", + "workflow_node.notify.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", + "workflow_node.notify.form.webhook_data.guide": "Supported variables:
      1. ${SUBJECT}: The subject of notification.
      2. ${MESSAGE}: The message of notification.

      Please visit the authorization management page for addtional notes.", + "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", "workflow_node.end.label": "End", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 9de079d1c..0f26e6320 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -346,6 +346,14 @@ "access.form.webhook_headers.placeholder": "请输入 Webhook 请求标头", "access.form.webhook_headers.errmsg.invalid": "请输入有效的请求标头", "access.form.webhook_headers.tooltip": "格式:
      key1: val2
      key2: val2


      示例:
      Content-Type: application/json
      User-Agent: certimate
      ", + "access.form.webhook_default_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", + "access.form.webhook_default_data_for_deployment.label": "默认的 Webhook 部署证书回调数据(可选)", + "access.form.webhook_default_data_for_deployment.placeholder": "请输入默认的 Webhook 回调数据", + "access.form.webhook_default_data_for_deployment.guide": "小贴士:回调数据是一个 JSON 格式的数据。

      其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
      1. ${DOMAIN}:证书的主域名(即 CommonName)。
      2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
      3. ${CERTIFICATE}:证书文件 PEM 格式内容。
      4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

      当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
      1. application/json(默认)。
      2. application/x-www-form-urlencoded:不支持嵌套数据。
      3. multipart/form-data:不支持嵌套数据。
      4. ", + "access.form.webhook_default_data_for_notification.label": "默认的 Webhook 推送通知回调数据(可选)", + "access.form.webhook_default_data_for_notification.placeholder": "请输入默认的 Webhook 回调数据", + "access.form.webhook_default_data_for_notification.guide": "小贴士:回调数据是一个 JSON 格式的数据。

        其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
        1. ${DOMAIN}:证书的主域名(即 CommonName)。
        2. ${SUBJECT}:通知主题。
        3. ${MESSAGE}:通知内容。

        当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
        1. application/json(默认)。
        2. application/x-www-form-urlencoded:不支持嵌套数据。
        3. multipart/form-data:不支持嵌套数据。
        4. ", + "access.form.webhook_default_data_preset.button": "使用预设模板", "access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.webhook_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", "access.form.webhook_allow_insecure_conns.switch.on": "允许", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 31bcf762c..d58ebe909 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -684,11 +684,11 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "网宿云 CDN Pro 部署任务 Webhook ID(可选)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "请输入网宿云 CDN Pro 部署任务 Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", - "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(JSON 格式)", + "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据", - "workflow_node.deploy.form.webhook_data.guide": "小贴士:回调数据是一个 JSON 格式的键值对。其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。

          支持的变量:
          ${DOMAIN}:证书的主域名(即 CommonName)
          ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)
          ${CERTIFICATE}:证书文件 PEM 格式内容
          ${PRIVATE_KEY}:私钥文件 PEM 格式内容", + "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", + "workflow_node.deploy.form.webhook_data.guide": "支持的变量:
          1. ${DOMAIN}:证书的主域名(即 CommonName)。
          2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
          3. ${CERTIFICATE}:证书文件 PEM 格式内容。
          4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

          其他注意事项请前往授权管理页面查看。", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", - "workflow_node.deploy.form.webhook_data_preset.button": "使用预设模板", "workflow_node.deploy.form.strategy_config.label": "执行策略", "workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,", @@ -719,18 +719,24 @@ "workflow_node.notify.form.provider_access.label": "通知渠道授权", "workflow_node.notify.form.provider_access.placeholder": "请选择通知渠道授权", "workflow_node.notify.form.provider_access.button": "新建", + "workflow_node.notify.form.params_config.label": "参数设置", "workflow_node.notify.form.email_sender_address.label": "发送邮箱地址(可选)", - "workflow_node.notify.form.email_sender_address.placeholder": "请输入发送邮箱地址", + "workflow_node.notify.form.email_sender_address.placeholder": "请输入发送邮箱地址以覆盖默认值", "workflow_node.notify.form.email_sender_address.tooltip": "不填写时,将使用所选通知渠道授权的默认发送邮箱地址。", "workflow_node.notify.form.email_receiver_address.label": "接收邮箱地址(可选)", - "workflow_node.notify.form.email_receiver_address.placeholder": "请输入接收邮箱地址", + "workflow_node.notify.form.email_receiver_address.placeholder": "请输入接收邮箱地址以覆盖默认值", "workflow_node.notify.form.email_receiver_address.tooltip": "不填写时,将使用所选通知渠道授权的默认接收邮箱地址。", "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID(可选)", - "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID 以覆盖默认值", "workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", "workflow_node.notify.form.telegram_chat_id.label": "Telegram 会话 ID(可选)", - "workflow_node.notify.form.telegram_chat_id.placeholder": "请输入 Telegram 会话 ID", + "workflow_node.notify.form.telegram_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", "workflow_node.notify.form.telegram_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", + "workflow_node.notify.form.webhook_data.label": "Webhook 回调数据(可选)", + "workflow_node.notify.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", + "workflow_node.notify.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", + "workflow_node.notify.form.webhook_data.guide": "支持的变量:
          1. ${DOMAIN}:证书的主域名(即 CommonName)。
          2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
          3. ${CERTIFICATE}:证书文件 PEM 格式内容。
          4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

          其他注意事项请前往授权管理页面查看。", + "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "workflow_node.end.label": "结束", From 7e707cd9739c2d4d6f524af6f053be97ceea57ba Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 27 Apr 2025 22:44:10 +0800 Subject: [PATCH 14/17] feat: webhook preset template data --- .../access/AccessFormWebhookConfig.tsx | 151 ++++++++++++++++-- .../workflow/node/ApplyNodeConfigForm.tsx | 4 +- ui/src/i18n/locales/en/nls.access.json | 8 +- ui/src/i18n/locales/zh/nls.access.json | 8 +- 4 files changed, 159 insertions(+), 12 deletions(-) diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index f05d3097f..e393b0580 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -1,5 +1,6 @@ import { useTranslation } from "react-i18next"; -import { Alert, Button, Form, type FormInstance, Input, Select, Switch } from "antd"; +import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons"; +import { Alert, Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -128,8 +129,124 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa formInst.setFieldValue("defaultDataForDeployment", initFormModel().defaultDataForDeployment); }; - const handlePresetDataForNotificationClick = () => { - formInst.setFieldValue("defaultDataForNotification", initFormModel().defaultDataForNotification); + const handlePresetDataForNotificationClick = (key: string) => { + switch (key) { + case "bark": + formInst.setFieldValue("url", "https://api.day.app/push"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json\r\nAuthorization: Bearer "); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + title: "${SUBJECT}", + body: "${MESSAGE}", + group: "", + device_keys: "", + }, + null, + 2 + ) + ); + break; + + case "gotify": + formInst.setFieldValue("url", "https:///"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json\r\nAuthorization: Bearer "); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + title: "${SUBJECT}", + message: "${MESSAGE}", + priority: 1, + }, + null, + 2 + ) + ); + break; + + case "ntfy": + formInst.setFieldValue("url", "https:///"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + topic: "", + title: "${SUBJECT}", + message: "${MESSAGE}", + priority: 1, + }, + null, + 2 + ) + ); + break; + + case "pushover": + formInst.setFieldValue("url", "https://api.pushover.net/1/messages.json"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + token: "", + user: "", + title: "${SUBJECT}", + message: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + case "pushplus": + formInst.setFieldValue("url", "https://www.pushplus.plus/send"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + token: "", + title: "${SUBJECT}", + content: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + case "serverchan": + formInst.setFieldValue("url", "https://sctapi.ftqq.com/.send"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + text: "${SUBJECT}", + desp: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + default: + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue("defaultDataForNotification", initFormModel().defaultDataForNotification); + break; + } }; const handleFormChange = (_: unknown, values: z.infer) => { @@ -169,10 +286,12 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa