Compare commits

..

68 Commits

Author SHA1 Message Date
Fu Diwei
a4f736e0f3 build: fix goreleaser.yml 2025-05-27 05:04:35 +08:00
Fu Diwei
d8935337d6 build: fix goreleaser.yml 2025-05-27 04:59:38 +08:00
Fu Diwei
211f66dc0a fix: tsc build error 2025-05-27 04:43:44 +08:00
RHQYZ
d964b129b0 bump version to v0.3.14 2025-05-27 04:35:10 +08:00
RHQYZ
a758b1d6d4 Merge pull request #727 from fudiwei/feat/providers
new providers
2025-05-27 04:34:07 +08:00
Fu Diwei
037305d8cd update README 2025-05-27 04:31:26 +08:00
Fu Diwei
cfdd3c621f feat: new deployment provider: unicloud webhost 2025-05-27 04:29:51 +08:00
Fu Diwei
5339963524 feat(ui): improve i18n 2025-05-26 23:02:57 +08:00
RHQYZ
af7d05e669 Merge pull request #726 from fudiwei/feat/providers
new providers
2025-05-26 21:52:02 +08:00
Fu Diwei
bf1d03a30e feat(migration): tracer 2025-05-26 21:49:37 +08:00
Fu Diwei
3bb88d9f93 chore(deps): upgrade npm dependencies 2025-05-26 17:04:40 +08:00
Fu Diwei
b0eb71421f chore(deps): upgrade go mod dependencies 2025-05-26 16:55:15 +08:00
Fu Diwei
e82a59289b feat: new notification provider: slack bot 2025-05-26 16:41:44 +08:00
Fu Diwei
8e23b14bf3 feat: new notification provider: discord bot 2025-05-26 16:41:38 +08:00
Fu Diwei
cd9dac7765 feat: new acme dns-01 provider: duckdns 2025-05-26 13:59:22 +08:00
Fu Diwei
40f4488009 feat: new acme dns-01 provider: digitalocean 2025-05-26 13:59:22 +08:00
Fu Diwei
4c13a3e86a feat: new acme dns-01 provider: hetzner 2025-05-26 13:59:16 +08:00
Fu Diwei
b139139f50 Merge branch 'main' into feat/providers 2025-05-26 11:37:01 +08:00
RHQYZ
55e16f4b17 Merge pull request #725 from fudiwei/bugfix
bugfix
2025-05-26 11:30:31 +08:00
Fu Diwei
46b4ff73c9 fix: ari double renewal error 2025-05-26 11:29:04 +08:00
Fu Diwei
970a1f0f79 fix: i18n error in AccessEditModal 2025-05-26 10:17:25 +08:00
Fu Diwei
11ff80ab28 fix: #723 2025-05-26 09:27:33 +08:00
Fu Diwei
7cd036f41e fix: #724 2025-05-26 09:26:11 +08:00
Fu Diwei
0909671be6 refactor: clean code 2025-05-25 23:32:41 +08:00
Fu Diwei
b798b824db refactor(ui): MultipleSplitValueInput 2025-05-25 23:05:08 +08:00
Fu Diwei
71c093c042 refactor: clean code 2025-05-25 21:54:39 +08:00
Yoan.liu
9878c12512 Merge pull request #722 from KukiSa/main
Fix Netlify Site Inversion of ca_certificates and key
2025-05-25 08:30:14 +08:00
Signaliks
b43797a0fb Update models.go 2025-05-24 21:32:34 +08:00
Yoan.liu
312589ab1c reduce the binary size 2025-05-23 17:23:36 +08:00
Yoan.liu
bff8add010 Merge branch 'main' of github.com:usual2970/certimate 2025-05-23 16:48:54 +08:00
Yoan.liu
6b9f295167 update Dockerfile 2025-05-23 16:48:41 +08:00
RHQYZ
0e8b271e8d Merge pull request #716 from fudiwei/bugfix
bugfix
2025-05-21 20:31:09 +08:00
Fu Diwei
87aae4087c fix: #706 2025-05-21 20:28:21 +08:00
RHQYZ
c34346cb31 bump version to v0.3.13 2025-05-21 00:20:08 +08:00
RHQYZ
7643975ef9 Merge pull request #714 from fudiwei/bugfix
bugfix
2025-05-20 23:14:06 +08:00
Fu Diwei
799ad61dcc fix: #713 2025-05-20 23:10:44 +08:00
Fu Diwei
1c3cb1b21b fix: #710 2025-05-20 23:04:34 +08:00
Fu Diwei
9d9ca88ebe fix: tsc build error 2025-05-20 23:04:28 +08:00
Fu Diwei
d81a33f24a fix: #704 2025-05-20 21:52:33 +08:00
Fu Diwei
398337826e fix: #706 2025-05-20 21:51:38 +08:00
RHQYZ
7469310fdb Merge pull request #699 from Lensual/feat-ssh-jumpserver
feat: ssh jump server support (#49) (#95)
2025-05-20 21:42:53 +08:00
RHQYZ
fe993b54f3 Merge branch 'main' into feat-ssh-jumpserver 2025-05-20 21:42:38 +08:00
RHQYZ
1ed1c62f76 Merge pull request #697 from fudiwei/main
new providers
2025-05-20 21:41:07 +08:00
Fu Diwei
524c4fd1e8 feat: new deployment provider: baotawaf console 2025-05-20 21:27:26 +08:00
Fu Diwei
591df58992 feat: new deployment provider: baotawaf site 2025-05-20 21:27:26 +08:00
Fu Diwei
4ad08d983a refactor: clean code 2025-05-20 21:27:26 +08:00
Fu Diwei
7a663d31cb feat: new deployment provider: wangsu cdn 2025-05-20 21:27:26 +08:00
Fu Diwei
e6fc92eccb feat: new deployment provider: wangsu certificate management 2025-05-20 21:27:19 +08:00
Fu Diwei
c9e6bd0c2f feat: new deployment provider: lecdn 2025-05-19 22:51:17 +08:00
神楽坂ニャン
36dd4ef3eb update: update formSchema for jump server & fix username validate message 2025-05-19 14:27:35 +08:00
Fu Diwei
a66e1c04c9 feat(ui): allow clear input when field is optional 2025-05-19 11:39:45 +08:00
Fu Diwei
3098f6a82f feat: rename certificate props 'issuer' to 'issuerOrg' 2025-05-19 10:35:05 +08:00
神楽坂ニャン
4a8eaa9ffa feat: ssh jump server support 2025-05-17 19:47:31 +08:00
Fu Diwei
3462e454d0 feat: new deployment provider: aliyun ga 2025-05-16 23:30:03 +08:00
Fu Diwei
eabd16dd71 feat: new deployment provider: flexcdn 2025-05-16 22:18:03 +08:00
Fu Diwei
122d766cab feat: new ca provider: custom acme ca 2025-05-16 21:40:40 +08:00
Fu Diwei
980d1ee0b9 feat: new deployment providers: ratpanel console & site 2025-05-16 20:15:59 +08:00
RHQYZ
e9f248d8ec Merge branch 'usual2970:main' into main 2025-05-16 19:23:03 +08:00
RHQYZ
2906576de0 Merge pull request #696 from devhaozi/main
feat: backend support for ratpanel
2025-05-16 19:22:15 +08:00
Fu Diwei
fd875feef3 feat: support 1panel v2 2025-05-16 18:57:39 +08:00
耗子
871d3ece90 fix: test case 2025-05-16 18:51:03 +08:00
耗子
8014abc836 feat: add test cases 2025-05-16 18:43:50 +08:00
Fu Diwei
0840454143 update README 2025-05-16 15:10:44 +08:00
RHQYZ
06fd95782a Merge pull request #693 from fudiwei/main
refactor
2025-05-16 15:07:21 +08:00
Fu Diwei
ef0f0f6b43 refactor: remove nikoksr/notify deps 2025-05-16 15:04:39 +08:00
Fu Diwei
b15bf8ef98 refactor: clean code 2025-05-16 13:56:45 +08:00
Fu Diwei
dd2087b101 refactor: clean code 2025-05-16 11:55:07 +08:00
耗子
bd4aa4806f feat: backend support for ratpanel 2025-05-16 02:33:46 +08:00
268 changed files with 10095 additions and 2682 deletions

View File

@@ -23,6 +23,11 @@ jobs:
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Install upx (optional)
run: |
sudo apt-get update
sudo apt-get install -y upx
- name: Build WebUI
run: |

View File

@@ -30,6 +30,9 @@ builds:
- goos: darwin
goarch: arm
upx:
- enabled: true
release:
draft: true

View File

@@ -13,7 +13,8 @@ WORKDIR /app
COPY ../. /app/
RUN rm -rf /app/ui/dist
COPY --from=webui-builder /app/ui/dist /app/ui/dist
RUN go build -o certimate
ENV CGO_ENABLED=0
RUN go build -ldflags="-s -w" -o certimate

View File

@@ -21,7 +21,8 @@ $(OS_ARCH):
@mkdir -p $(BUILD_DIR)
GOOS=$(word 1,$(subst /, ,$@)) \
GOARCH=$(word 2,$(subst /, ,$@)) \
go build -o $(BUILD_DIR)/$(BINARY_NAME)_$(word 1,$(subst /, ,$@))_$(word 2,$(subst /, ,$@)) -ldflags="-X main.version=$(VERSION)" .
CGO_ENABLED=0 \
go build -o $(BUILD_DIR)/$(BINARY_NAME)_$(word 1,$(subst /, ,$@))_$(word 2,$(subst /, ,$@)) -ldflags="-X main.version=$(VERSION) -s -w" .
# 清理构建文件
clean:

View File

@@ -39,7 +39,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
- 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法;
- 支持 PEM、PFX、JKS 等多种格式输出证书;
- 支持 30+ 域名托管商如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)
- 支持 80+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers)
- 支持 90+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-hosting-providers)
- 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道;
- 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构;
- 更多特性等待探索。

View File

@@ -39,7 +39,7 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate
- Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC.
- Supports various certificate formats such as PEM, PFX, JKS.
- Supports more than 30+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers));
- Supports more than 80+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers));
- Supports more than 90+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-hosting-providers));
- Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more;
- Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust ServicesSSL.com, ZeroSSL, and more;
- More features waiting to be discovered.

57
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.3
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1
github.com/Edgio/edgio-api v0.0.0-workspace
github.com/G-Core/gcorelabscdn-go v1.0.31
@@ -14,58 +14,60 @@ require (
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.3
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.4
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.0
github.com/alibabacloud-go/esa-20240910/v2 v2.32.0
github.com/alibabacloud-go/esa-20240910/v2 v2.33.0
github.com/alibabacloud-go/fc-20230330/v4 v4.3.5
github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12
github.com/alibabacloud-go/ga-20191120/v3 v3.1.8
github.com/alibabacloud-go/live-20161101 v1.1.1
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
github.com/alibabacloud-go/slb-20140515/v4 v4.0.10
github.com/alibabacloud-go/tea v1.3.9
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.2
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.3
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go-v2/service/acm v1.32.0
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1
github.com/baidubce/bce-sdk-go v0.9.226
github.com/baidubce/bce-sdk-go v0.9.228
github.com/blinkbean/dingtalk v1.1.3
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.46
github.com/go-acme/lego/v4 v4.23.1
github.com/go-lark/lark v1.16.0
github.com/go-resty/resty/v2 v2.16.5
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.148
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.150
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
github.com/libdns/dynv6 v1.0.0
github.com/libdns/libdns v0.2.3
github.com/luthermonson/go-proxmox v0.2.2
github.com/nikoksr/notify v1.3.0
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
github.com/pkg/sftp v1.13.9
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.28.0
github.com/pocketbase/pocketbase v0.28.2
github.com/povsister/scp v0.0.0-20250504051308-e467f71ea63c
github.com/qiniu/go-sdk/v7 v7.25.3
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1155
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1161
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1162
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1166
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1173
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1150
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1120
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1124
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1162
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1160
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1162
github.com/ucloud/ucloud-sdk-go v0.22.35
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1172
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1169
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1166
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1164
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1170
github.com/ucloud/ucloud-sdk-go v0.22.41
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.12
github.com/volcengine/volc-sdk-golang v1.0.207
github.com/volcengine/volcengine-go-sdk v1.1.7
github.com/volcengine/volc-sdk-golang v1.0.208
github.com/volcengine/volcengine-go-sdk v1.1.8
gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1
gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
k8s.io/api v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/api v0.33.1
k8s.io/apimachinery v0.33.1
k8s.io/client-go v0.33.1
software.sslmate.com/src/go-pkcs12 v0.5.0
)
@@ -84,13 +86,11 @@ require (
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/diskfs/go-diskfs v1.5.0 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-lark/lark v1.15.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
@@ -99,7 +99,6 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
@@ -126,7 +125,6 @@ require (
github.com/qiniu/x v1.10.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
@@ -195,12 +193,9 @@ require (
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
golang.org/x/image v0.27.0 // indirect
@@ -216,10 +211,10 @@ require (
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.62.1 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect
modernc.org/sqlite v1.37.0 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.1 // indirect
)
replace github.com/Edgio/edgio-api v0.0.0-workspace => ./internal/pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace

132
go.sum
View File

@@ -36,8 +36,8 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 h1:j8BorDEigD8UFOSZQiSqAMOOleyQOOQPnUAwV+Ls1gA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
@@ -101,8 +101,8 @@ github.com/alibabacloud-go/cas-20200407/v3 v3.0.4 h1:ngRlctbt135zoujwX0lXSv9m4h1
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4/go.mod h1:6n9MZ9SH3HlSzfe2oKwjOqhJx3dxvW2gMDO+lq8t9U4=
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2 h1:+KJOPukTM+xMyiLOW5qBwYKG2df3Ar7coRsqc1juKO8=
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2/go.mod h1:GnPiPL3HlzCi8SGiLiVgKrAFkP1vTtcF4yGtjsl4wfo=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.3 h1:OTLn0ShbE0jJj+5Z+P76zeHsZYxZjO7YVThQoeaBM9M=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.3/go.mod h1:eUmD1G4BjEBOAHIeJrHJL7pyLGgXSRTPLjBmYY7uPEg=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.4 h1:SsyAoXM1R4J3I4xQdPW/rRW8cTo2KN440/4h/pGiwRQ=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.4/go.mod h1:eUmD1G4BjEBOAHIeJrHJL7pyLGgXSRTPLjBmYY7uPEg=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
@@ -112,6 +112,7 @@ github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+M
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 h1:ASXSBga98QrGMxbIThCD6jAti09gedLfvry6yJtsoBE=
@@ -131,12 +132,14 @@ github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/ql
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8=
github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/esa-20240910/v2 v2.32.0 h1:eudSgNIkCg6huIu3HuF16BJG6+CA6bIuIddxpuPydpg=
github.com/alibabacloud-go/esa-20240910/v2 v2.32.0/go.mod h1:HZS5PmYJvcmH4vrJYuCvK3AnYzD9hLlO8CT0hgRyDXo=
github.com/alibabacloud-go/esa-20240910/v2 v2.33.0 h1:10IWxrMcF1W6/7BUJIJifrofduSG0wRFqDbRfIsR3Zw=
github.com/alibabacloud-go/esa-20240910/v2 v2.33.0/go.mod h1:HZS5PmYJvcmH4vrJYuCvK3AnYzD9hLlO8CT0hgRyDXo=
github.com/alibabacloud-go/fc-20230330/v4 v4.3.5 h1:nDNjVzGwkQPbQnAuxAmxvS9x8QGLph8j0ptEdZDPGBA=
github.com/alibabacloud-go/fc-20230330/v4 v4.3.5/go.mod h1:vEJimQ6E/e+m2z0/oXdeQWlFw/Pi/Ar6NKcMrSvcILE=
github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 h1:A3D8Mp6qf8DfR6Dt5MpS8aDVaWfS4N85T5CvGUvgrjM=
github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12/go.mod h1:F5c0E5UB3k8v6neTtw3FBcJ1YCNFzVoL1JPRHTe33u4=
github.com/alibabacloud-go/ga-20191120/v3 v3.1.8 h1:5GF0PXijDhxRQ3gTg9Ee/CVPtglkxuVdz4yIQgYLPgw=
github.com/alibabacloud-go/ga-20191120/v3 v3.1.8/go.mod h1:RVpR9VL4YECKoZCQijTYfPk8k52O61v6hSRekjxF0kw=
github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8=
github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4=
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko=
@@ -187,8 +190,8 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4 h1:MYP2xfrcud8vlWljQ4lhemNgAgi9/AUAa450n8TUXZo=
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4/go.mod h1:5ocQ6hIc9tpGixD2iy099aOGwIgpzjT2le4Krd4aLn8=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.2 h1:CmhJzCZ5RiSiWU6BV2XJUtIMD2LDo9FFfqlYGtx1aAw=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.2/go.mod h1:9itYSTzipL3NlvhvNYfTjFaapoZzG68nlu/KUdh9SpA=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.3 h1:25tmcJxIitrk55crBGssPlqRzmFcpGVW5TEFxdUvfg0=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.1.3/go.mod h1:9itYSTzipL3NlvhvNYfTjFaapoZzG68nlu/KUdh9SpA=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 h1:yUkCbrSM1cWtgBfRVKMQtdt22KhDvKY7g4V+92eG9wA=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.100/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
@@ -247,8 +250,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjK
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/baidubce/bce-sdk-go v0.9.226 h1:VKEKcJC9P33yIfYJZr12Q/4Bvj18RFbgO8w8XOfU8AI=
github.com/baidubce/bce-sdk-go v0.9.226/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/baidubce/bce-sdk-go v0.9.228 h1:XEY3/oAxXcsi7+3atib9fMI6YNE9sL5qo+WMZ+iqmNE=
github.com/baidubce/bce-sdk-go v0.9.228/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -360,8 +363,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-lark/lark v1.15.1 h1:fo6PQKBJht/71N9Zn3/xjknOYx0TmdVuP+VP8NrUCsI=
github.com/go-lark/lark v1.15.1/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
github.com/go-lark/lark v1.16.0 h1:U6BwkLM9wrZedSM7cIiMofganr8PCvJN+M75w2lf2Gg=
github.com/go-lark/lark v1.16.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@@ -400,8 +403,6 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
@@ -544,8 +545,8 @@ github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.148 h1:PdWSbniKnPhKe1B19KUHW/9ahYbFH2EY6Iq6sxOnomo=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.148/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.150 h1:Ih+z79Ko1ClH4dlv7O1lyHRiVCjkb2NZYYk+1cSZbU8=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.150/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -569,8 +570,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -679,8 +678,6 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM=
@@ -740,8 +737,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.28.0 h1:dnMHSO0wuYpKs6oP3X5buw1lY9ptd8zy1fTjN+Ae+mA=
github.com/pocketbase/pocketbase v0.28.0/go.mod h1:WE6xMM4+pxKIVNl4B1mcOEZXlDvPGl7cZ64TW2iXHdI=
github.com/pocketbase/pocketbase v0.28.2 h1:b6cfUfr5d4whvUFGFhI8oHRzx/eB76GCUQGftqgv9lM=
github.com/pocketbase/pocketbase v0.28.2/go.mod h1:ElwIYS1b5xS9w0U7AK7tsm6FuC0lzw57H8p/118Cu7g=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/povsister/scp v0.0.0-20250504051308-e467f71ea63c h1:1+j5JHz9mUzYSp0scuF6hzvJP28EDBFe5eBJb0xnGk4=
@@ -774,8 +771,8 @@ github.com/qiniu/x v1.10.5 h1:7V/CYWEmo9axJULvrJN6sMYh2FdY+esN5h8jwDkA4b0=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -830,35 +827,34 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1155 h1:ildxJtjnqiKZxWDVKHT/ncIknGDijtg60MuFELON8bY=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1155/go.mod h1:iLASpooTdyXtx642E5Ws7cfWENsp4/uZ/78TFoln7OI=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1161 h1:yGFg9/6j3NP10r9PfSWHfekuq4SwPyqblWnfISfKANo=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1161/go.mod h1:9MzQSEULYm5wHAKz8R3oQ8ovg4vWeLFzn0DmRWTc6zg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1120/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1124/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1166 h1:cEoDsBt7vGh7YtfVHVmgXKQURZANBE8UKK/So84QUdU=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1166/go.mod h1:0o5Cfgdh+bAx7kpQ5a5wce/ZUiDvy4Md8NcbrLtk6i8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1150/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1155/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1160/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1161/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1162 h1:bscCBygP9JRl6iNabF+vmBOhY+xayFFGYV5Wa0NzH0A=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1162/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1164/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1166/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1169/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1170/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1172/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1173 h1:W5bzEWiJwiwRZR0/P1l78OYWUXYsXLjhBaQ64c+9+fk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1173/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1150 h1:RQQYfZOFYlkxKR2+xp8el3+8xs9DhxBy+ajlHtapqtQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1150/go.mod h1:zpfr6EBWy7ClASTGUgIy01Gn4R79UXf+2QGQeyR124A=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1120 h1:z0t0lb5h1mZirXftO8MRg25COYZHx0ubQjSPhZT/LY0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1120/go.mod h1:IFZL44Keyl+MHrhpFwUaQmJvMDwGr+t+cUfFAC+74lU=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1124 h1:LQKAlxFb0sYiE8ojK5h9+seuFzogoJtYnXmiRF+4F4Q=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1124/go.mod h1:tYbK0FbHVG+78od7eZpzczE8qk0JWKO/osTQWuiJ3Fo=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1162 h1:z/JF+JGi6bGf8vnK9ZeVXz+1Q3ih8nF6KjThxhtIrNc=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1162/go.mod h1:rsO8JCP+WQeLQ32wAB4oRRjsEz0O+kvCGDqc7Ze1jc4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1160 h1:aNVEDS1yQ7sLfXOOQ/bF3eljFjyvHoJ/J8qSC9mC9gw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1160/go.mod h1:kf6NQmKK6sh1ACwh8iliBy7I/burd+AWusNz6zbDvLM=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1162 h1:gnmuUaoFAShc9FKj3Omswu3n08bHM/sGsl8xjFAkFNs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1162/go.mod h1:bu3KAFeoJ1xDGQp72h9Le3FqbOcCcdomOUig3OqgcE4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1172 h1:6SUO0hTie3zxnUEMxmhnS1iRIXpAukSZV27Nrx4NwIk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1172/go.mod h1:tmN4zfu70SD0iee3qfpc09NRLel30zGoAuzIs4X0Kfs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1169 h1:oBymtJEmKDnS2NIR0PDLd+xCGQ+7uMoEt7zEB5Q3x8U=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1169/go.mod h1:K27PNEgRJ602ESXUNnlRnCkf1+XYHI6RVP/ylIe/Aro=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1166 h1:+kIsoG2If/0y15PpZsXfT0QqTuwec9nMgo1JP8KQMkw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1166/go.mod h1:lGmBMXqe3vBg6Bi9h4mVpWLKd7jQIMRbndr8KNHBqSw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1164 h1:caaIGWOs/JtWOK5ptVMEiiGgzU7Jf6UpMv+9IbIa4vs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1164/go.mod h1:SJI2mc77gDC7Tw1QoF/4d5SwLvz8HQFUecWtIXb+r/Q=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1170 h1:kcQCWuI9zOkZgL5CK66HNAJmSWCSJxRrDxXT+j02CeE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1170/go.mod h1:vTukVfThbBIc4lOf4eq/q51eEk78oZUJd2lAoJBOJwI=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
@@ -867,18 +863,18 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ucloud/ucloud-sdk-go v0.22.35 h1:Q4CY3Ae5813jmNUrGdCjc8tlyleL5Lyl0APnpK5L4sk=
github.com/ucloud/ucloud-sdk-go v0.22.35/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ucloud/ucloud-sdk-go v0.22.41 h1:JndTJhCx7A1wggZfVb4KMm7D0Wfvd/HkmQVfSNjClQA=
github.com/ucloud/ucloud-sdk-go v0.22.41/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.12 h1:u9+32DXQIOFPG8oQ3xrjSAUSyAcaq5bqO4cEBom/6lA=
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.12/go.mod h1:IrjK84IJJTuOZOTMv/P18Ydjy/x+ow7fF7q11jAxXLM=
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
github.com/volcengine/volc-sdk-golang v1.0.207 h1:1OJ/nC92dF1URRoyO1AHSghCob12NT1PAA/GoK8uU18=
github.com/volcengine/volc-sdk-golang v1.0.207/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volcengine-go-sdk v1.1.7 h1:5ElF1inqX1QUKX8/XGk+HGpG+F01W+m73cLQH+0x50s=
github.com/volcengine/volcengine-go-sdk v1.1.7/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
github.com/volcengine/volc-sdk-golang v1.0.208 h1:DyGUPjEKhWS08BkptfqenXTuUq+LKb7+gX/RBAtNl8w=
github.com/volcengine/volc-sdk-golang v1.0.208/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volcengine-go-sdk v1.1.8 h1:/T2p7qeeLWWhGrhtB00b8VNlE32S266LcO+jqFUYwzY=
github.com/volcengine/volcengine-go-sdk v1.1.8/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -1399,39 +1395,39 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -3,25 +3,26 @@ package applicant
import "github.com/usual2970/certimate/internal/domain"
const (
sslProviderLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt)
sslProviderLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging)
sslProviderBuypass = string(domain.CAProviderTypeBuypass)
sslProviderGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices)
sslProviderSSLCom = string(domain.CAProviderTypeSSLCom)
sslProviderZeroSSL = string(domain.CAProviderTypeZeroSSL)
caLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt)
caLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging)
caBuypass = string(domain.CAProviderTypeBuypass)
caGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices)
caSSLCom = string(domain.CAProviderTypeSSLCom)
caZeroSSL = string(domain.CAProviderTypeZeroSSL)
caCustom = string(domain.CAProviderTypeACMECA)
sslProviderDefault = sslProviderLetsEncrypt
caDefault = caLetsEncrypt
)
var sslProviderUrls = map[string]string{
sslProviderLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory",
sslProviderLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory",
sslProviderBuypass: "https://api.buypass.com/acme/directory",
sslProviderGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory",
sslProviderSSLCom: "https://acme.ssl.com/sslcom-dv-rsa",
sslProviderSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa",
sslProviderSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc",
sslProviderZeroSSL: "https://acme.zerossl.com/v2/DV90",
var caDirUrls = map[string]string{
caLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory",
caLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory",
caBuypass: "https://api.buypass.com/acme/directory",
caGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory",
caSSLCom: "https://acme.ssl.com/sslcom-dv-rsa",
caSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa",
caSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc",
caZeroSSL: "https://acme.zerossl.com/v2/DV90",
}
type acmeSSLProviderConfig struct {

View File

@@ -7,6 +7,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"fmt"
"strings"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
@@ -19,22 +20,31 @@ import (
)
type acmeUser struct {
CA string
Email string
// 证书颁发机构标识。
// 通常等同于 [CAProviderType] 的值。
// 对于自定义 ACME CA值为 "custom#{access_id}"。
CA string
// 邮箱。
Email string
// 注册信息。
Registration *registration.Resource
// CSR 私钥。
privkey string
}
func newAcmeUser(ca, email string) (*acmeUser, error) {
func newAcmeUser(ca, caAccessId, email string) (*acmeUser, error) {
repo := repository.NewAcmeAccountRepository()
applyUser := &acmeUser{
CA: ca,
Email: email,
}
if ca == caCustom {
applyUser.CA = fmt.Sprintf("%s#%s", ca, caAccessId)
}
acmeAccount, err := repo.GetByCAAndEmail(ca, email)
acmeAccount, err := repo.GetByCAAndEmail(applyUser.CA, applyUser.Email)
if err != nil {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
@@ -73,6 +83,10 @@ func (u *acmeUser) hasRegistration() bool {
return u.Registration != nil
}
func (u *acmeUser) getCAProvider() string {
return strings.Split(u.CA, "#")[0]
}
func (u *acmeUser) getPrivateKeyPEM() string {
return u.privkey
}
@@ -94,16 +108,16 @@ func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userR
func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) {
var reg *registration.Resource
var err error
switch user.CA {
case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging:
switch user.getCAProvider() {
case caLetsEncrypt, caLetsEncryptStaging:
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
case sslProviderBuypass:
case caBuypass:
{
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
}
case sslProviderGoogleTrustServices:
case caGoogleTrustServices:
{
access := domain.AccessConfigForGoogleTrustServices{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
@@ -117,7 +131,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case sslProviderSSLCom:
case caSSLCom:
{
access := domain.AccessConfigForSSLCom{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
@@ -131,7 +145,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case sslProviderZeroSSL:
case caZeroSSL:
{
access := domain.AccessConfigForZeroSSL{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
@@ -145,6 +159,26 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case caCustom:
{
access := domain.AccessConfigForACMECA{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
if access.EabKid == "" && access.EabHmacKey == "" {
reg, err = client.Registration.Register(registration.RegisterOptions{
TermsOfServiceAgreed: true,
})
} else {
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: access.EabKid,
HmacEncoded: access.EabHmacKey,
})
}
}
default:
err = fmt.Errorf("unsupported ca provider '%s'", user.CA)
}

View File

@@ -20,18 +20,20 @@ import (
"golang.org/x/time/rate"
"github.com/usual2970/certimate/internal/domain"
maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice"
"github.com/usual2970/certimate/internal/repository"
)
type ApplyResult struct {
CertificateFullChain string
CSR string
FullChainCertificate string
IssuerCertificate string
PrivateKey string
ACMEAccountUrl string
ACMECertUrl string
ACMECertStableUrl string
CSR string
ARIReplaced bool
}
type Applicant interface {
@@ -53,20 +55,20 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
nodeConfig := config.Node.GetConfigForApply()
options := &applicantProviderOptions{
Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeConfig.ContactEmail,
Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderExtendedConfig: nodeConfig.ProviderConfig,
CAProvider: domain.CAProviderType(nodeConfig.CAProvider),
CAProviderAccessConfig: make(map[string]any),
CAProviderExtendedConfig: nodeConfig.CAProviderConfig,
KeyAlgorithm: nodeConfig.KeyAlgorithm,
Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
DnsPropagationWait: nodeConfig.DnsPropagationWait,
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
DnsTTL: nodeConfig.DnsTTL,
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeConfig.ContactEmail,
Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeConfig.ProviderConfig,
CAProvider: domain.CAProviderType(nodeConfig.CAProvider),
CAProviderAccessConfig: make(map[string]any),
CAProviderServiceConfig: nodeConfig.CAProviderConfig,
KeyAlgorithm: nodeConfig.KeyAlgorithm,
Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
DnsPropagationWait: nodeConfig.DnsPropagationWait,
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
DnsTTL: nodeConfig.DnsTTL,
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
}
accessRepo := repository.NewAccessRepository()
@@ -81,6 +83,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
if access, err := accessRepo.GetById(context.Background(), nodeConfig.CAProviderAccessId); err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.CAProviderAccessId, err)
} else {
options.CAProviderAccessId = access.Id
options.CAProviderAccessConfig = access.Config
}
}
@@ -91,13 +94,13 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
sslProviderConfig := &acmeSSLProviderConfig{
Config: make(map[domain.CAProviderType]map[string]any),
Provider: sslProviderDefault,
Provider: caDefault,
}
if settings != nil {
if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil {
return nil, err
} else if sslProviderConfig.Provider == "" {
sslProviderConfig.Provider = sslProviderDefault
sslProviderConfig.Provider = caDefault
}
}
@@ -107,7 +110,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
certRepo := repository.NewCertificateRepository()
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id)
if lastCertificate != nil {
if lastCertificate != nil && !lastCertificate.ACMERenewed {
newCertSan := slices.Clone(options.Domains)
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
slices.Sort(newCertSan)
@@ -117,8 +120,8 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
if lastCertX509 != nil {
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
options.ReplacedARIAcct = lastCertificate.ACMEAccountUrl
options.ReplacedARICert = replacedARICertId
options.ARIReplaceAcct = lastCertificate.ACMEAccountUrl
options.ARIReplaceCert = replacedARICertId
}
}
}
@@ -163,7 +166,7 @@ func getLimiter(key string) *rate.Limiter {
}
func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) {
user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail)
user, err := newAcmeUser(string(options.CAProvider), options.CAProviderAccessId, options.ContactEmail)
if err != nil {
return nil, err
}
@@ -175,13 +178,26 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt
// Create an ACME client config
config := lego.NewConfig(user)
config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
config.CADirURL = sslProviderUrls[user.CA]
if user.CA == sslProviderSSLCom {
switch user.getCAProvider() {
case caSSLCom:
if strings.HasPrefix(options.KeyAlgorithm, "RSA") {
config.CADirURL = sslProviderUrls[sslProviderSSLCom+"RSA"]
config.CADirURL = caDirUrls[caSSLCom+"RSA"]
} else if strings.HasPrefix(options.KeyAlgorithm, "EC") {
config.CADirURL = sslProviderUrls[sslProviderSSLCom+"ECC"]
config.CADirURL = caDirUrls[caSSLCom+"ECC"]
} else {
config.CADirURL = caDirUrls[caSSLCom]
}
case caCustom:
caDirURL := maputil.GetString(options.CAProviderAccessConfig, "endpoint")
if caDirURL != "" {
config.CADirURL = caDirURL
} else {
return nil, fmt.Errorf("invalid ca provider endpoint")
}
default:
config.CADirURL = caDirUrls[user.CA]
}
// Create an ACME client
@@ -220,22 +236,24 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt
Domains: options.Domains,
Bundle: true,
}
if options.ReplacedARIAcct == user.Registration.URI {
certRequest.ReplacesCertID = options.ReplacedARICert
if options.ARIReplaceAcct == user.Registration.URI {
certRequest.ReplacesCertID = options.ARIReplaceCert
}
certResource, err := client.Certificate.Obtain(certRequest)
if err != nil {
return nil, err
}
return &ApplyResult{
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
CSR: strings.TrimSpace(string(certResource.CSR)),
FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)),
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
ACMEAccountUrl: user.Registration.URI,
ACMECertUrl: certResource.CertURL,
ACMECertStableUrl: certResource.CertStableURL,
CSR: strings.TrimSpace(string(certResource.CSR)),
ARIReplaced: certRequest.ReplacesCertID != "",
}, nil
}

View File

@@ -17,11 +17,14 @@ import (
pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud"
pDeSEC "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/desec"
pDigitalOcean "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/digitalocean"
pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla"
pDuckDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/duckdns"
pDynv6 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6"
pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore"
pGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname"
pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
pHetzner "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/hetzner"
pHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
pJDCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud"
pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
@@ -42,22 +45,23 @@ import (
)
type applicantProviderOptions struct {
Domains []string
ContactEmail string
Provider domain.ACMEDns01ProviderType
ProviderAccessConfig map[string]any
ProviderExtendedConfig map[string]any
CAProvider domain.CAProviderType
CAProviderAccessConfig map[string]any
CAProviderExtendedConfig map[string]any
KeyAlgorithm string
Nameservers []string
DnsPropagationWait int32
DnsPropagationTimeout int32
DnsTTL int32
DisableFollowCNAME bool
ReplacedARIAcct string
ReplacedARICert string
Domains []string
ContactEmail string
Provider domain.ACMEDns01ProviderType
ProviderAccessConfig map[string]any
ProviderServiceConfig map[string]any
CAProvider domain.CAProviderType
CAProviderAccessId string
CAProviderAccessConfig map[string]any
CAProviderServiceConfig map[string]any
KeyAlgorithm string
Nameservers []string
DnsPropagationWait int32
DnsPropagationTimeout int32
DnsTTL int32
DisableFollowCNAME bool
ARIReplaceAcct string
ARIReplaceCert string
}
func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) {
@@ -104,7 +108,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
applicant, err := pAliyunESA.NewChallengeProvider(&pAliyunESA.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
Region: maputil.GetString(options.ProviderServiceConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
@@ -125,8 +129,8 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
applicant, err := pAWSRoute53.NewChallengeProvider(&pAWSRoute53.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
HostedZoneId: maputil.GetString(options.ProviderExtendedConfig, "hostedZoneId"),
Region: maputil.GetString(options.ProviderServiceConfig, "region"),
HostedZoneId: maputil.GetString(options.ProviderServiceConfig, "hostedZoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
@@ -245,6 +249,21 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
case domain.ACMEDns01ProviderTypeDigitalOcean:
{
access := domain.AccessConfigForDigitalOcean{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pDigitalOcean.NewChallengeProvider(&pDigitalOcean.ChallengeProviderConfig{
AccessToken: access.AccessToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeDNSLA:
{
access := domain.AccessConfigForDNSLA{}
@@ -261,6 +280,20 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
case domain.ACMEDns01ProviderTypeDuckDNS:
{
access := domain.AccessConfigForDuckDNS{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pDuckDNS.NewChallengeProvider(&pDuckDNS.ChallengeProviderConfig{
Token: access.Token,
DnsPropagationTimeout: options.DnsPropagationTimeout,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeDynv6:
{
access := domain.AccessConfigForDynv6{}
@@ -323,6 +356,21 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
case domain.ACMEDns01ProviderTypeHetzner:
{
access := domain.AccessConfigForHetzner{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pHetzner.NewChallengeProvider(&pHetzner.ChallengeProviderConfig{
ApiToken: access.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeHuaweiCloud, domain.ACMEDns01ProviderTypeHuaweiCloudDNS:
{
access := domain.AccessConfigForHuaweiCloud{}
@@ -333,7 +381,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
Region: maputil.GetString(options.ProviderServiceConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
@@ -350,7 +398,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"),
RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
@@ -475,7 +523,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
}
applicant, err := pPowerDNS.NewChallengeProvider(&pPowerDNS.ChallengeProviderConfig{
ApiUrl: access.ApiUrl,
ServerUrl: access.ServerUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
DnsPropagationTimeout: options.DnsPropagationTimeout,
@@ -520,7 +568,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"),
ZoneId: maputil.GetString(options.ProviderServiceConfig, "zoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})

View File

@@ -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),
ProviderExtendedConfig: nodeConfig.ProviderConfig,
Provider: domain.DeploymentProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeConfig.ProviderConfig,
}
accessRepo := repository.NewAccessRepository()

View File

File diff suppressed because it is too large Load Diff

View File

@@ -16,11 +16,18 @@ type Access struct {
}
type AccessConfigFor1Panel struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiVersion string `json:"apiVersion"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForACMECA struct {
Endpoint string `json:"endpoint"`
EabKid string `json:"eabKid,omitempty"`
EabHmacKey string `json:"eabHmacKey,omitempty"`
}
type AccessConfigForACMEHttpReq struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode,omitempty"`
@@ -55,7 +62,13 @@ type AccessConfigForBaishan struct {
}
type AccessConfigForBaotaPanel struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForBaotaWAF struct {
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
@@ -74,7 +87,7 @@ type AccessConfigForCacheFly struct {
}
type AccessConfigForCdnfly struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
@@ -99,11 +112,20 @@ type AccessConfigForDeSEC struct {
Token string `json:"token"`
}
type AccessConfigForDigitalOcean struct {
AccessToken string `json:"accessToken"`
}
type AccessConfigForDingTalkBot struct {
WebhookUrl string `json:"webhookUrl"`
Secret string `json:"secret"`
}
type AccessConfigForDiscordBot struct {
BotToken string `json:"botToken"`
DefaultChannelId string `json:"defaultChannelId,omitempty"`
}
type AccessConfigForDNSLA struct {
ApiId string `json:"apiId"`
ApiSecret string `json:"apiSecret"`
@@ -114,6 +136,10 @@ type AccessConfigForDogeCloud struct {
SecretKey string `json:"secretKey"`
}
type AccessConfigForDuckDNS struct {
Token string `json:"token"`
}
type AccessConfigForDynv6 struct {
HttpToken string `json:"httpToken"`
}
@@ -133,6 +159,14 @@ type AccessConfigForEmail struct {
DefaultReceiverAddress string `json:"defaultReceiverAddress,omitempty"`
}
type AccessConfigForFlexCDN struct {
ServerUrl string `json:"serverUrl"`
ApiRole string `json:"apiRole"`
AccessKeyId string `json:"accessKeyId"`
AccessKey string `json:"accessKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForGcore struct {
ApiToken string `json:"apiToken"`
}
@@ -148,7 +182,7 @@ type AccessConfigForGoDaddy struct {
}
type AccessConfigForGoEdge struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiRole string `json:"apiRole"`
AccessKeyId string `json:"accessKeyId"`
AccessKey string `json:"accessKey"`
@@ -160,6 +194,10 @@ type AccessConfigForGoogleTrustServices struct {
EabHmacKey string `json:"eabHmacKey"`
}
type AccessConfigForHetzner struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForHuaweiCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
@@ -178,6 +216,15 @@ type AccessConfigForLarkBot struct {
WebhookUrl string `json:"webhookUrl"`
}
type AccessConfigForLeCDN struct {
ServerUrl string `json:"serverUrl"`
ApiVersion string `json:"apiVersion"`
ApiRole string `json:"apiRole"`
Username string `json:"username"`
Password string `json:"password"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForMattermost struct {
ServerUrl string `json:"serverUrl"`
Username string `json:"username"`
@@ -219,13 +266,13 @@ type AccessConfigForPorkbun struct {
}
type AccessConfigForPowerDNS struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForProxmoxVE struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
@@ -240,12 +287,24 @@ type AccessConfigForRainYun struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForRatPanel struct {
ServerUrl string `json:"serverUrl"`
AccessTokenId int32 `json:"accessTokenId"`
AccessToken string `json:"accessToken"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSafeLine struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSlackBot struct {
BotToken string `json:"botToken"`
DefaultChannelId string `json:"defaultChannelId,omitempty"`
}
type AccessConfigForSSH struct {
Host string `json:"host"`
Port int32 `json:"port"`
@@ -253,6 +312,14 @@ type AccessConfigForSSH struct {
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
JumpServers []struct {
Host string `json:"host"`
Port int32 `json:"port"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
} `json:"jumpServers,omitempty"`
}
type AccessConfigForSSLCom struct {
@@ -260,7 +327,7 @@ type AccessConfigForSSLCom struct {
EabHmacKey string `json:"eabHmacKey"`
}
type AccessConfigForTelegram struct {
type AccessConfigForTelegramBot struct {
BotToken string `json:"botToken"`
DefaultChatId int64 `json:"defaultChatId,omitempty"`
}
@@ -276,6 +343,11 @@ type AccessConfigForUCloud struct {
ProjectId string `json:"projectId,omitempty"`
}
type AccessConfigForUniCloud struct {
Username string `json:"username"`
Password string `json:"password"`
}
type AccessConfigForUpyun struct {
Username string `json:"username"`
Password string `json:"password"`

View File

@@ -20,7 +20,7 @@ type Certificate struct {
SerialNumber string `json:"serialNumber" db:"serialNumber"`
Certificate string `json:"certificate" db:"certificate"`
PrivateKey string `json:"privateKey" db:"privateKey"`
Issuer string `json:"issuer" db:"issuer"`
IssuerOrg string `json:"issuerOrg" db:"issuerOrg"`
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"`
EffectAt time.Time `json:"effectAt" db:"effectAt"`
@@ -28,6 +28,7 @@ type Certificate struct {
ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"`
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
ACMERenewed bool `json:"acmeRenewed" db:"acmeRenewed"`
WorkflowId string `json:"workflowId" db:"workflowId"`
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"`
@@ -38,7 +39,7 @@ type Certificate struct {
func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate {
c.SubjectAltNames = strings.Join(certX509.DNSNames, ";")
c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16))
c.Issuer = strings.Join(certX509.Issuer.Organization, ";")
c.IssuerOrg = strings.Join(certX509.Issuer.Organization, ";")
c.EffectAt = certX509.NotBefore
c.ExpireAt = certX509.NotAfter

View File

@@ -10,7 +10,7 @@ type AccessProviderType string
*/
const (
AccessProviderType1Panel = AccessProviderType("1panel")
AccessProviderTypeACMECA = AccessProviderType("acmeca") // ACME CA预留
AccessProviderTypeACMECA = AccessProviderType("acmeca")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai预留
AccessProviderTypeAliyun = AccessProviderType("aliyun")
@@ -19,6 +19,7 @@ const (
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBaishan = AccessProviderType("baishan")
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeBunny = AccessProviderType("bunny")
AccessProviderTypeBuypass = AccessProviderType("buypass")
@@ -30,26 +31,30 @@ const (
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 天翼云(预留)
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 联通云(预留)
AccessProviderTypeDeSEC = AccessProviderType("desec")
AccessProviderTypeDigitalOcean = AccessProviderType("digitalocean")
AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot")
AccessProviderTypeDiscordBot = AccessProviderType("discordbot")
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeDuckDNS = AccessProviderType("duckdns")
AccessProviderTypeDynv6 = AccessProviderType("dynv6")
AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeEmail = AccessProviderType("email")
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly预留
AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") // FlexCDN预留
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly预留
AccessProviderTypeFlexCDN = AccessProviderType("flexcdn")
AccessProviderTypeGname = AccessProviderType("gname")
AccessProviderTypeGcore = AccessProviderType("gcore")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeGoEdge = AccessProviderType("goedge")
AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices")
AccessProviderTypeHetzner = AccessProviderType("hetzner")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
AccessProviderTypeLarkBot = AccessProviderType("larkbot")
AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt")
AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging")
AccessProviderTypeLeCDN = AccessProviderType("lecdn") // LeCDN预留
AccessProviderTypeLeCDN = AccessProviderType("lecdn")
AccessProviderTypeLocal = AccessProviderType("local")
AccessProviderTypeMattermost = AccessProviderType("mattermost")
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
@@ -64,12 +69,15 @@ const (
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSlackBot = AccessProviderType("slackbot")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeSSLCOM = AccessProviderType("sslcom")
AccessProviderTypeTelegram = AccessProviderType("telegram")
AccessProviderTypeTelegramBot = AccessProviderType("telegrambot")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
AccessProviderTypeUniCloud = AccessProviderType("unicloud")
AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
@@ -90,6 +98,7 @@ type CAProviderType string
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA)
CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass)
CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices)
CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt)
@@ -123,11 +132,14 @@ const (
ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud)
ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)
ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6)
ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore)
ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname)
ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy)
ACMEDns01ProviderTypeHetzner = ACMEDns01ProviderType(AccessProviderTypeHetzner)
ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
@@ -172,7 +184,7 @@ const (
DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos")
DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa")
DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc")
DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") // 阿里云全球加速(预留)
DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga")
DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live")
DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb")
DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss")
@@ -188,13 +200,15 @@ const (
DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn")
DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console")
DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site")
DeploymentProviderTypeBaotaWAFConsole = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-console")
DeploymentProviderTypeBaotaWAFSite = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-site")
DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn")
DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn")
DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly)
DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly)
DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn")
DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications")
DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) // FlexCDN预留
DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN)
DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn")
DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge)
DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn")
@@ -206,7 +220,7 @@ const (
DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live")
DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod")
DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret")
DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) // LeCDN预留
DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN)
DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal)
DeploymentProviderTypeNetlifySite = DeploymentProviderType(AccessProviderTypeNetlify + "-site")
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
@@ -214,6 +228,8 @@ const (
DeploymentProviderTypeQiniuKodo = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo")
DeploymentProviderTypeQiniuPili = DeploymentProviderType(AccessProviderTypeQiniu + "-pili")
DeploymentProviderTypeRainYunRCDN = DeploymentProviderType(AccessProviderTypeRainYun + "-rcdn")
DeploymentProviderTypeRatPanelConsole = DeploymentProviderType(AccessProviderTypeRatPanel + "-console")
DeploymentProviderTypeRatPanelSite = DeploymentProviderType(AccessProviderTypeRatPanel + "-site")
DeploymentProviderTypeSafeLine = DeploymentProviderType(AccessProviderTypeSafeLine)
DeploymentProviderTypeSSH = DeploymentProviderType(AccessProviderTypeSSH)
DeploymentProviderTypeTencentCloudCDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn")
@@ -229,6 +245,7 @@ const (
DeploymentProviderTypeTencentCloudWAF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-waf")
DeploymentProviderTypeUCloudUCDN = DeploymentProviderType(AccessProviderTypeUCloud + "-ucdn")
DeploymentProviderTypeUCloudUS3 = DeploymentProviderType(AccessProviderTypeUCloud + "-us3")
DeploymentProviderTypeUniCloudWebHost = DeploymentProviderType(AccessProviderTypeUniCloud + "-webhost")
DeploymentProviderTypeUpyunCDN = DeploymentProviderType(AccessProviderTypeUpyun + "-cdn")
DeploymentProviderTypeUpyunFile = DeploymentProviderType(AccessProviderTypeUpyun + "-file")
DeploymentProviderTypeVolcEngineALB = DeploymentProviderType(AccessProviderTypeVolcEngine + "-alb")
@@ -239,9 +256,9 @@ const (
DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex")
DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live")
DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos")
DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") // 网宿 CDN预留
DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn")
DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro")
DeploymentProviderTypeWangsuCert = DeploymentProviderType(AccessProviderTypeWangsu + "-cert") // 网宿证书管理(预留)
DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate")
DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook)
)
@@ -256,10 +273,12 @@ type NotificationProviderType string
*/
const (
NotificationProviderTypeDingTalkBot = NotificationProviderType(AccessProviderTypeDingTalkBot)
NotificationProviderTypeDiscordBot = NotificationProviderType(AccessProviderTypeDiscordBot)
NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail)
NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot)
NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost)
NotificationProviderTypeTelegram = NotificationProviderType(AccessProviderTypeTelegram)
NotificationProviderTypeSlackBot = NotificationProviderType(AccessProviderTypeSlackBot)
NotificationProviderTypeTelegramBot = NotificationProviderType(AccessProviderTypeTelegramBot)
NotificationProviderTypeWebhook = NotificationProviderType(AccessProviderTypeWebhook)
NotificationProviderTypeWeComBot = NotificationProviderType(AccessProviderTypeWeComBot)
)

View File

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

View File

@@ -31,9 +31,9 @@ func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error
nodeConfig := config.Node.GetConfigForNotify()
options := &notifierProviderOptions{
Provider: domain.NotificationProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderExtendedConfig: nodeConfig.ProviderConfig,
Provider: domain.NotificationProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeConfig.ProviderConfig,
}
accessRepo := repository.NewAccessRepository()

View File

@@ -6,21 +6,23 @@ import (
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
pDingTalkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot"
pDiscordBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/discordbot"
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pLarkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot"
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
pSlackBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/slackbot"
pTelegramBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom"
pWeComBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot"
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
ProviderExtendedConfig map[string]any
Provider domain.NotificationProviderType
ProviderAccessConfig map[string]any
ProviderServiceConfig map[string]any
}
func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) {
@@ -36,12 +38,25 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{
return pDingTalkBot.NewNotifier(&pDingTalkBot.NotifierConfig{
WebhookUrl: access.WebhookUrl,
Secret: access.Secret,
})
}
case domain.NotificationProviderTypeDiscordBot:
{
access := domain.AccessConfigForDiscordBot{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pDiscordBot.NewNotifier(&pDiscordBot.NotifierConfig{
BotToken: access.BotToken,
ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotificationProviderTypeEmail:
{
access := domain.AccessConfigForEmail{}
@@ -55,8 +70,8 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
SmtpTls: access.SmtpTls,
Username: access.Username,
Password: access.Password,
SenderAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "senderAddress", access.DefaultSenderAddress),
ReceiverAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", access.DefaultReceiverAddress),
SenderAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "senderAddress", access.DefaultSenderAddress),
ReceiverAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "receiverAddress", access.DefaultReceiverAddress),
})
}
@@ -67,7 +82,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pLark.NewNotifier(&pLark.NotifierConfig{
return pLarkBot.NewNotifier(&pLarkBot.NotifierConfig{
WebhookUrl: access.WebhookUrl,
})
}
@@ -83,20 +98,33 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
ServerUrl: access.ServerUrl,
Username: access.Username,
Password: access.Password,
ChannelId: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "channelId", access.DefaultChannelId),
ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotificationProviderTypeTelegram:
case domain.NotificationProviderTypeSlackBot:
{
access := domain.AccessConfigForTelegram{}
access := domain.AccessConfigForSlackBot{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pTelegram.NewNotifier(&pTelegram.NotifierConfig{
return pSlackBot.NewNotifier(&pSlackBot.NotifierConfig{
BotToken: access.BotToken,
ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotificationProviderTypeTelegramBot:
{
access := domain.AccessConfigForTelegramBot{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pTelegramBot.NewNotifier(&pTelegramBot.NotifierConfig{
BotToken: access.BotToken,
ChatId: maputil.GetOrDefaultInt64(options.ProviderExtendedConfig, "chatId", access.DefaultChatId),
ChatId: maputil.GetOrDefaultInt64(options.ProviderServiceConfig, "chatId", access.DefaultChatId),
})
}
@@ -117,7 +145,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
}
}
if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" {
if extendedHeadersString := maputil.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" {
h, err := httputil.ParseHeaders(extendedHeadersString)
if err != nil {
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
@@ -129,7 +157,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
return pWebhook.NewNotifier(&pWebhook.NotifierConfig{
WebhookUrl: access.Url,
WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForNotification),
WebhookData: maputil.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForNotification),
Method: access.Method,
Headers: mergedHeaders,
AllowInsecureConnections: access.AllowInsecureConnections,
@@ -143,7 +171,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pWeCom.NewNotifier(&pWeCom.NotifierConfig{
return pWeComBot.NewNotifier(&pWeComBot.NotifierConfig{
WebhookUrl: access.WebhookUrl,
})
}

View File

@@ -6,17 +6,17 @@ 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"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot"
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"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot"
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"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom"
pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot"
maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
)
@@ -52,9 +52,9 @@ func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, c
case domain.NotifyChannelTypeGotify:
return pGotify.NewNotifier(&pGotify.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
Token: maputil.GetString(channelConfig, "token"),
Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1),
ServerUrl: maputil.GetString(channelConfig, "url"),
Token: maputil.GetString(channelConfig, "token"),
Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1),
})
case domain.NotifyChannelTypeLark:
@@ -83,7 +83,7 @@ func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, c
case domain.NotifyChannelTypeServerChan:
return pServerChan.NewNotifier(&pServerChan.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
ServerUrl: maputil.GetString(channelConfig, "url"),
})
case domain.NotifyChannelTypeTelegram:

View File

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

View File

@@ -89,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return &DNSProvider{
client: client,
config: config,
siteIDs: make(map[string]int64),
}, nil
}
@@ -100,9 +102,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err)
}
siteId, err := d.getSiteId(authZone)
siteName := strings.TrimRight(authZone, ".")
siteId, err := d.getSiteId(siteName)
if err != nil {
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", authZone, err)
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err)
}
if err := d.addOrUpdateDNSRecord(siteId, strings.TrimRight(info.EffectiveFQDN, "."), info.Value); err != nil {
@@ -120,9 +123,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err)
}
siteId, err := d.getSiteId(authZone)
siteName := strings.TrimRight(authZone, ".")
siteId, err := d.getSiteId(siteName)
if err != nil {
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", authZone, err)
return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err)
}
if err := d.removeDNSRecord(siteId, strings.TrimRight(info.EffectiveFQDN, ".")); err != nil {
@@ -148,10 +152,11 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) {
pageSize := 500
for {
request := &aliesa.ListSitesRequest{
SiteName: tea.String(siteName),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageNumber)),
AccessType: tea.String("NS"),
SiteName: tea.String(siteName),
SiteSearchType: tea.String("exact"),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageSize)),
AccessType: tea.String("NS"),
}
response, err := d.client.ListSites(request)
if err != nil {
@@ -178,7 +183,7 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) {
}
}
return 0, errors.New("failed to get site id")
return 0, errors.New("site not found")
}
func (d *DNSProvider) findDNSRecord(siteId int64, effectiveFQDN string) (*aliesa.ListRecordsResponseBodyRecords, error) {
@@ -186,11 +191,12 @@ func (d *DNSProvider) findDNSRecord(siteId int64, effectiveFQDN string) (*aliesa
pageSize := 500
for {
request := &aliesa.ListRecordsRequest{
SiteId: tea.Int64(siteId),
Type: tea.String("TXT"),
RecordName: tea.String(effectiveFQDN),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageNumber)),
SiteId: tea.Int64(siteId),
Type: tea.String("TXT"),
RecordName: tea.String(effectiveFQDN),
RecordMatchType: tea.String("exact"),
PageNumber: tea.Int32(int32(pageNumber)),
PageSize: tea.Int32(int32(pageSize)),
}
response, err := d.client.ListRecords(request)
if err != nil {

View File

@@ -0,0 +1,36 @@
package namedotcom
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/digitalocean"
)
type ChallengeProviderConfig struct {
AccessToken string `json:"accessToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := digitalocean.NewDefaultConfig()
providerConfig.AuthToken = config.AccessToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := digitalocean.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,32 @@
package namedotcom
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/duckdns"
)
type ChallengeProviderConfig struct {
Token string `json:"token"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := duckdns.NewDefaultConfig()
providerConfig.Token = config.Token
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
provider, err := duckdns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,36 @@
package namedotcom
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/hetzner"
)
type ChallengeProviderConfig struct {
ApiToken string `json:"apiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := hetzner.NewDefaultConfig()
providerConfig.APIKey = config.ApiToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := hetzner.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -11,7 +11,7 @@ import (
)
type ChallengeProviderConfig struct {
ApiUrl string `json:"apiUrl"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
@@ -23,9 +23,9 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider,
panic("config is nil")
}
host, _ := url.Parse(config.ApiUrl)
serverUrl, _ := url.Parse(config.ServerUrl)
providerConfig := pdns.NewDefaultConfig()
providerConfig.Host = host
providerConfig.Host = serverUrl
providerConfig.APIKey = config.ApiKey
if config.AllowInsecureConnections {
providerConfig.HTTPClient.Transport = &http.Transport{

View File

@@ -9,12 +9,15 @@ import (
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel"
onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 服务地址。
ServerUrl string `json:"serverUrl"`
// 1Panel 版本。
// 可取值 "v1"、"v2"。
ApiVersion string `json:"apiVersion"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
@@ -26,7 +29,7 @@ type DeployerConfig struct {
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *opsdk.Client
sdkClient *onepanelsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
@@ -36,7 +39,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -59,7 +62,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 设置面板 SSL 证书
updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{
updateSystemSSLReq := &onepanelsdk.UpdateSystemSSLRequest{
Cert: certPEM,
Key: privkeyPEM,
SSL: "enable",
@@ -79,16 +82,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
func createSdkClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid 1panel server url")
}
if apiVersion == "" {
return nil, errors.New("invalid 1panel api version")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
client := onepanelsdk.NewClient(serverUrl, apiVersion, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,7 +14,8 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiVersion string
fApiKey string
)
@@ -23,7 +24,8 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
@@ -33,7 +35,8 @@ Shell command to run this test:
go test -v ./1panel_console_test.go -args \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_SERVERURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIVERSION="v1" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key"
*/
func TestDeploy(t *testing.T) {
@@ -44,12 +47,14 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIVERSION: %v", fApiVersion),
fmt.Sprintf("APIKEY: %v", fApiKey),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiVersion: fApiVersion,
ApiKey: fApiKey,
AllowInsecureConnections: true,
AutoRestart: true,

View File

@@ -12,12 +12,15 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl"
opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel"
onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 服务地址。
ServerUrl string `json:"serverUrl"`
// 1Panel 版本。
// 可取值 "v1"、"v2"。
ApiVersion string `json:"apiVersion"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
@@ -35,7 +38,7 @@ type DeployerConfig struct {
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *opsdk.Client
sdkClient *onepanelsdk.Client
sslUploader uploader.Uploader
}
@@ -46,14 +49,16 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
ApiUrl: config.ApiUrl,
ApiKey: config.ApiKey,
ServerUrl: config.ServerUrl,
ApiVersion: config.ApiVersion,
ApiKey: config.ApiKey,
AllowInsecureConnections: config.AllowInsecureConnections,
})
if err != nil {
return nil, fmt.Errorf("failed to create ssl uploader: %w", err)
@@ -103,7 +108,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string,
}
// 获取网站 HTTPS 配置
getHttpsConfReq := &opsdk.GetHttpsConfRequest{
getHttpsConfReq := &onepanelsdk.GetHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
}
getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq)
@@ -122,7 +127,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string,
// 修改网站 HTTPS 配置
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{
updateHttpsConfReq := &onepanelsdk.UpdateHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
Type: "existed",
WebsiteSSLID: certId,
@@ -147,7 +152,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
}
// 获取证书详情
getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{
getWebsiteSSLReq := &onepanelsdk.GetWebsiteSSLRequest{
SSLID: d.config.CertificateId,
}
getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq)
@@ -157,7 +162,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
}
// 更新证书
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{
Type: "paste",
SSLID: d.config.CertificateId,
Description: getWebsiteSSLResp.Data.Description,
@@ -173,16 +178,20 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
return nil
}
func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
func createSdkClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid 1panel server url")
}
if apiVersion == "" {
return nil, errors.New("invalid 1panel api version")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
client := onepanelsdk.NewClient(serverUrl, apiVersion, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,7 +14,8 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiVersion string
fApiKey string
fWebsiteId int64
)
@@ -24,7 +25,8 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.Int64Var(&fWebsiteId, argsPrefix+"WEBSITEID", 0, "")
}
@@ -35,7 +37,8 @@ Shell command to run this test:
go test -v ./1panel_site_test.go -args \
--CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELSITE_SERVERURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELSITE_APIVERSION="v1" \
--CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id"
*/
@@ -47,13 +50,15 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIVERSION: %v", fApiVersion),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("WEBSITEID: %v", fWebsiteId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiVersion: fApiVersion,
ApiKey: fApiKey,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_WEBSITE,

View File

@@ -157,7 +157,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
if listListenersResp.Body.Listeners != nil {
for _, listener := range listListenersResp.Body.Listeners {
listenerIds = append(listenerIds, *listener.ListenerId)
listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId))
}
}
@@ -192,7 +192,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
if listListenersResp.Body.Listeners != nil {
for _, listener := range listListenersResp.Body.Listeners {
listenerIds = append(listenerIds, *listener.ListenerId)
listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId))
}
}
@@ -211,8 +211,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
d.logger.Info("found https/quic listeners to deploy", slog.Any("listenerIds", listenerIds))
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)
}
}
}

View File

@@ -96,6 +96,7 @@ func TestDeploy(t *testing.T) {
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("REGION: %v", fRegion),
fmt.Sprintf("LISTENERID: %v", fListenerId),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{

View File

@@ -171,7 +171,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil {
errs = append(errs, err)

View File

@@ -18,7 +18,7 @@ var (
fAccessKeySecret string
fRegion string
fLoadbalancerId string
fListenerPort int
fListenerPort int64
fDomain string
)
@@ -31,7 +31,7 @@ func init() {
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "")
flag.Int64Var(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}

View File

@@ -22,6 +22,7 @@ type DeployerConfig struct {
// 阿里云地域。
Region string `json:"region"`
// 服务版本。
// 可取值 "2.0"、"3.0"。
ServiceVersion string `json:"serviceVersion"`
// 自定义域名(支持泛域名)。
Domain string `json:"domain"`

View File

@@ -0,0 +1,322 @@
package aliyunga
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
aliga "github.com/alibabacloud-go/ga-20191120/v3/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice"
)
type DeployerConfig struct {
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 全球加速实例 ID。
AcceleratorId string `json:"acceleratorId"`
// 全球加速监听 ID。
// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
ListenerId string `json:"listenerId,omitempty"`
// SNI 域名(不支持泛域名)。
// 部署资源类型为 [RESOURCE_TYPE_ACCELERATOR]、[RESOURCE_TYPE_LISTENER] 时选填。
Domain string `json:"domain,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *aliga.Client
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("failed to create ssl uploader: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
sslUploader: uploader,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
d.sslUploader.WithLogger(logger)
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 上传证书到 CAS
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_ACCELERATOR:
if err := d.deployToAccelerator(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil {
return nil, err
}
case RESOURCE_TYPE_LISTENER:
if err := d.deployToListener(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType)
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) deployToAccelerator(ctx context.Context, cloudCertId string) error {
if d.config.AcceleratorId == "" {
return errors.New("config `acceleratorId` is required")
}
// 查询 HTTPS 监听列表
// REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlisteners
listenerIds := make([]string, 0)
listListenersPageNumber := int32(1)
listListenersPageSize := int32(50)
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
listListenersReq := &aliga.ListListenersRequest{
RegionId: tea.String("cn-hangzhou"),
AcceleratorId: tea.String(d.config.AcceleratorId),
PageNumber: tea.Int32(listListenersPageNumber),
PageSize: tea.Int32(listListenersPageSize),
}
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
d.logger.Debug("sdk request 'ga.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ga.ListListeners': %w", err)
}
if listListenersResp.Body.Listeners != nil {
for _, listener := range listListenersResp.Body.Listeners {
if strings.EqualFold(tea.StringValue(listener.Protocol), "https") {
listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId))
}
}
}
if len(listListenersResp.Body.Listeners) < int(listListenersPageSize) {
break
} else {
listListenersPageNumber++
}
}
// 遍历更新监听证书
if len(listenerIds) == 0 {
d.logger.Info("no ga listeners to deploy")
} else {
var errs []error
d.logger.Info("found https listeners to deploy", slog.Any("listenerIds", listenerIds))
for _, listenerId := range listenerIds {
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, listenerId, cloudCertId); err != nil {
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
}
return nil
}
func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId string) error {
if d.config.AcceleratorId == "" {
return errors.New("config `acceleratorId` is required")
}
if d.config.ListenerId == "" {
return errors.New("config `listenerId` is required")
}
// 更新监听
if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, d.config.ListenerId, cloudCertId); err != nil {
return err
}
return nil
}
func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudAcceleratorId string, cloudListenerId string, cloudCertId string) error {
// 查询监听绑定的证书列表
// REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlistenercertificates
var listenerDefaultCertificate *aliga.ListListenerCertificatesResponseBodyCertificates
var listenerAdditionalCertificates []*aliga.ListListenerCertificatesResponseBodyCertificates = make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0)
var listListenerCertificatesNextToken *string
for {
listListenerCertificatesReq := &aliga.ListListenerCertificatesRequest{
RegionId: tea.String("cn-hangzhou"),
AcceleratorId: tea.String(d.config.AcceleratorId),
NextToken: listListenerCertificatesNextToken,
MaxResults: tea.Int32(20),
}
listListenerCertificatesResp, err := d.sdkClient.ListListenerCertificates(listListenerCertificatesReq)
d.logger.Debug("sdk request 'ga.ListListenerCertificates'", slog.Any("request", listListenerCertificatesReq), slog.Any("response", listListenerCertificatesResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ga.ListListenerCertificates': %w", err)
}
if listListenerCertificatesResp.Body.Certificates != nil {
for _, certificate := range listListenerCertificatesResp.Body.Certificates {
if tea.BoolValue(certificate.IsDefault) {
listenerDefaultCertificate = certificate
} else {
listenerAdditionalCertificates = append(listenerAdditionalCertificates, certificate)
}
}
}
if listListenerCertificatesResp.Body.NextToken == nil {
break
} else {
listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken
}
}
if d.config.Domain == "" {
// 未指定 SNI只需部署到监听器
if listenerDefaultCertificate != nil && tea.StringValue(listenerDefaultCertificate.CertificateId) == cloudCertId {
d.logger.Info("no need to update ga listener default certificate")
return nil
}
// 修改监听的属性
// REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updatelistener
updateListenerReq := &aliga.UpdateListenerRequest{
RegionId: tea.String("cn-hangzhou"),
ListenerId: tea.String(cloudListenerId),
Certificates: []*aliga.UpdateListenerRequestCertificates{{
Id: tea.String(cloudCertId),
}},
}
updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
d.logger.Debug("sdk request 'ga.UpdateListener'", slog.Any("request", updateListenerReq), slog.Any("response", updateListenerResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ga.UpdateListener': %w", err)
}
} else {
// 指定 SNI需部署到扩展域名
if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool {
return tea.StringValue(item.CertificateId) == cloudCertId
}) {
d.logger.Info("no need to update ga listener additional certificate")
return nil
}
if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool {
return tea.StringValue(item.Domain) == d.config.Domain
}) {
// 为监听替换扩展证书
// REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updateadditionalcertificatewithlistener
updateAdditionalCertificateWithListenerReq := &aliga.UpdateAdditionalCertificateWithListenerRequest{
RegionId: tea.String("cn-hangzhou"),
AcceleratorId: tea.String(cloudAcceleratorId),
ListenerId: tea.String(cloudListenerId),
CertificateId: tea.String(cloudCertId),
Domain: tea.String(d.config.Domain),
}
updateAdditionalCertificateWithListenerResp, err := d.sdkClient.UpdateAdditionalCertificateWithListener(updateAdditionalCertificateWithListenerReq)
d.logger.Debug("sdk request 'ga.UpdateAdditionalCertificateWithListener'", slog.Any("request", updateAdditionalCertificateWithListenerReq), slog.Any("response", updateAdditionalCertificateWithListenerResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ga.UpdateAdditionalCertificateWithListener': %w", err)
}
} else {
// 为监听绑定扩展证书
// REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-associateadditionalcertificateswithlistener
associateAdditionalCertificatesWithListenerReq := &aliga.AssociateAdditionalCertificatesWithListenerRequest{
RegionId: tea.String("cn-hangzhou"),
AcceleratorId: tea.String(cloudAcceleratorId),
ListenerId: tea.String(cloudListenerId),
Certificates: []*aliga.AssociateAdditionalCertificatesWithListenerRequestCertificates{{
Id: tea.String(cloudCertId),
Domain: tea.String(d.config.Domain),
}},
}
associateAdditionalCertificatesWithListenerResp, err := d.sdkClient.AssociateAdditionalCertificatesWithListener(associateAdditionalCertificatesWithListenerReq)
d.logger.Debug("sdk request 'ga.AssociateAdditionalCertificatesWithListener'", slog.Any("request", associateAdditionalCertificatesWithListenerReq), slog.Any("response", associateAdditionalCertificatesWithListenerResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ga.AssociateAdditionalCertificatesWithListener': %w", err)
}
}
}
return nil
}
func createSdkClient(accessKeyId, accessKeySecret string) (*aliga.Client, error) {
// 接入点一览 https://api.aliyun.com/product/Ga
config := &aliopen.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
Endpoint: tea.String("ga.cn-hangzhou.aliyuncs.com"),
}
client, err := aliga.NewClient(config)
if err != nil {
return nil, err
}
return client, nil
}
func createSslUploader(accessKeyId, accessKeySecret string) (uploader.Uploader, error) {
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: accessKeyId,
AccessKeySecret: accessKeySecret,
Region: "cn-hangzhou",
})
return uploader, err
}

View File

@@ -0,0 +1,118 @@
package aliyunga_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ga"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
fAcceleratorId string
fListenerId string
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNGA_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fAcceleratorId, argsPrefix+"ACCELERATORID", "", "")
flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./aliyun_ga_test.go -args \
--CERTIMATE_DEPLOYER_ALIYUNGA_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_ALIYUNGA_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_DEPLOYER_ALIYUNGA_ACCELERATORID="your-ga-accelerator-id" \
--CERTIMATE_DEPLOYER_ALIYUNGA_LISTENERID="your-ga-listener-id" \
--CERTIMATE_DEPLOYER_ALIYUNGA_DOMAIN="your-ga-sni-domain"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy_ToAccelerator", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("ACCELERATORID: %v", fAcceleratorId),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
ResourceType: provider.RESOURCE_TYPE_ACCELERATOR,
AcceleratorId: fAcceleratorId,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
t.Run("Deploy_ToListener", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("LISTENERID: %v", fListenerId),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
ResourceType: provider.RESOURCE_TYPE_LISTENER,
ListenerId: fListenerId,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -0,0 +1,10 @@
package aliyunga
type ResourceType string
const (
// 资源类型:部署到指定全球加速器。
RESOURCE_TYPE_ACCELERATOR = ResourceType("accelerator")
// 资源类型:部署到指定监听器。
RESOURCE_TYPE_LISTENER = ResourceType("listener")
)

View File

@@ -145,7 +145,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
if listListenersResp.Body.Listeners != nil {
for _, listener := range listListenersResp.Body.Listeners {
listenerIds = append(listenerIds, *listener.ListenerId)
listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId))
}
}
@@ -167,7 +167,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil {
errs = append(errs, err)

View File

@@ -13,8 +13,8 @@ import (
)
type DeployerConfig struct {
// 宝塔面板地址。
ApiUrl string `json:"apiUrl"`
// 宝塔面板服务地址。
ServerUrl string `json:"serverUrl"`
// 宝塔面板接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
@@ -36,7 +36,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -82,16 +82,16 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid baota api url")
func createSdkClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid baota server url")
}
if apiKey == "" {
return nil, errors.New("invalid baota api key")
}
client := btsdk.NewClient(apiUrl, apiKey)
client := btsdk.NewClient(serverUrl, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,7 +14,7 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiKey string
)
@@ -23,7 +23,7 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
@@ -33,7 +33,7 @@ Shell command to run this test:
go test -v ./baotapanel_console_test.go -args \
--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_APIURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_APIKEY="your-api-key"
*/
func TestDeploy(t *testing.T) {
@@ -44,12 +44,12 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiKey: fApiKey,
AllowInsecureConnections: true,
AutoRestart: true,

View File

@@ -14,8 +14,8 @@ import (
)
type DeployerConfig struct {
// 宝塔面板地址。
ApiUrl string `json:"apiUrl"`
// 宝塔面板服务地址。
ServerUrl string `json:"serverUrl"`
// 宝塔面板接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
@@ -41,7 +41,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -124,16 +124,16 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid baota api url")
func createSdkClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid baota server url")
}
if apiKey == "" {
return nil, errors.New("invalid baota api key")
}
client := btsdk.NewClient(apiUrl, apiKey)
client := btsdk.NewClient(serverUrl, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,7 +14,7 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiKey string
fSiteType string
fSiteName string
@@ -25,7 +25,7 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fSiteType, argsPrefix+"SITETYPE", "", "")
flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "")
@@ -37,7 +37,7 @@ Shell command to run this test:
go test -v ./baotapanel_site_test.go -args \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_SITETYPE="php" \
--CERTIMATE_DEPLOYER_BAOTAPANELSITE_SITENAME="your-site-name"
@@ -50,14 +50,14 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("SITETYPE: %v", fSiteType),
fmt.Sprintf("SITENAME: %v", fSiteName),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiKey: fApiKey,
AllowInsecureConnections: true,
SiteType: fSiteType,

View File

@@ -0,0 +1,88 @@
package baotapanelconsole
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf"
)
type DeployerConfig struct {
// 堡塔云 WAF 服务地址。
ServerUrl string `json:"serverUrl"`
// 堡塔云 WAF 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *btsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 设置面板 SSL
configSetSSLReq := &btsdk.ConfigSetSSLRequest{
CertContent: certPEM,
KeyContent: privkeyPEM,
}
configSetSSLResp, err := d.sdkClient.ConfigSetSSL(configSetSSLReq)
d.logger.Debug("sdk request 'bt.ConfigSetSSL'", slog.Any("request", configSetSSLReq), slog.Any("response", configSetSSLResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'bt.ConfigSetSSL': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid baota server url")
}
if apiKey == "" {
return nil, errors.New("invalid baota api key")
}
client := btsdk.NewClient(serverUrl, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -0,0 +1,73 @@
package baotapanelconsole_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-console"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fApiKey string
fSiteName string
fSitePort int64
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./baotawaf_console_test.go -args \
--CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_APIKEY="your-api-key"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
ApiKey: fApiKey,
AllowInsecureConnections: true,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -0,0 +1,151 @@
package baotapanelwaf
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf"
typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
)
type DeployerConfig struct {
// 堡塔云 WAF 服务地址。
ServerUrl string `json:"serverUrl"`
// 堡塔云 WAF 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 网站名称。
SiteName string `json:"siteName"`
// 网站 SSL 端口。
// 零值时默认为 443。
SitePort int32 `json:"sitePort,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *btsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
if d.config.SiteName == "" {
return nil, errors.New("config `siteName` is required")
}
if d.config.SitePort == 0 {
d.config.SitePort = 443
}
// 遍历获取网站列表,获取网站 ID
// REF: https://support.huaweicloud.com/api-waf/ListHost.html
siteId := ""
getSitListPage := int32(1)
getSitListPageSize := int32(100)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
getSiteListReq := &btsdk.GetSiteListRequest{
SiteName: typeutil.ToPtr(d.config.SiteName),
Page: typeutil.ToPtr(getSitListPage),
PageSize: typeutil.ToPtr(getSitListPageSize),
}
getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq)
d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err)
}
if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil {
for _, siteItem := range getSiteListResp.Result.List {
if siteItem.SiteName == d.config.SiteName {
siteId = siteItem.SiteId
break
}
}
}
if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) {
break
} else {
getSitListPage++
}
}
if siteId == "" {
return nil, errors.New("site not found")
}
// 修改站点配置
modifySiteReq := &btsdk.ModifySiteRequest{
SiteId: siteId,
Type: typeutil.ToPtr("openCert"),
Server: &btsdk.SiteServerInfo{
ListenSSLPort: typeutil.ToPtr(d.config.SitePort),
SSL: &btsdk.SiteServerSSLInfo{
IsSSL: typeutil.ToPtr(int32(1)),
FullChain: typeutil.ToPtr(certPEM),
PrivateKey: typeutil.ToPtr(privkeyPEM),
},
},
}
modifySiteResp, err := d.sdkClient.ModifySite(modifySiteReq)
d.logger.Debug("sdk request 'bt.ModifySite'", slog.Any("request", modifySiteReq), slog.Any("response", modifySiteResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'bt.ModifySite': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid baota server url")
}
if apiKey == "" {
return nil, errors.New("invalid baota api key")
}
client := btsdk.NewClient(serverUrl, apiKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -0,0 +1,81 @@
package baotapanelwaf_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fApiKey string
fSiteName string
fSitePort int64
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFSITE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "")
flag.Int64Var(&fSitePort, argsPrefix+"SITEPORT", 0, "")
}
/*
Shell command to run this test:
go test -v ./baotawaf_site_test.go -args \
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITENAME="your-site-name"\
--CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITEPORT=443
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("SITENAME: %v", fSiteName),
fmt.Sprintf("SITEPORT: %v", fSitePort),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
ApiKey: fApiKey,
AllowInsecureConnections: true,
SiteName: fSiteName,
SitePort: int32(fSitePort),
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -15,8 +15,8 @@ import (
)
type DeployerConfig struct {
// Cdnfly 地址。
ApiUrl string `json:"apiUrl"`
// Cdnfly 服务地址。
ServerUrl string `json:"serverUrl"`
// Cdnfly 用户端 API Key。
ApiKey string `json:"apiKey"`
// Cdnfly 用户端 API Secret。
@@ -46,7 +46,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.ApiSecret, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.ApiSecret, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -160,9 +160,9 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
return nil
}
func createSdkClient(apiUrl, apiKey, apiSecret string, skipTlsVerify bool) (*cfsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid cachefly api url")
func createSdkClient(serverUrl, apiKey, apiSecret string, skipTlsVerify bool) (*cfsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid cachefly server url")
}
if apiKey == "" {
@@ -173,7 +173,7 @@ func createSdkClient(apiUrl, apiKey, apiSecret string, skipTlsVerify bool) (*cfs
return nil, errors.New("invalid cachefly api secret")
}
client := cfsdk.NewClient(apiUrl, apiKey, apiSecret)
client := cfsdk.NewClient(serverUrl, apiKey, apiSecret)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,7 +14,7 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiKey string
fApiSecret string
fCertificateId string
@@ -25,7 +25,7 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fApiSecret, argsPrefix+"APISECRET", "", "")
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
@@ -37,7 +37,7 @@ Shell command to run this test:
go test -v ./cdnfly_test.go -args \
--CERTIMATE_DEPLOYER_CDNFLY_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_CDNFLY_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_CDNFLY_APIURL="http://127.0.0.1:88" \
--CERTIMATE_DEPLOYER_CDNFLY_SERVERURL="http://127.0.0.1:88" \
--CERTIMATE_DEPLOYER_CDNFLY_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_CDNFLY_APISECRET="your-api-secret" \
--CERTIMATE_DEPLOYER_CDNFLY_CERTIFICATEID="your-cert-id"
@@ -50,14 +50,14 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("APISECRET: %v", fApiSecret),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiKey: fApiKey,
ApiSecret: fApiSecret,
AllowInsecureConnections: true,

View File

@@ -0,0 +1,8 @@
package flexcdn
type ResourceType string
const (
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
)

View File

@@ -0,0 +1,145 @@
package flexcdn
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"log/slog"
"net/url"
"time"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
flexcdnsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/flexcdn"
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
)
type DeployerConfig struct {
// FlexCDN 服务地址。
ServerUrl string `json:"serverUrl"`
// FlexCDN 用户角色。
// 可取值 "user"、"admin"。
ApiRole string `json:"apiRole"`
// FlexCDN AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// FlexCDN AccessKey。
AccessKey string `json:"accessKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId int64 `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *flexcdnsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_CERTIFICATE:
if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType)
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error {
if d.config.CertificateId == 0 {
return errors.New("config `certificateId` is required")
}
// 解析证书内容
certX509, err := certutil.ParseCertificateFromPEM(certPEM)
if err != nil {
return err
}
// 修改证书
// REF: https://flexcdn.cloud/dev/api/service/SSLCertService?role=user#updateSSLCert
updateSSLCertReq := &flexcdnsdk.UpdateSSLCertRequest{
SSLCertId: d.config.CertificateId,
IsOn: true,
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
Description: "upload from certimate",
ServerName: certX509.Subject.CommonName,
IsCA: false,
CertData: base64.StdEncoding.EncodeToString([]byte(certPEM)),
KeyData: base64.StdEncoding.EncodeToString([]byte(privkeyPEM)),
TimeBeginAt: certX509.NotBefore.Unix(),
TimeEndAt: certX509.NotAfter.Unix(),
DNSNames: certX509.DNSNames,
CommonNames: []string{certX509.Subject.CommonName},
}
updateSSLCertResp, err := d.sdkClient.UpdateSSLCert(updateSSLCertReq)
d.logger.Debug("sdk request 'flexcdn.UpdateSSLCert'", slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'flexcdn.UpdateSSLCert': %w", err)
}
return nil
}
func createSdkClient(serverUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*flexcdnsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid flexcdn server url")
}
if apiRole != "user" && apiRole != "admin" {
return nil, errors.New("invalid flexcdn api role")
}
if accessKeyId == "" {
return nil, errors.New("invalid flexcdn access key id")
}
if accessKey == "" {
return nil, errors.New("invalid flexcdn access key")
}
client := flexcdnsdk.NewClient(serverUrl, apiRole, accessKeyId, accessKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -0,0 +1,83 @@
package flexcdn_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fAccessKeyId string
fAccessKey string
fCertificateId int64
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_FLEXCDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
}
/*
Shell command to run this test:
go test -v ./flexcdn_test.go -args \
--CERTIMATE_DEPLOYER_FLEXCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_FLEXCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_FLEXCDN_SERVERURL="http://127.0.0.1:7788" \
--CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEY="your-access-key" \
--CERTIMATE_DEPLOYER_FLEXCDN_CERTIFICATEID="your-cerficiate-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy_ToCertificate", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEY: %v", fAccessKey),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
ApiRole: "user",
AccessKeyId: fAccessKeyId,
AccessKey: fAccessKey,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_CERTIFICATE,
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -16,9 +16,10 @@ import (
)
type DeployerConfig struct {
// GoEdge URL
ApiUrl string `json:"apiUrl"`
// GoEdge 服务地址
ServerUrl string `json:"serverUrl"`
// GoEdge 用户角色。
// 可取值 "user"、"admin"。
ApiRole string `json:"apiRole"`
// GoEdge AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
@@ -46,7 +47,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -118,9 +119,9 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
return nil
}
func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*goedgesdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid goedge api url")
func createSdkClient(serverUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*goedgesdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid goedge server url")
}
if apiRole != "user" && apiRole != "admin" {
@@ -135,7 +136,7 @@ func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVeri
return nil, errors.New("invalid goedge access key")
}
client := goedgesdk.NewClient(apiUrl, apiRole, accessKeyId, accessKey)
client := goedgesdk.NewClient(serverUrl, apiRole, accessKeyId, accessKey)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,10 +14,10 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fAccessKeyId string
fAccessKey string
fCertificateId int
fCertificateId int64
)
func init() {
@@ -25,10 +25,10 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
}
/*
@@ -37,7 +37,7 @@ Shell command to run this test:
go test -v ./goedge_test.go -args \
--CERTIMATE_DEPLOYER_GOEDGE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_GOEDGE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_GOEDGE_APIURL="http://127.0.0.1:7788" \
--CERTIMATE_DEPLOYER_GOEDGE_SERVERURL="http://127.0.0.1:7788" \
--CERTIMATE_DEPLOYER_GOEDGE_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_GOEDGE_ACCESSKEY="your-access-key" \
--CERTIMATE_DEPLOYER_GOEDGE_CERTIFICATEID="your-cerficiate-id"
@@ -45,24 +45,25 @@ Shell command to run this test:
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Run("Deploy_ToCertificate", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEY: %v", fAccessKey),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiRole: "user",
AccessKeyId: fAccessKeyId,
AccessKey: fAccessKey,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_CERTIFICATE,
CertificateId: int64(fCertificateId),
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)

View File

@@ -210,7 +210,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil {
errs = append(errs, err)

View File

@@ -0,0 +1,8 @@
package lecdn
type ResourceType string
const (
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
)

View File

@@ -0,0 +1,176 @@
package lecdn
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"time"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
leclientsdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/client"
lemastersdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/master"
)
type DeployerConfig struct {
// LeCDN 服务地址。
ServerUrl string `json:"serverUrl"`
// LeCDN 版本。
// 可取值 "v3"。
ApiVersion string `json:"apiVersion"`
// LeCDN 用户角色。
// 可取值 "client"、"master"。
ApiRole string `json:"apiRole"`
// LeCDN 用户名。
Username string `json:"accessKeyId"`
// LeCDN 用户密码。
Password string `json:"accessKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId int64 `json:"certificateId,omitempty"`
// 客户 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时选填。
ClientId int64 `json:"clientId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient interface{}
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
const (
apiVersionV3 = "v3"
apiRoleClient = "client"
apiRoleMaster = "master"
)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.ApiVersion, config.ApiRole, config.Username, config.Password, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_CERTIFICATE:
if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType)
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error {
if d.config.CertificateId == 0 {
return errors.New("config `certificateId` is required")
}
// 修改证书
// REF: https://wdk0pwf8ul.feishu.cn/wiki/YE1XwCRIHiLYeKkPupgcXrlgnDd
switch sdkClient := d.sdkClient.(type) {
case *leclientsdkv3.Client:
updateSSLCertReq := &leclientsdkv3.UpdateCertificateRequest{
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
Description: "upload from certimate",
Type: "upload",
SSLPEM: certPEM,
SSLKey: privkeyPEM,
AutoRenewal: false,
}
updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq)
d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err)
}
case *lemastersdkv3.Client:
updateSSLCertReq := &lemastersdkv3.UpdateCertificateRequest{
ClientId: d.config.ClientId,
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
Description: "upload from certimate",
Type: "upload",
SSLPEM: certPEM,
SSLKey: privkeyPEM,
AutoRenewal: false,
}
updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq)
d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err)
}
default:
panic("sdk client is not implemented")
}
return nil
}
func createSdkClient(serverUrl, apiVersion, apiRole, username, password string, skipTlsVerify bool) (interface{}, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid lecdn server url")
}
if username == "" {
return nil, errors.New("invalid lecdn username")
}
if password == "" {
return nil, errors.New("invalid lecdn password")
}
if apiVersion == apiVersionV3 && apiRole == apiRoleClient {
// v3 版客户端
client := leclientsdkv3.NewClient(serverUrl, username, password)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
} else if apiVersion == apiVersionV3 && apiRole == apiRoleMaster {
// v3 版主控端
client := lemastersdkv3.NewClient(serverUrl, username, password)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}
return nil, fmt.Errorf("invalid lecdn api version or user role")
}

View File

@@ -0,0 +1,87 @@
package lecdn_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/lecdn"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fApiVersion string
fUsername string
fPassword string
fCertificateId int64
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_LECDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v3", "")
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
}
/*
Shell command to run this test:
go test -v ./lecdn_test.go -args \
--CERTIMATE_DEPLOYER_LECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_LECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_LECDN_SERVERURL="http://127.0.0.1:5090" \
--CERTIMATE_DEPLOYER_LECDN_USERNAME="your-username" \
--CERTIMATE_DEPLOYER_LECDN_PASSWORD="your-password" \
--CERTIMATE_DEPLOYER_LECDN_CERTIFICATEID="your-cerficiate-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy_ToCertificate", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIVERSION: %v", fApiVersion),
fmt.Sprintf("USERNAME: %v", fUsername),
fmt.Sprintf("PASSWORD: %v", fPassword),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
ApiVersion: fApiVersion,
ApiRole: "user",
Username: fUsername,
Password: fPassword,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_CERTIFICATE,
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -16,8 +16,8 @@ import (
)
type DeployerConfig struct {
// Proxmox VE 地址。
ApiUrl string `json:"apiUrl"`
// Proxmox VE 服务地址。
ServerUrl string `json:"serverUrl"`
// Proxmox VE API Token。
ApiToken string `json:"apiToken"`
// Proxmox VE API Token Secret。
@@ -43,7 +43,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiToken, config.ApiTokenSecret, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiToken, config.ApiTokenSecret, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -91,9 +91,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*proxmox.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid pve api url")
func createSdkClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*proxmox.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid pve server url")
}
if apiToken == "" {
@@ -112,7 +112,7 @@ func createSdkClient(apiUrl, apiToken, apiTokenSecret string, skipTlsVerify bool
}
}
client := proxmox.NewClient(
strings.TrimRight(apiUrl, "/")+"/api2/json",
strings.TrimRight(serverUrl, "/")+"/api2/json",
proxmox.WithHTTPClient(httpClient),
proxmox.WithAPIToken(apiToken, apiTokenSecret),
)

View File

@@ -14,7 +14,7 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiToken string
fApiTokenSecret string
fNodeName string
@@ -25,7 +25,7 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
flag.StringVar(&fApiTokenSecret, argsPrefix+"APITOKENSECRET", "", "")
flag.StringVar(&fNodeName, argsPrefix+"NODENAME", "", "")
@@ -37,7 +37,7 @@ Shell command to run this test:
go test -v ./proxmoxve_test.go -args \
--CERTIMATE_DEPLOYER_PROXMOXVE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_PROXMOXVE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_PROXMOXVE_APIURL="http://127.0.0.1:8006" \
--CERTIMATE_DEPLOYER_PROXMOXVE_SERVERURL="http://127.0.0.1:8006" \
--CERTIMATE_DEPLOYER_PROXMOXVE_APITOKEN="your-api-token" \
--CERTIMATE_DEPLOYER_PROXMOXVE_APITOKENSECRET="your-api-token-secret" \
--CERTIMATE_DEPLOYER_PROXMOXVE_NODENAME="your-cluster-node-name"
@@ -50,14 +50,14 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APITOKEN: %v", fApiToken),
fmt.Sprintf("APITOKENSECRET: %v", fApiTokenSecret),
fmt.Sprintf("NODENAME: %v", fNodeName),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiToken: fApiToken,
ApiTokenSecret: fApiTokenSecret,
AllowInsecureConnections: true,

View File

@@ -0,0 +1,94 @@
package ratpanelconsole
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel"
)
type DeployerConfig struct {
// 耗子面板服务地址。
ServerUrl string `json:"serverUrl"`
// 耗子面板访问令牌 ID。
AccessTokenId int32 `json:"accessTokenId"`
// 耗子面板访问令牌。
AccessToken string `json:"accessToken"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *rpsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 设置面板 SSL 证书
settingCertReq := &rpsdk.SettingCertRequest{
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
settingCertResp, err := d.sdkClient.SettingCert(settingCertReq)
d.logger.Debug("sdk request 'ratpanel.SettingCert'", slog.Any("request", settingCertReq), slog.Any("response", settingCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.SettingCert': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(serverUrl string, accessTokenId int32, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid ratpanel server url")
}
if accessTokenId == 0 {
return nil, errors.New("invalid ratpanel access token id")
}
if accessToken == "" {
return nil, errors.New("invalid ratpanel access token")
}
client := rpsdk.NewClient(serverUrl, accessTokenId, accessToken)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -0,0 +1,76 @@
package ratpanelconsole_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-console"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fAccessTokenId int64
fAccessToken string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_RATPANELCONSOLE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.Int64Var(&fAccessTokenId, argsPrefix+"ACCESSTOKENID", 0, "")
flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "")
}
/*
Shell command to run this test:
go test -v ./ratpanel_console_test.go -args \
--CERTIMATE_DEPLOYER_RATPANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_RATPANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_RATPANELCONSOLE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_RATPANELCONSOLE_ACCESSTOKENID="your-access-token-id" \
--CERTIMATE_DEPLOYER_RATPANELCONSOLE_ACCESSTOKEN="your-access-token"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("ACCESSTOKENID: %v", fAccessTokenId),
fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
AccessTokenId: int32(fAccessTokenId),
AccessToken: fAccessToken,
AllowInsecureConnections: true,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -0,0 +1,101 @@
package ratpanelsite
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel"
)
type DeployerConfig struct {
// 耗子面板服务地址。
ServerUrl string `json:"serverUrl"`
// 耗子面板访问令牌 ID。
AccessTokenId int32 `json:"accessTokenId"`
// 耗子面板访问令牌。
AccessToken string `json:"accessToken"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 网站名称。
SiteName string `json:"siteName"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *rpsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ServerUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
if d.config.SiteName == "" {
return nil, errors.New("config `siteName` is required")
}
// 设置站点 SSL 证书
websiteCertReq := &rpsdk.WebsiteCertRequest{
SiteName: d.config.SiteName,
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
websiteCertResp, err := d.sdkClient.WebsiteCert(websiteCertReq)
d.logger.Debug("sdk request 'ratpanel.WebsiteCert'", slog.Any("request", websiteCertReq), slog.Any("response", websiteCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.WebsiteCert': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(serverUrl string, accessTokenId int32, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid ratpanel server url")
}
if accessTokenId == 0 {
return nil, errors.New("invalid ratpanel access token id")
}
if accessToken == "" {
return nil, errors.New("invalid ratpanel access token")
}
client := rpsdk.NewClient(serverUrl, accessTokenId, accessToken)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -0,0 +1,81 @@
package ratpanelsite_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-site"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fAccessTokenId int64
fAccessToken string
fSiteName string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_RATPANELSITE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.Int64Var(&fAccessTokenId, argsPrefix+"ACCESSTOKENID", 0, "")
flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "")
flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "")
}
/*
Shell command to run this test:
go test -v ./ratpanel_site_test.go -args \
--CERTIMATE_DEPLOYER_RATPANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_RATPANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_RATPANELSITE_SERVERURL="http://127.0.0.1:8888" \
--CERTIMATE_DEPLOYER_RATPANELSITE_ACCESSTOKENID="your-access-token-id" \
--CERTIMATE_DEPLOYER_RATPANELSITE_ACCESSTOKEN="your-access-token" \
--CERTIMATE_DEPLOYER_RATPANELSITE_SITENAME="your-site-name"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("ACCESSTOKENID: %v", fAccessTokenId),
fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken),
fmt.Sprintf("SITENAME: %v", fSiteName),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
AccessTokenId: int32(fAccessTokenId),
AccessToken: fAccessToken,
AllowInsecureConnections: true,
SiteName: fSiteName,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -13,8 +13,8 @@ import (
)
type DeployerConfig struct {
// 雷池 URL
ApiUrl string `json:"apiUrl"`
// 雷池服务地址
ServerUrl string `json:"serverUrl"`
// 雷池 API Token。
ApiToken string `json:"apiToken"`
// 是否允许不安全的连接。
@@ -39,7 +39,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiToken, config.AllowInsecureConnections)
client, err := createSdkClient(config.ServerUrl, config.ApiToken, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
@@ -98,16 +98,16 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
return nil
}
func createSdkClient(apiUrl, apiToken string, skipTlsVerify bool) (*safelinesdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid safeline api url")
func createSdkClient(serverUrl, apiToken string, skipTlsVerify bool) (*safelinesdk.Client, error) {
if _, err := url.Parse(serverUrl); err != nil {
return nil, errors.New("invalid safeline server url")
}
if apiToken == "" {
return nil, errors.New("invalid safeline api token")
}
client := safelinesdk.NewClient(apiUrl, apiToken)
client := safelinesdk.NewClient(serverUrl, apiToken)
if skipTlsVerify {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@@ -14,9 +14,9 @@ import (
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fServerUrl string
fApiToken string
fCertificateId int
fCertificateId int64
)
func init() {
@@ -24,9 +24,9 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
}
/*
@@ -35,7 +35,7 @@ Shell command to run this test:
go test -v ./safeline_test.go -args \
--CERTIMATE_DEPLOYER_SAFELINE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_SAFELINE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_SAFELINE_APIURL="http://127.0.0.1:9443" \
--CERTIMATE_DEPLOYER_SAFELINE_SERVERURL="http://127.0.0.1:9443" \
--CERTIMATE_DEPLOYER_SAFELINE_APITOKEN="your-api-token" \
--CERTIMATE_DEPLOYER_SAFELINE_CERTIFICATEID="your-cerficiate-id"
*/
@@ -47,13 +47,13 @@ func TestDeploy(t *testing.T) {
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIURL: %v", fApiUrl),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APITOKEN: %v", fApiToken),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ServerUrl: fServerUrl,
ApiToken: fApiToken,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_CERTIFICATE,

View File

@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"log/slog"
"net"
"os"
"path/filepath"
@@ -16,6 +17,23 @@ import (
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
)
type JumpServerConfig struct {
// SSH 主机。
// 零值时默认为 "localhost"。
SshHost string `json:"sshHost,omitempty"`
// SSH 端口。
// 零值时默认为 22。
SshPort int32 `json:"sshPort,omitempty"`
// SSH 登录用户名。
SshUsername string `json:"sshUsername,omitempty"`
// SSH 登录密码。
SshPassword string `json:"sshPassword,omitempty"`
// SSH 登录私钥。
SshKey string `json:"sshKey,omitempty"`
// SSH 登录私钥口令。
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
}
type DeployerConfig struct {
// SSH 主机。
// 零值时默认为 "localhost"。
@@ -31,6 +49,8 @@ type DeployerConfig struct {
SshKey string `json:"sshKey,omitempty"`
// SSH 登录私钥口令。
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
// 跳板机配置数组。
JumpServers []JumpServerConfig `json:"jumpServers,omitempty"`
// 是否回退使用 SCP。
UseSCP bool `json:"useSCP,omitempty"`
// 前置命令。
@@ -97,8 +117,61 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 连接
var targetConn net.Conn
// 连接到跳板机
if len(d.config.JumpServers) > 0 {
var jumpClient *ssh.Client
for i, jumpServerConf := range d.config.JumpServers {
d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost))
var jumpConn net.Conn
// 第一个连接是主机发起,后续通过跳板机发起
if jumpClient == nil {
jumpConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort))
} else {
jumpConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort))
}
if err != nil {
return nil, fmt.Errorf("failed to connect to jump server [%d]: %w", i+1, err)
}
defer jumpConn.Close()
newClient, err := createSshClient(
jumpConn,
jumpServerConf.SshHost,
jumpServerConf.SshPort,
jumpServerConf.SshUsername,
jumpServerConf.SshPassword,
jumpServerConf.SshKey,
jumpServerConf.SshKeyPassphrase,
)
if err != nil {
return nil, fmt.Errorf("failed to create jump server ssh client[%d]: %w", i+1, err)
}
defer newClient.Close()
jumpClient = newClient
d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost))
}
// 通过跳板机发起 TCP 连接到目标服务器
targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
if err != nil {
return nil, fmt.Errorf("failed to connect to target server: %w", err)
}
} else {
// 直接发起 TCP 连接到目标服务器
targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
if err != nil {
return nil, fmt.Errorf("failed to connect to target server: %w", err)
}
}
defer targetConn.Close()
// 通过已有的连接创建目标服务器 SSH 客户端
client, err := createSshClient(
targetConn,
d.config.SshHost,
d.config.SshPort,
d.config.SshUsername,
@@ -189,7 +262,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return &deployer.DeployResult{}, nil
}
func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
func createSshClient(conn net.Conn, host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
if host == "" {
host = "localhost"
}
@@ -217,11 +290,16 @@ func createSshClient(host string, port int32, username string, password string,
authMethod = ssh.Password(password)
}
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
sshConn, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
return nil, err
}
return ssh.NewClient(sshConn, chans, reqs), nil
}
func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) {

View File

@@ -15,7 +15,7 @@ var (
fInputCertPath string
fInputKeyPath string
fSshHost string
fSshPort int
fSshPort int64
fSshUsername string
fSshPassword string
fOutputCertPath string
@@ -28,7 +28,7 @@ func init() {
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fSshHost, argsPrefix+"SSHHOST", "", "")
flag.IntVar(&fSshPort, argsPrefix+"SSHPORT", 0, "")
flag.Int64Var(&fSshPort, argsPrefix+"SSHPORT", 0, "")
flag.StringVar(&fSshUsername, argsPrefix+"SSHUSERNAME", "", "")
flag.StringVar(&fSshPassword, argsPrefix+"SSHPASSWORD", "", "")
flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "")

View File

@@ -2,9 +2,11 @@ package tencentcloudcdn
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
@@ -132,6 +134,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err)
}
// 循环获取部署任务详情,等待任务状态变更
// REF: https://cloud.tencent.com.cn/document/api/400/91658
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest()
describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId))
describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq)
d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err)
}
var runningCount, succeededCount, failedCount, totalCount int64
if describeHostDeployRecordDetailResp.Response.TotalCount == nil {
return nil, errors.New("unexpected deployment job status")
} else {
if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil {
runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount
}
if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil {
succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount
}
if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil {
failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount
}
if describeHostDeployRecordDetailResp.Response.TotalCount != nil {
totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount
}
if succeededCount+failedCount == totalCount {
break
}
}
d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount))
time.Sleep(time.Second * 5)
}
}
return &deployer.DeployResult{}, nil

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log/slog"
"time"
tcclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
@@ -151,6 +152,49 @@ func (d *DeployerProvider) deployViaSslService(ctx context.Context, cloudCertId
return fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err)
}
// 循环获取部署任务详情,等待任务状态变更
// REF: https://cloud.tencent.com.cn/document/api/400/91658
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest()
describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId))
describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq)
d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err)
}
var runningCount, succeededCount, failedCount, totalCount int64
if describeHostDeployRecordDetailResp.Response.TotalCount == nil {
return errors.New("unexpected deployment job status")
} else {
if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil {
runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount
}
if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil {
succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount
}
if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil {
failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount
}
if describeHostDeployRecordDetailResp.Response.TotalCount != nil {
totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount
}
if succeededCount+failedCount == totalCount {
break
}
}
d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount))
time.Sleep(time.Second * 5)
}
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log/slog"
"time"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
@@ -102,6 +103,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err)
}
// 循环获取部署任务详情,等待任务状态变更
// REF: https://cloud.tencent.com.cn/document/api/400/91658
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest()
describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId))
describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq)
d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err)
}
var runningCount, succeededCount, failedCount, totalCount int64
if describeHostDeployRecordDetailResp.Response.TotalCount == nil {
return nil, errors.New("unexpected deployment job status")
} else {
if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil {
runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount
}
if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil {
succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount
}
if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil {
failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount
}
if describeHostDeployRecordDetailResp.Response.TotalCount != nil {
totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount
}
if succeededCount+failedCount == totalCount {
break
}
}
d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount))
time.Sleep(time.Second * 5)
}
return &deployer.DeployResult{}, nil
}

View File

@@ -2,9 +2,11 @@ package tencentcloudecdn
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
@@ -103,7 +105,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
} else {
d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds))
// 证书部署到 ECDN 实例
// 证书部署到 CDN 实例
// REF: https://cloud.tencent.com/document/product/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
@@ -115,6 +117,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err)
}
// 循环获取部署任务详情,等待任务状态变更
// REF: https://cloud.tencent.com.cn/document/api/400/91658
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest()
describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId))
describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq)
d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err)
}
var runningCount, succeededCount, failedCount, totalCount int64
if describeHostDeployRecordDetailResp.Response.TotalCount == nil {
return nil, errors.New("unexpected deployment job status")
} else {
if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil {
runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount
}
if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil {
succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount
}
if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil {
failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount
}
if describeHostDeployRecordDetailResp.Response.TotalCount != nil {
totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount
}
if succeededCount+failedCount == totalCount {
break
}
}
d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount))
time.Sleep(time.Second * 5)
}
}
return &deployer.DeployResult{}, nil

View File

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

View File

@@ -0,0 +1,101 @@
package unicloudwebhost
import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
unisdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/dcloud/unicloud"
)
type DeployerConfig struct {
// uniCloud 控制台账号。
Username string `json:"username"`
// uniCloud 控制台密码。
Password string `json:"password"`
// 服务空间提供商。
// 可取值 "aliyun"、"tencent"。
SpaceProvider string `json:"spaceProvider"`
// 服务空间 ID。
SpaceId string `json:"spaceId"`
// 托管网站域名(不支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *unisdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.Username, config.Password)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
if d.config.SpaceProvider == "" {
return nil, errors.New("config `spaceProvider` is required")
}
if d.config.SpaceId == "" {
return nil, errors.New("config `spaceId` is required")
}
if d.config.Domain == "" {
return nil, errors.New("config `domain` is required")
}
// 变更网站证书
createDomainWithCertReq := &unisdk.CreateDomainWithCertRequest{
Provider: d.config.SpaceProvider,
SpaceId: d.config.SpaceId,
Domain: d.config.Domain,
Cert: url.QueryEscape(certPEM),
Key: url.QueryEscape(privkeyPEM),
}
createDomainWithCertResp, err := d.sdkClient.CreateDomainWithCert(createDomainWithCertReq)
d.logger.Debug("sdk request 'unicloud.host.CreateDomainWithCert'", slog.Any("request", createDomainWithCertReq), slog.Any("response", createDomainWithCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'unicloud.host.CreateDomainWithCert': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(username, password string) (*unisdk.Client, error) {
if username == "" {
return nil, errors.New("invalid unicloud username")
}
if password == "" {
return nil, errors.New("invalid unicloud password")
}
client := unisdk.NewClient(username, password)
return client, nil
}

View File

@@ -0,0 +1,85 @@
package unicloudwebhost_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/unicloud-webhost"
)
var (
fInputCertPath string
fInputKeyPath string
fUsername string
fPassword string
fSpaceProvider string
fSpaceId string
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
flag.StringVar(&fSpaceProvider, argsPrefix+"SPACEPROVIDER", "", "")
flag.StringVar(&fSpaceId, argsPrefix+"SPACEID", "", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./unicloud_webhost_test.go -args \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_USERNAME="your-username" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_PASSWORD="your-password" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_SPACEPROVIDER="aliyun/tencent" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_SPACEID="your-space-id" \
--CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("USERNAME: %v", fUsername),
fmt.Sprintf("PASSWORD: %v", fPassword),
fmt.Sprintf("SPACEPROVIDER: %v", fSpaceProvider),
fmt.Sprintf("SPACEID: %v", fSpaceId),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
Username: fUsername,
Password: fPassword,
SpaceProvider: fSpaceProvider,
SpaceId: fSpaceId,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -0,0 +1,104 @@
package wangsucdn
import (
"context"
"errors"
"fmt"
"log/slog"
"strconv"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate"
wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn"
)
type DeployerConfig struct {
// 网宿云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 网宿云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 加速域名数组。
Domains []string `json:"domains"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *wangsusdk.Client
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
})
if err != nil {
return nil, fmt.Errorf("failed to create ssl uploader: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
sslUploader: uploader,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 上传证书到证书管理
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 批量修改域名证书配置
// REF: https://www.wangsu.com/document/api-doc/37447
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
batchUpdateCertificateConfigReq := &wangsusdk.BatchUpdateCertificateConfigRequest{
CertificateId: certId,
DomainNames: d.config.Domains,
}
batchUpdateCertificateConfigResp, err := d.sdkClient.BatchUpdateCertificateConfig(batchUpdateCertificateConfigReq)
d.logger.Debug("sdk request 'cdn.BatchUpdateCertificateConfig'", slog.Any("request", batchUpdateCertificateConfigReq), slog.Any("response", batchUpdateCertificateConfigResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.BatchUpdateCertificateConfig': %w", err)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) {
if accessKeyId == "" {
return nil, errors.New("invalid wangsu access key id")
}
if accessKeySecret == "" {
return nil, errors.New("invalid wangsu access key secret")
}
return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil
}

View File

@@ -0,0 +1,75 @@
package wangsucdn_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdn"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./wangsu_cdn_test.go -args \
--CERTIMATE_DEPLOYER_WANGSUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_WANGSUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_DEPLOYER_WANGSUCDN_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
Domains: []string{fDomain},
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -17,7 +17,7 @@ import (
"time"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn"
wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdnpro"
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
)
@@ -88,9 +88,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
// 查询已部署加速域名的详情
getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain)
d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp))
d.logger.Debug("sdk request 'cdnpro.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetHostnameDetail': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetHostnameDetail': %w", err)
}
// 生成网宿云证书参数
@@ -126,9 +126,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
NewVersion: certificateNewVersionInfo,
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateCertificate': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateCertificate': %w", err)
}
wangsuCertUrl = createCertificateResp.CertificateUrl
@@ -149,9 +149,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
NewVersion: certificateNewVersionInfo,
}
updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateCertificate': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.UpdateCertificate': %w", err)
}
wangsuCertUrl = updateCertificateResp.CertificateUrl
@@ -186,9 +186,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
createDeploymentTaskReq.Webhook = typeutil.ToPtr(d.config.WebhookId)
}
createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp))
d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateDeploymentTask': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateDeploymentTask': %w", err)
}
// 循环获取部署任务详细信息,等待任务状态变更
@@ -206,9 +206,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
}
getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId)
d.logger.Info("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
d.logger.Info("sdk request 'cdnpro.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDeploymentTaskDetail': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetDeploymentTaskDetail': %w", err)
}
if getDeploymentTaskDetailResp.Status == "failed" {

View File

@@ -0,0 +1,109 @@
package wangsucertificate
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate"
wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/certificate"
typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
)
type DeployerConfig struct {
// 网宿云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 网宿云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 证书 ID。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateId string `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *wangsusdk.Client
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
})
if err != nil {
return nil, fmt.Errorf("failed to create ssl uploader: %w", err)
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
sslUploader: uploader,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
if d.config.CertificateId == "" {
// 上传证书到证书管理
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
} else {
// 修改证书
// REF: https://www.wangsu.com/document/api-doc/25568?productCode=certificatemanagement
updateCertificateReq := &wangsusdk.UpdateCertificateRequest{
Name: typeutil.ToPtr(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
Certificate: typeutil.ToPtr(certPEM),
PrivateKey: typeutil.ToPtr(privkeyPEM),
Comment: typeutil.ToPtr("upload from certimate"),
}
updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq)
d.logger.Debug("sdk request 'certificatemanagement.UpdateCertificate'", slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err)
}
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) {
if accessKeyId == "" {
return nil, errors.New("invalid wangsu access key id")
}
if accessKeySecret == "" {
return nil, errors.New("invalid wangsu access key secret")
}
return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil
}

View File

@@ -0,0 +1,75 @@
package wangsucertificate_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
fCertificateId string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
}
/*
Shell command to run this test:
go test -v ./wangsu_certificate_test.go -args \
--CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_CERTIFICATEID="your-certificate-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -159,9 +159,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
// 生成请求
// 其中 GET 请求需转换为查询参数
req := d.httpClient.R().
SetContext(ctx).
SetHeaderMultiValues(webhookHeaders)
req := d.httpClient.R().SetHeaderMultiValues(webhookHeaders)
req.URL = webhookUrl.String()
req.Method = webhookMethod
if webhookMethod == http.MethodGet {
@@ -178,7 +176,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
}
// 发送请求
resp, err := req.SetDebug(true).Send()
resp, err := req.Send()
if err != nil {
return nil, fmt.Errorf("failed to send webhook request: %w", err)
} else if resp.IsError() {

View File

@@ -2,10 +2,10 @@ package bark
import (
"context"
"fmt"
"log/slog"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/bark"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -19,8 +19,9 @@ type NotifierConfig struct {
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -30,9 +31,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
@@ -46,16 +50,26 @@ 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) {
var srv notify.Notifier
if n.config.ServerUrl == "" {
srv = bark.New(n.config.DeviceKey)
} else {
srv = bark.NewWithServers(n.config.DeviceKey, n.config.ServerUrl)
const defaultServerURL = "https://api.day.app/"
serverUrl := defaultServerURL
if n.config.ServerUrl != "" {
serverUrl = n.config.ServerUrl
}
err = srv.Send(ctx, subject, message)
// REF: https://bark.day.app/#/tutorial
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,
"body": message,
"device_key": n.config.DeviceKey,
})
resp, err := req.Post(serverUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("bark api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("bark api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil

View File

@@ -1,4 +1,4 @@
package dingtalk
package dingtalkbot
import (
"context"
@@ -6,7 +6,7 @@ import (
"log/slog"
"net/url"
"github.com/nikoksr/notify/service/dingding"
"github.com/blinkbean/dingtalk"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -48,17 +48,18 @@ 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) {
webhookUrl, err := url.Parse(n.config.WebhookUrl)
if err != nil {
return nil, fmt.Errorf("invalid webhook url: %w", err)
return nil, fmt.Errorf("dingtalk api error: invalid webhook url: %w", err)
}
srv := dingding.New(&dingding.Config{
Token: webhookUrl.Query().Get("access_token"),
Secret: n.config.Secret,
})
var bot *dingtalk.DingTalk
if n.config.Secret == "" {
bot = dingtalk.InitDingTalk([]string{webhookUrl.Query().Get("access_token")}, "")
} else {
bot = dingtalk.InitDingTalkWithSecret(webhookUrl.Query().Get("access_token"), n.config.Secret)
}
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
if err := bot.SendTextMessage(subject + "\n" + message); err != nil {
return nil, fmt.Errorf("dingtalk api error: %w", err)
}
return &notifier.NotifyResult{}, nil

View File

@@ -1,4 +1,4 @@
package dingtalk_test
package dingtalkbot_test
import (
"context"
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot"
)
const (
@@ -16,22 +16,23 @@ const (
)
var (
fAccessToken string
fSecret string
fWebhookUrl string
fSecret string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_DINGTALK_"
argsPrefix := "CERTIMATE_NOTIFIER_DINGTALKBOT_"
flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "")
flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "")
flag.StringVar(&fSecret, argsPrefix+"SECRET", "", "")
}
/*
Shell command to run this test:
go test -v ./dingtalk_test.go -args \
--CERTIMATE_NOTIFIER_DINGTALK_URL="https://example.com/your-webhook-url"
go test -v ./dingtalkbot_test.go -args \
--CERTIMATE_NOTIFIER_DINGTALKBOT_WEBHOOKURL="https://example.com/your-webhook-url" \
--CERTIMATE_NOTIFIER_DINGTALKBOT_SECRET="your-secret"
*/
func TestNotify(t *testing.T) {
flag.Parse()
@@ -39,13 +40,13 @@ func TestNotify(t *testing.T) {
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken),
fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl),
fmt.Sprintf("SECRET: %v", fSecret),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
AccessToken: fAccessToken,
Secret: fSecret,
WebhookUrl: fWebhookUrl,
Secret: fSecret,
})
if err != nil {
t.Errorf("err: %+v", err)

View File

@@ -0,0 +1,68 @@
package discordbot
import (
"context"
"fmt"
"log/slog"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Discord Bot API Token。
BotToken string `json:"botToken"`
// Discord Channel ID。
ChannelId string `json:"channelId"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
if logger == nil {
n.logger = slog.Default()
} else {
n.logger = logger
}
return n
}
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://discord.com/developers/docs/resources/message#create-message
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bot "+n.config.BotToken).
SetBody(map[string]any{
"content": subject + "\n" + message,
})
resp, err := req.Post(fmt.Sprintf("https://discord.com/api/v9/channels/%s/messages", n.config.ChannelId))
if err != nil {
return nil, fmt.Errorf("discord api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("discord api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -0,0 +1,64 @@
package discordbot_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/discordbot"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fApiToken string
fChannelId string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_DISCORDBOT_"
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", 0, "")
}
/*
Shell command to run this test:
go test -v ./discordbot_test.go -args \
--CERTIMATE_NOTIFIER_DISCORDBOT_APITOKEN="your-bot-token" \
--CERTIMATE_NOTIFIER_DISCORDBOT_CHANNELID="your-channel-id"
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("APITOKEN: %v", fApiToken),
fmt.Sprintf("CHANNELID: %v", fChannelId),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
BotToken: fApiToken,
ChannelId: fChannelId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -17,7 +17,7 @@ const (
var (
fSmtpHost string
fSmtpPort int
fSmtpPort int64
fSmtpTLS bool
fUsername string
fPassword string
@@ -29,7 +29,7 @@ func init() {
argsPrefix := "CERTIMATE_NOTIFIER_EMAIL_"
flag.StringVar(&fSmtpHost, argsPrefix+"SMTPHOST", "", "")
flag.IntVar(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "")
flag.Int64Var(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "")
flag.BoolVar(&fSmtpTLS, argsPrefix+"SMTPTLS", false, "")
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")

View File

@@ -1,20 +1,19 @@
package gotify
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Gotify 服务地址。
Url string `json:"url"`
ServerUrl string `json:"serverUrl"`
// Gotify Token。
Token string `json:"token"`
// Gotify 消息优先级。
@@ -24,7 +23,7 @@ type NotifierConfig struct {
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *http.Client
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -34,10 +33,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: http.DefaultClient,
httpClient: client,
}, nil
}
@@ -51,45 +52,23 @@ 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) {
reqBody := &struct {
Title string `json:"title"`
Message string `json:"message"`
Priority int64 `json:"priority"`
}{
Title: subject,
Message: message,
Priority: n.config.Priority,
}
serverUrl := strings.TrimRight(n.config.ServerUrl, "/")
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("gotify api error: failed to encode message body: %w", err)
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
fmt.Sprintf("%s/message", n.config.Url),
bytes.NewReader(body),
)
if err != nil {
return nil, fmt.Errorf("gotify api error: failed to create new request: %w", err)
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.config.Token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := n.httpClient.Do(req)
// REF: https://gotify.net/api-docs#/message/createMessage
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+n.config.Token).
SetBody(map[string]any{
"title": subject,
"message": message,
"priority": n.config.Priority,
})
resp, err := req.Post(fmt.Sprintf("%s/message", serverUrl))
if err != nil {
return nil, fmt.Errorf("gotify api error: failed to send request: %w", err)
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("gotify api error: failed to read response: %w", err)
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gotify api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result))
} else if resp.IsError() {
return nil, fmt.Errorf("gotify api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil

View File

@@ -48,9 +48,9 @@ func TestNotify(t *testing.T) {
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
Url: fUrl,
Token: fToken,
Priority: fPriority,
ServerUrl: fUrl,
Token: fToken,
Priority: fPriority,
})
if err != nil {
t.Errorf("err: %+v", err)

View File

@@ -1,10 +1,11 @@
package lark
package larkbot
import (
"context"
"fmt"
"log/slog"
"github.com/nikoksr/notify/service/lark"
"github.com/go-lark/lark"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -42,11 +43,17 @@ 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 := lark.NewWebhookService(n.config.WebhookUrl)
err = srv.Send(ctx, subject, message)
bot := lark.NewNotificationBot(n.config.WebhookUrl)
content := lark.NewPostBuilder().
Title(subject).
TextTag(message, 1, false).
Render()
msg := lark.NewMsgBuffer(lark.MsgPost).Post(content)
resp, err := bot.PostNotificationV2(msg.Build())
if err != nil {
return nil, err
return nil, fmt.Errorf("lark api error: %w", err)
} else if resp.Code != 0 {
return nil, fmt.Errorf("lark api error: code='%d', message='%s'", resp.Code, resp.Msg)
}
return &notifier.NotifyResult{}, nil

View File

@@ -1,4 +1,4 @@
package serverchan_test
package larkbot_test
import (
"context"
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot"
)
const (
@@ -18,7 +18,7 @@ const (
var fWebhookUrl string
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_WECOM_"
argsPrefix := "CERTIMATE_NOTIFIER_LARKBOT_"
flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "")
}
@@ -26,8 +26,8 @@ func init() {
/*
Shell command to run this test:
go test -v ./wecom_test.go -args \
--CERTIMATE_NOTIFIER_WECOM_WEBHOOKURL="https://example.com/your-webhook-url" \
go test -v ./larkbot_test.go -args \
--CERTIMATE_NOTIFIER_LARKBOT_WEBHOOKURL="https://example.com/your-webhook-url"
*/
func TestNotify(t *testing.T) {
flag.Parse()

View File

@@ -1,32 +1,31 @@
package mattermost
import (
"bytes"
"context"
"encoding/json"
"io"
"fmt"
"log/slog"
"net/http"
"strings"
"github.com/nikoksr/notify/service/mattermost"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// 服务地址。
// Mattermost 服务地址。
ServerUrl string `json:"serverUrl"`
// 用户名。
// Mattermost 用户名。
Username string `json:"username"`
// 密码。
// Mattermost 密码。
Password string `json:"password"`
// 频道 ID。
// Mattermost 频道 ID。
ChannelId string `json:"channelId"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -36,9 +35,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
@@ -52,17 +54,31 @@ 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 := mattermost.New(strings.TrimRight(n.config.ServerUrl, "/"))
serverUrl := strings.TrimRight(n.config.ServerUrl, "/")
if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil {
return nil, err
// REF: https://developers.mattermost.com/api-documentation/#/operations/Login
loginReq := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"login_id": n.config.Username,
"password": n.config.Password,
})
loginResp, err := loginReq.Post(fmt.Sprintf("%s/api/v4/users/login", serverUrl))
if err != nil {
return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err)
} else if loginResp.IsError() {
return nil, fmt.Errorf("mattermost api error: unexpected status code: %d, resp: %s", loginResp.StatusCode(), loginResp.String())
} else if loginResp.Header().Get("Token") == "" {
return nil, fmt.Errorf("mattermost api error: received empty login token")
}
srv.AddReceivers(n.config.ChannelId)
// 复写消息样式
srv.PreSend(func(req *http.Request) error {
m := map[string]interface{}{
// REF: https://developers.mattermost.com/api-documentation/#/operations/CreatePost
postReq := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+loginResp.Header().Get("Token")).
SetBody(map[string]any{
"channel_id": n.config.ChannelId,
"props": map[string]interface{}{
"attachments": []map[string]interface{}{
@@ -72,20 +88,12 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
},
},
},
}
if body, err := json.Marshal(m); err != nil {
return err
} else {
req.ContentLength = int64(len(body))
req.Body = io.NopCloser(bytes.NewReader(body))
}
return nil
})
if err = srv.Send(ctx, subject, message); err != nil {
return nil, err
})
postResp, err := postReq.Post(fmt.Sprintf("%s/api/v4/posts", serverUrl))
if err != nil {
return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err)
} else if postResp.IsError() {
return nil, fmt.Errorf("mattermost api error: unexpected status code: %d, resp: %s", postResp.StatusCode(), postResp.String())
}
return &notifier.NotifyResult{}, nil

View File

@@ -1,13 +1,11 @@
package pushover
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -22,7 +20,7 @@ type NotifierConfig struct {
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *http.Client
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -32,10 +30,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: http.DefaultClient,
httpClient: client,
}, nil
}
@@ -50,46 +50,20 @@ 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) {
// REF: https://pushover.net/api
reqBody := &struct {
Token string `json:"token"`
User string `json:"user"`
Title string `json:"title"`
Message string `json:"message"`
}{
Token: n.config.Token,
User: n.config.User,
Title: subject,
Message: message,
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("pushover api error: failed to encode message body: %w", err)
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"https://api.pushover.net/1/messages.json",
bytes.NewReader(body),
)
if err != nil {
return nil, fmt.Errorf("pushover api error: failed to create new request: %w", err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := n.httpClient.Do(req)
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,
"message": message,
"token": n.config.Token,
"user": n.config.User,
})
resp, err := req.Post("https://api.pushover.net/1/messages.json")
if err != nil {
return nil, fmt.Errorf("pushover api error: failed to send request: %w", err)
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("pushover api error: failed to read response: %w", err)
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("pushover api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result))
} else if resp.IsError() {
return nil, fmt.Errorf("pushover api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil

View File

@@ -1,13 +1,12 @@
package pushplus
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -20,7 +19,7 @@ type NotifierConfig struct {
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *http.Client
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -30,10 +29,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: http.DefaultClient,
httpClient: client,
}, nil
}
@@ -47,55 +48,30 @@ 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) {
// REF: https://pushplus.plus/doc/guide/api.html
reqBody := &struct {
Token string `json:"token"`
Title string `json:"title"`
Content string `json:"content"`
}{
Token: n.config.Token,
Title: subject,
Content: message,
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("pushplus api error: failed to encode message body: %w", err)
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"https://www.pushplus.plus/send",
bytes.NewReader(body),
)
if err != nil {
return nil, fmt.Errorf("pushplus api error: failed to create new request: %w", err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := n.httpClient.Do(req)
// REF: https://pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,
"content": message,
"token": n.config.Token,
})
resp, err := req.Post("https://www.pushplus.plus/send")
if err != nil {
return nil, fmt.Errorf("pushplus api error: failed to send request: %w", err)
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("pushplus api error: failed to read response: %w", err)
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("pushplus api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result))
} else if resp.IsError() {
return nil, fmt.Errorf("pushplus api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
var errorResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Code int `json:"code"`
Message string `json:"msg"`
}
if err := json.Unmarshal(result, &errorResponse); err != nil {
return nil, fmt.Errorf("pushplus api error: failed to decode response: %w", err)
if err := json.Unmarshal(resp.Body(), &errorResponse); err != nil {
return nil, fmt.Errorf("pushplus api error: failed to unmarshal response: %w", err)
} else if errorResponse.Code != 200 {
return nil, fmt.Errorf("pushplus api error: unexpected response code: %d, msg: %s", errorResponse.Code, errorResponse.Msg)
return nil, fmt.Errorf("pushplus api error: code='%d', message='%s'", errorResponse.Code, errorResponse.Message)
}
return &notifier.NotifyResult{}, nil

View File

@@ -2,22 +2,23 @@ package serverchan
import (
"context"
"fmt"
"log/slog"
"net/http"
notifyHttp "github.com/nikoksr/notify/service/http"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// ServerChan 服务地址。
Url string `json:"url"`
ServerUrl string `json:"serverUrl"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -27,9 +28,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
@@ -43,24 +47,19 @@ 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 := notifyHttp.New()
srv.AddReceivers(&notifyHttp.Webhook{
URL: n.config.Url,
Header: http.Header{},
ContentType: "application/json",
Method: http.MethodPost,
BuildPayload: func(subject, message string) (payload any) {
return map[string]string{
"text": subject,
"desp": message,
}
},
})
err = srv.Send(ctx, subject, message)
// REF: https://sct.ftqq.com/
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"text": subject,
"desp": message,
})
resp, err := req.Post(n.config.ServerUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("serverchan api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("serverchan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil

View File

@@ -39,7 +39,7 @@ func TestNotify(t *testing.T) {
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
Url: fUrl,
ServerUrl: fUrl,
})
if err != nil {
t.Errorf("err: %+v", err)

View File

@@ -0,0 +1,70 @@
package discordbot
import (
"context"
"fmt"
"log/slog"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Slack Bot API Token。
BotToken string `json:"botToken"`
// Slack Channel ID。
ChannelId string `json:"channelId"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
if logger == nil {
n.logger = slog.Default()
} else {
n.logger = logger
}
return n
}
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://docs.slack.dev/messaging/sending-and-scheduling-messages#publishing
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+n.config.BotToken).
SetBody(map[string]any{
"token": n.config.BotToken,
"channel": n.config.ChannelId,
"text": subject + "\n" + message,
})
resp, err := req.Post("https://slack.com/api/chat.postMessage")
if err != nil {
return nil, fmt.Errorf("slack api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("slack api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -0,0 +1,64 @@
package discordbot_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/slackbot"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fApiToken string
fChannelId string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_SLACKBOT_"
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", 0, "")
}
/*
Shell command to run this test:
go test -v ./slackbot_test.go -args \
--CERTIMATE_NOTIFIER_SLACKBOT_APITOKEN="your-bot-token" \
--CERTIMATE_NOTIFIER_SLACKBOT_CHANNELID="your-channel-id"
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("APITOKEN: %v", fApiToken),
fmt.Sprintf("CHANNELID: %v", fChannelId),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
BotToken: fApiToken,
ChannelId: fChannelId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -1,10 +1,11 @@
package telegram
package telegrambot
import (
"context"
"fmt"
"log/slog"
"github.com/nikoksr/notify/service/telegram"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
@@ -17,8 +18,9 @@ type NotifierConfig struct {
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
@@ -28,9 +30,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
@@ -44,16 +49,19 @@ 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, err := telegram.New(n.config.BotToken)
// REF: https://core.telegram.org/bots/api#sendmessage
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"chat_id": n.config.ChatId,
"text": subject + "\n" + message,
})
resp, err := req.Post(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", n.config.BotToken))
if err != nil {
return nil, err
}
srv.AddReceivers(n.config.ChatId)
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
return nil, fmt.Errorf("telegram api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("telegram api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil

Some files were not shown because too many files have changed in this diff Show More