Compare commits

..

293 Commits
v0.3.9 ... main

Author SHA1 Message Date
Fu Diwei
eb77ba5885 bump version to v0.3.19 2025-06-22 21:09:54 +08:00
RHQYZ
34887cba7c Merge pull request #808 from fudiwei/new-repo
move to new repo
2025-06-22 20:50:18 +08:00
PBK Bin
14a3f055ee build(ui): define version constant 2025-06-22 20:50:01 +08:00
Fu Diwei
882bcdc954 feat(ui): improve i18n 2025-06-19 19:44:58 +08:00
Fu Diwei
801050d3ff style: format code 2025-06-19 11:26:37 +08:00
Fu Diwei
f7a7c7f11c Merge branch 'sync-upstream' into new-repo 2025-06-18 23:15:21 +08:00
RHQYZ
2aac79af54 Merge pull request #810 from fudiwei/bugfix
build: fix go cross build
2025-06-18 23:12:52 +08:00
Fu Diwei
813fc562e3 build: fix go cross build 2025-06-18 22:27:15 +08:00
Fu Diwei
86fc556bbe feat(ui): add robots.txt 2025-06-17 22:33:45 +08:00
Fu Diwei
680e6952ba fix(ui): remove unused field in AccessFormAliyunConfig 2025-06-17 22:00:22 +08:00
Fu Diwei
e53b2161d4 Merge branch 'sync-upstream' into new-repo 2025-06-17 21:15:52 +08:00
Fu Diwei
b617b45495 fix: typo module paths 2025-06-17 20:31:56 +08:00
Fu Diwei
583d308459 docs: edit CONTRIBUTING 2025-06-17 20:22:53 +08:00
Fu Diwei
9354c60380 chore: edit LICENSE 2025-06-17 16:38:00 +08:00
Fu Diwei
b8bbbee1e0 chore: change repo 2025-06-17 16:29:08 +08:00
Fu Diwei
205275b52d chore: move '/internal/pkg' to '/pkg' 2025-06-17 16:28:51 +08:00
Fu Diwei
30840bbba5 refactor: modify directory structure 2025-06-17 16:28:34 +08:00
Fu Diwei
299a722aa9 refactor: re-impl sdk3rd 2025-06-17 16:28:08 +08:00
RHQYZ
9421da2cde bump vertion to v0.3.18 2025-06-15 22:40:27 +08:00
Fu Diwei
312047e0ee fix: int64 overflow 2025-06-15 22:40:19 +08:00
Fu Diwei
4ac3618f7e refactor: abstractions 2025-06-15 22:39:39 +08:00
RHQYZ
3fb9b00f66 bump vertion to v0.3.18 2025-06-15 22:28:44 +08:00
RHQYZ
a492fbe18c Merge pull request #799 from fudiwei/bugfix
bugfix
2025-06-15 22:23:24 +08:00
Fu Diwei
0383233315 fix: int64 overflow 2025-06-15 22:21:36 +08:00
Fu Diwei
4752c49fed refactor: modify directory structure 2025-06-15 22:05:43 +08:00
Fu Diwei
8149034bdc refactor: rename utils 2025-06-15 21:51:07 +08:00
Fu Diwei
e7ce12772a refactor: remove zod.trim() validators 2025-06-15 21:40:21 +08:00
Fu Diwei
0434f95a1e fix: tsc build error 2025-06-15 20:51:40 +08:00
Fu Diwei
769b24aa88 refactor: clean code 2025-06-15 20:51:28 +08:00
RHQYZ
94a0292fad Merge pull request #797 from usual2970/hotfix/cer_expire
fix: correct certificate expiration check logic
2025-06-15 20:41:08 +08:00
RHQYZ
410231cd1f Merge pull request #796 from bigsk05/main
fixed 1panel system ssl deploy
2025-06-15 20:41:00 +08:00
Yoan.liu
1d4e048777 refactor code 2025-06-14 21:51:10 +08:00
RHQYZ
c16fe1a807 Merge pull request #791 from usual2970/feat/panic_recover
处理panic避免影响到其它工作流
2025-06-14 21:48:23 +08:00
PBK Bin
e8ed831b28 Merge pull request #789 from PBK-B/bin/fix_remove_email_setting
feat(ui): support deleting email input box auto-complete items #174
2025-06-14 21:48:12 +08:00
RHQYZ
c7c89efbe7 Merge pull request #784 from PBK-B/bin/chore_add_gitkeep
chore: add ui/dist/.gitkeep file
2025-06-14 21:47:57 +08:00
RHQYZ
d7f3d9c512 Merge pull request #783 from fudiwei/feat/providers
new providers
2025-06-14 21:47:37 +08:00
RHQYZ
ce67cc5a39 Merge pull request #782 from fudiwei/bugfix
bugfix
2025-06-14 21:47:26 +08:00
Fu Diwei
945d0da36f update README 2025-06-14 21:41:58 +08:00
Fu Diwei
898311c6e5 feat: new deployment provider: ctcccloud elb 2025-06-14 21:41:58 +08:00
Fu Diwei
5d6bc03f21 feat: new deployment provider: ctcccloud lvdn 2025-06-14 21:41:58 +08:00
Fu Diwei
018743299b feat: new deployment provider: ctcccloud cms 2025-06-14 21:41:58 +08:00
Fu Diwei
18e7238067 feat: new deployment provider: ctcccloud ao 2025-06-14 21:41:58 +08:00
Fu Diwei
b7d1ff8960 feat: new deployment provider: ctcccloud icdn 2025-06-14 21:41:58 +08:00
Fu Diwei
0d44373de6 feat: new deployment provider: ctcccloud cdn 2025-06-14 21:41:52 +08:00
Yoan.liu
3d680e50e2 fix: correct certificate expiration check logic 2025-06-14 21:16:58 +08:00
Bigsk
9542079e20 fixed 1panel system ssl deploy 2025-06-13 23:43:24 +08:00
Yoan.liu
081e83e0bf recover from panics 2025-06-13 13:37:14 +08:00
Fu Diwei
9c8ab98efb feat: new acme dns-01 provider: statecloud smartdns 2025-06-12 23:21:03 +08:00
Fu Diwei
bf26db77cb fix: #786 2025-06-12 14:13:43 +08:00
Fu Diwei
08ea915d24 fix: #786 2025-06-12 14:03:29 +08:00
Fu Diwei
fb62f1e105 feat: ipv6 connect 2025-06-12 13:39:01 +08:00
PBK-B
261b3a8546 chore: add ui/dist/.gitkeep file 2025-06-11 23:25:14 +08:00
Fu Diwei
563adbec2a feat: allow configuring smtp sender display name 2025-06-11 23:10:57 +08:00
Fu Diwei
c6dfe11bdb feat: add preset scripts for qnap on deployment to ssh 2025-06-11 23:09:56 +08:00
Fu Diwei
e4bfa90a77 feat: new deployment provider: apisix 2025-06-11 22:17:07 +08:00
Fu Diwei
b833d09466 feat: new deployment provider: tencentcloud gaap 2025-06-11 20:53:19 +08:00
Fu Diwei
3ec0ba7052 fix: #781 2025-06-11 19:58:27 +08:00
Fu Diwei
57eb66b889 chore: remove unused files 2025-06-10 21:52:56 +08:00
RHQYZ
a048eb95a9 Merge pull request #779 from fudiwei/main
build: keep release files as before
2025-06-10 21:41:10 +08:00
Fu Diwei
23e58b914e build: keep release files as before 2025-06-10 21:38:47 +08:00
Fu Diwei
1170f635fd build: keep release files as before 2025-06-10 21:10:27 +08:00
Yoan.liu
553aceac44 retain executable permission 2025-06-10 11:08:29 +08:00
Yoan.liu
e24de70c02 split release into multiple jobs 2025-06-09 23:40:24 +08:00
Yoan.liu
bba2b25757 split release into multiple jobs 2025-06-09 23:11:50 +08:00
Yoan.liu
4132ec3617 split release into multiple jobs 2025-06-09 22:40:43 +08:00
Fu Diwei
9b3c7e16c0 fix: #769 2025-06-09 21:34:03 +08:00
Fu Diwei
0448538073 feat: bump version to v0.3.17 2025-06-09 21:16:58 +08:00
RHQYZ
80157496d5 Merge pull request #774 from lfffffy/main
fix(docs): 修复腾讯云文档链接使用错误的 com.cn 域名
2025-06-09 21:09:39 +08:00
RHQYZ
62e2ed2fb8 Merge pull request #768 from fudiwei/main
enhance & bugfix
2025-06-09 21:09:28 +08:00
Fu Diwei
a750592eb5 feat: duplicate workflow 2025-06-09 21:07:52 +08:00
Fu Diwei
5e6d729631 fix: #769 2025-06-09 20:44:48 +08:00
Fu Diwei
24fe824757 feat: allow skip notify nodes when all previous nodes were skipped 2025-06-09 20:40:06 +08:00
Fu Diwei
84a3f3346a fix: #769 2025-06-09 19:11:04 +08:00
Fu Diwei
bd26dfecb8 feat: improve workflow log 2025-06-09 10:06:41 +08:00
leun
43182de732 fix(docs): 修复腾讯云文档链接使用错误的 com.cn 域名
将文档链接中的 cloud.tencent.com.cn 统一替换为正确的 cloud.tencent.com,
以避免链接跳转失败或文档加载异常的问题。
2025-06-08 15:00:28 +08:00
Fu Diwei
d58109f4be feat: support wildcard domains on deployment to wangsu cdn 2025-06-05 20:28:50 +08:00
Fu Diwei
59935df6b1 fix: #766 2025-06-05 20:24:43 +08:00
Fu Diwei
252da5d7e1 refactor(ui): clean code 2025-06-05 10:24:39 +08:00
RHQYZ
c3e7590f53 bump version to v0.3.16 2025-06-03 23:50:48 +08:00
RHQYZ
65cd1dc850 Merge pull request #761 from fudiwei/feat/providers
new providers
2025-06-03 23:48:27 +08:00
RHQYZ
2203bb5268 Merge pull request #760 from fudiwei/bugfix
bugfix: could not add branches in workflows
2025-06-03 23:48:13 +08:00
Fu Diwei
8e5c36968a chore: improve error 2025-06-03 23:45:00 +08:00
Fu Diwei
9ad0e6fb57 feat: support ssh challenge-response 2025-06-03 23:39:48 +08:00
Fu Diwei
7d55383cf7 feat: new deployment provider: aws iam 2025-06-03 23:39:48 +08:00
Fu Diwei
6dc65eea2f feat: new acme dns-01 provider: ucloud udnr 2025-06-03 23:39:48 +08:00
Fu Diwei
7210f63884 feat: new acme dns-01 provider: constellix 2025-06-03 23:39:42 +08:00
Fu Diwei
f94db675fb fix: could not add branches in workflows 2025-06-03 22:37:03 +08:00
RHQYZ
e6cf4d3e07 bump version to v0.3.15 2025-06-02 23:10:34 +08:00
RHQYZ
cc5098c4bc Merge pull request #755 from fudiwei/main
Support duplicating workflow nodes or branches
2025-06-02 23:09:14 +08:00
Fu Diwei
025e606db4 feat(ui): duplicate workflow node 2025-06-02 23:06:18 +08:00
RHQYZ
d3e8bacd58 Merge pull request #742 from tailorvii/wangsu
fix: wangsu get certificate list api error #741
2025-06-01 23:05:10 +08:00
RHQYZ
308b21bb33 Merge pull request #734 from fudiwei/bugfix
bugfix
2025-06-01 23:04:53 +08:00
RHQYZ
262c1d7fcb Merge pull request #735 from fudiwei/feat/providers
enhance providers
2025-06-01 23:04:42 +08:00
RHQYZ
722c3a0e83 Merge pull request #712 from usual2970/feat/condition
workflow conditional branch & monitoring node
2025-06-01 23:04:31 +08:00
Fu Diwei
f885b49daf feat: add certtest workflow template 2025-06-01 22:59:24 +08:00
Fu Diwei
6731c465e7 refactor: workflow condition node
refactor: workflow condition node
2025-05-31 17:30:37 +08:00
Fu Diwei
28811c46d8 fix: #746 2025-05-31 16:29:17 +08:00
tailor
599cf17c9e fix: wangsu get certificate list api error 2025-05-29 11:24:30 +08:00
Fu Diwei
f0af36b59e refactor: clean code 2025-05-28 22:43:18 +08:00
Fu Diwei
e73e2739c1 feat: use discard handler as default providers logger 2025-05-28 22:21:41 +08:00
Fu Diwei
efdeacf01a feat: add preset webhook template for serverchan3 2025-05-28 21:39:02 +08:00
Fu Diwei
3a829ad53b refactor: workflow monitor(aka inspect) node 2025-05-28 21:05:56 +08:00
Yoan.liu
605de595b1 remove upx compression 2025-05-28 20:34:18 +08:00
Fu Diwei
daf22b7f15 feat: initialize aliyun fc ssl protocol 2025-05-28 16:44:11 +08:00
Fu Diwei
0e8ebaa885 fix: #732 2025-05-28 14:51:18 +08:00
Fu Diwei
829fa29cf1 feat: add user-agent http header for thirdparty sdks 2025-05-28 10:46:02 +08:00
Fu Diwei
ddb46f9dda refactor: clean code 2025-05-28 10:17:33 +08:00
Fu Diwei
df1f216b5b feat: support configuring aliyun resource group id 2025-05-27 21:19:06 +08:00
Fu Diwei
b8b94dfd77 feat: support configuring huaweicloud enterprise project id 2025-05-27 21:19:02 +08:00
Fu Diwei
4489096e57 Merge branch 'main' into feat/condition 2025-05-27 05:36:42 +08:00
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
Yoan.liu
9cdc59b272 refactor code 2025-05-22 17:09:14 +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
Yoan.liu
75326b1ddd refactor code 2025-05-21 15:59:02 +08:00
Yoan.liu
7d8dd523a2 Merge branch 'main' into feat/condition 2025-05-21 13:51:23 +08:00
Yoan.liu
993ca36755 add certificate mornitoring node 2025-05-21 13:48:54 +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
Yoan.liu
faad7cb6d7 improve condition evaluate 2025-05-20 22:54:41 +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
Yoan.liu
97d692910b expression evaluate 2025-05-20 18:09:42 +08:00
Yoan.liu
b546cf3ad0 multi language support 2025-05-20 14:55:48 +08:00
Yoan.liu
6353f0139b improve variable types 2025-05-20 11:01:04 +08:00
Fu Diwei
c9e6bd0c2f feat: new deployment provider: lecdn 2025-05-19 22:51:17 +08:00
Yoan.liu
1e67e9333e condition render 2025-05-19 21:59:37 +08:00
Yoan.liu
6f054ee594 update readme 2025-05-19 18:16:21 +08:00
Yoan.liu
05d43f38ce improve previous variables 2025-05-19 18:15:04 +08:00
Yoan.liu
b8ab077b57 improve ui 2025-05-19 17:41:39 +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
RHQYZ
12d0b66c61 Merge pull request #691 from fudiwei/main
refactor
2025-05-16 11:15:38 +08:00
Fu Diwei
f3bbb9e8b2 refactor: optimize third-party sdks 2025-05-16 11:10:43 +08:00
RHQYZ
dcd646b465 Merge branch 'usual2970:main' into main 2025-05-16 02:52:51 +08:00
耗子
bd4aa4806f feat: backend support for ratpanel 2025-05-16 02:33:46 +08:00
Fu Diwei
b122bbced9 build: config goreleaser 2025-05-16 02:26:38 +08:00
Fu Diwei
5edae845cb build: config goreleaser 2025-05-16 02:03:49 +08:00
Fu Diwei
b7af3c10e4 build: config goreleaser 2025-05-16 01:58:53 +08:00
Fu Diwei
2453048288 build: config goreleaser 2025-05-16 01:50:43 +08:00
Fu Diwei
221b6a6fc6 feat(ui): improve i18n 2025-05-16 01:34:05 +08:00
Fu Diwei
851ad70a6c bump version to v0.3.12 2025-05-15 23:52:21 +08:00
Fu Diwei
1844e9a9db update README 2025-05-15 23:41:25 +08:00
RHQYZ
04e9f4e909 Merge pull request #689 from fudiwei/feat/providers
new providers
2025-05-15 23:35:48 +08:00
Fu Diwei
e6e2587bfc feat: new deployment provider: netlify site 2025-05-15 23:25:48 +08:00
Fu Diwei
70bd2f0581 feat: new acme dns-01 provider: netlify 2025-05-15 23:25:48 +08:00
Fu Diwei
9e08cfd1d1 feat: support replacing old certificate on deployment to aws acm 2025-05-15 23:25:48 +08:00
Fu Diwei
cd93a2d72c feat: new acme dns-01 provider: netcup 2025-05-15 23:25:42 +08:00
RHQYZ
11a4d4f55c Merge pull request #684 from fudiwei/main
enhance & bugfix
2025-05-15 21:16:17 +08:00
RHQYZ
f33570d514 Merge branch 'usual2970:main' into main 2025-05-15 21:15:49 +08:00
Fu Diwei
1ee3b64a19 feat: config goedge api user role 2025-05-15 21:05:22 +08:00
Fu Diwei
a3a56f3346 feat: add preset scripts for synologydsm and fnos on deployment to ssh 2025-05-15 20:52:43 +08:00
Fu Diwei
564ed8f0d3 fix: #686 2025-05-15 17:52:24 +08:00
Fu Diwei
268ec4bd7f feat(ui): browser happy detecting 2025-05-15 02:20:47 +08:00
Fu Diwei
e55e6cc512 fix(ui): tsc build error 2025-05-15 01:57:00 +08:00
Fu Diwei
6c6bb78568 feat: deploy server certificate or intermedia certificate 2025-05-15 01:40:37 +08:00
Fu Diwei
178c62512d fix(ui): fix incorrect webhook guide 2025-05-15 01:16:29 +08:00
Fu Diwei
9eaf9fd933 feat(ui): CodeInput 2025-05-15 00:58:02 +08:00
Fu Diwei
04abf9dd76 feat(ui): TextFileInput 2025-05-14 00:42:59 +08:00
Fu Diwei
355059df3c fix(ui): disallow to create access of builtin providers 2025-05-13 22:32:33 +08:00
Fu Diwei
4a6c32877f chore: github issue templates 2025-05-13 22:00:15 +08:00
Fu Diwei
258f6b5001 fix: resolve #681 2025-05-13 21:33:59 +08:00
Fu Diwei
78d9501fce fix: fix typo 2025-05-13 21:22:01 +08:00
RHQYZ
15975bb92c Merge branch 'usual2970:main' into main 2025-05-13 21:19:13 +08:00
Fu Diwei
fef8c3cb1a build: set go version to 1.24 2025-05-13 01:33:39 +08:00
RHQYZ
4b226d7730 Merge branch 'usual2970:main' into main 2025-05-13 01:00:07 +08:00
RHQYZ
41abc8cab8 bump version to v0.3.11 2025-05-13 00:54:08 +08:00
Fu Diwei
4d3b3834f5 fix(ui): tsc build error 2025-05-13 00:52:41 +08:00
Fu Diwei
9b1ba574ff fix(ui): scale origin 2025-05-13 00:52:41 +08:00
Fu Diwei
ee7d950c15 feat(ui): max content height on AccessEditModal 2025-05-13 00:52:41 +08:00
Fu Diwei
8ca41f18be feat(ui): show search on AccessSelect 2025-05-13 00:52:41 +08:00
Fu Diwei
9d522bd9ef feat(ui): AccessProviderPicker 2025-05-13 00:52:41 +08:00
Fu Diwei
f9633616e2 feat: support replacing old certificate on deployment to gcore cdn 2025-05-13 00:52:41 +08:00
Fu Diwei
9a0dd53cd7 chore(deps): upgrade gomod dependencies 2025-05-13 00:52:41 +08:00
Fu Diwei
6bbcec6163 chore(deps): upgrade npm dependencies 2025-05-13 00:52:41 +08:00
Fu Diwei
fdee69bdaf feat(ui): move settings page entry from topbar to sider-menu 2025-05-13 00:52:41 +08:00
Fu Diwei
2ec4adf7d5 fix(ui): tsc build error 2025-05-13 00:52:05 +08:00
Fu Diwei
709684d00f fix(ui): scale origin 2025-05-13 00:43:58 +08:00
Fu Diwei
989fd1ec6d feat(ui): max content height on AccessEditModal 2025-05-13 00:39:18 +08:00
Fu Diwei
7d57c5abc0 feat(ui): show search on AccessSelect 2025-05-13 00:33:48 +08:00
Fu Diwei
0c42bb845d feat(ui): AccessProviderPicker 2025-05-13 00:28:58 +08:00
Fu Diwei
07037e8d49 feat: support replacing old certificate on deployment to gcore cdn 2025-05-12 23:19:13 +08:00
Fu Diwei
a1fc3841df chore(deps): upgrade gomod dependencies 2025-05-12 23:00:39 +08:00
Fu Diwei
1fee156457 chore(deps): upgrade npm dependencies 2025-05-12 22:12:59 +08:00
Fu Diwei
82bdccf7e7 feat(ui): move settings page entry from topbar to sider-menu 2025-05-12 21:34:00 +08:00
Fu Diwei
7936c34472 fix #672 2025-05-12 20:19:00 +08:00
RHQYZ
365fd0bd5b Merge pull request #670 from fudiwei/feat/providers
new providers
2025-05-09 21:52:26 +08:00
Fu Diwei
f63ee41405 feat: new deployment provider: proxmoxve 2025-05-09 20:53:25 +08:00
Fu Diwei
26359b9d16 feat: allow insecure connections in cdnfly, goedge, powerdns 2025-05-09 16:35:58 +08:00
RHQYZ
5abdb577fb Merge pull request #666 from fudiwei/feat/providers
new providers
2025-05-09 11:00:32 +08:00
RHQYZ
c67c4741e5 bump version to v0.3.10 2025-05-08 21:04:14 +08:00
RHQYZ
60bc4be9ea Merge pull request #664 from fudiwei/bugfix
bugfix
2025-05-08 21:02:18 +08:00
RHQYZ
b83f87b6c8 Merge pull request #665 from fudiwei/main
Support configuring dns propagation waiting time
2025-05-08 21:01:50 +08:00
Fu Diwei
0a73ba1a53 feat: add preset scripts 2025-05-08 20:43:09 +08:00
Fu Diwei
a4d397f24b fix: fix typo 2025-05-08 15:36:51 +08:00
Fu Diwei
809f231981 feat: set the default max workers to the number of available CPU cores 2025-05-07 22:15:11 +08:00
Fu Diwei
1499c637ee feat: new deployment provider: goedge 2025-05-07 22:06:43 +08:00
Fu Diwei
e5805a028b feat: new acme dns-01 provider: aliyun esa 2025-05-07 22:06:43 +08:00
Fu Diwei
5cb0463cf6 feat: set the default max workers to the number of available CPU cores 2025-05-07 22:06:43 +08:00
Fu Diwei
12c208cad4 feat: new deployment provider: aliyun ddoscoo 2025-05-07 22:06:31 +08:00
Fu Diwei
1946a4e68a feat: add dns propagation wait configuration to application node in workflows 2025-05-07 11:56:35 +08:00
Fu Diwei
dfed0af053 fix: #658 2025-05-07 10:12:13 +08:00
Yoan.liu
fc064aa9f8 Merge pull request #643 from fudiwei/main
Support configuring independent notification channel for each workflow
2025-04-30 21:37:03 +08:00
Fu Diwei
edaf08361f refactor: clean code 2025-04-28 12:09:28 +08:00
Fu Diwei
92c93822b9 feat: webhook general template 2025-04-28 10:52:58 +08:00
Fu Diwei
18a19096d3 feat: add dingtalk, lark, and wecom bot webhook 2025-04-27 23:44:01 +08:00
Fu Diwei
7e707cd973 feat: webhook preset template data 2025-04-27 22:44:10 +08:00
Fu Diwei
d33b8caf14 feat: support overwriting the default webhook data of deployers and notifiers 2025-04-27 22:06:04 +08:00
Fu Diwei
e533f9407f feat: reserve accesses for ca or notification 2025-04-27 11:41:09 +08:00
Fu Diwei
193a19b79c feat: support more content-type in webhook 2025-04-27 00:01:00 +08:00
Fu Diwei
609a252ee0 refactor: clean code 2025-04-26 21:27:53 +08:00
Fu Diwei
3be70c3696 feat: support configuring method and headers in webhook 2025-04-26 21:17:09 +08:00
Fu Diwei
3c2fbd720f feat: support overwriting the default config of notifiers 2025-04-25 11:51:19 +08:00
Fu Diwei
11b413d0dc feat: adapt email, mattermost, telegram, and webhook to new notifier module 2025-04-24 23:37:27 +08:00
Fu Diwei
a117fd7d93 refactor: clean code 2025-04-24 23:14:17 +08:00
Fu Diwei
794695c313 refactor: clean code 2025-04-24 21:04:55 +08:00
Fu Diwei
7478dd7f47 feat: deprecate old notification module and introduce new notifier module 2025-04-24 20:27:20 +08:00
Fu Diwei
2d17501072 refactor: clean code 2025-04-24 10:46:16 +08:00
Fu Diwei
034bb71b10 feat(ui): show ca provider global settings button only when not specified ca provider 2025-04-24 09:13:49 +08:00
Fu Diwei
97f102533c feat: enhance context cancellation handling 2025-04-23 19:32:21 +08:00
RHQYZ
a90b6a8589 Merge pull request #640 from fudiwei/main
field type conversion
2025-04-22 22:28:45 +08:00
Fu Diwei
e8f6c665f9 Merge branch 'upstream' 2025-04-22 22:16:01 +08:00
Fu Diwei
6dac89e9a1 refactor: remove pkg/errors 2025-04-22 22:12:58 +08:00
Fu Diwei
4f512a6cdd refactor: clean code 2025-04-22 21:51:36 +08:00
Fu Diwei
94f162c189 Merge branch 'sync-upstream' 2025-04-22 21:27:37 +08:00
Fu Diwei
f5807d215f style: format code 2025-04-22 21:24:48 +08:00
Fu Diwei
3189e65bad refactor: clean code 2025-04-22 21:18:16 +08:00
Fu Diwei
3febc53061 feat: change field provider's type of table access 2025-04-22 18:34:51 +08:00
Fu Diwei
efd8135341 chore(deps): upgrade gomod dependencies 2025-04-22 16:25:18 +08:00
Fu Diwei
56405eff6c chore(deps): upgrade npm dependencies 2025-04-22 16:25:08 +08:00
Fu Diwei
e0fad9d9a9 Merge branch 'main' of https://github.com/fudiwei/certimate 2025-04-22 10:27:08 +08:00
RHQYZ
363e23dd13 Merge branch 'usual2970:main' into main 2025-04-13 11:57:30 +08:00
RHQYZ
abe755cb69 Merge branch 'usual2970:main' into main 2025-04-09 22:56:26 +08:00
RHQYZ
cedbaf70c0 Merge branch 'usual2970:main' into main 2025-04-07 12:42:13 +08:00
Fu Diwei
2899aa1b19 feat: set placeholder values in preset scripts 2025-04-03 21:12:21 +08:00
Fu Diwei
ecff16c89d chore: improve error messages 2025-04-03 21:03:43 +08:00
1050 changed files with 44280 additions and 16583 deletions

View File

@@ -10,5 +10,7 @@ trim_trailing_whitespace = true
insert_final_newline = true
[*.go]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = tab

View File

@@ -10,21 +10,23 @@ body:
## Welcome!
**在提交 Issue 之前,请确认以下事项**
1. 我**确认**已尝试过使用当前最新版本,并能复现问题。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/usual2970/certimate/issues)(包括已关闭的),没有类似的问题。
1. 我**确认**已尝试过使用当前最新版本,并能复现问题。由于开发者精力有限,非当前最新版本的问题将被直接关闭,感谢理解。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/certimate-go/certimate/issues)(包括已关闭的),没有类似的问题。
3. 我**确认**已阅读过[文档](https://docs.certimate.me/),没有类似的问题。
4. 请**务必**按照模板规范详细描述问题,否则 Issue 将会被直接关闭。
5. 请保持每个 Issue 只包含一个缺陷报告。如果有多个缺陷,请分别提交 Issue。
**Before you submit the issue, please make sure of the following checklist**:
1. Yes, I'm using the latest release and can reproduce the issue.
2. Yes, I've searched for [existing issues](https://github.com/usual2970/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
1. Yes, I'm using the latest release and can reproduce the issue. Issues that are not in the latest version will be closed directly.
2. Yes, I've searched for [existing issues](https://github.com/certimate-go/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar.
4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly.
5. Please limit one report per issue.
- type: input
attributes:
label: 软件版本 / Release Version
description: 请提供 Certimate 的具体版本。 / Please provide the specific version of Certimate.
description: 请提供 Certimate 的具体版本(请不要填写 `latest` 之类的无效版本号)。 / Please provide the specific version of Certimate.
placeholder: (e.g. v1.0.0)
validations:
required: true
@@ -70,8 +72,9 @@ body:
validations:
required: false
- type: markdown
- type: checkboxes
attributes:
value: |
请保持每个 Issue 只包含一个缺陷报告。
Please limit one report per issue.
label: 贡献 / Contribution
options:
- label: 我乐意为此贡献 PR / I am interested in contributing a PR for this!
required: false

View File

@@ -11,15 +11,17 @@ body:
**在提交 Issue 之前,请确认以下事项**
1. 我**确认**是基于当前最新大版本而提出的新功能请求或改进意见。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/usual2970/certimate/issues)(包括已关闭的),没有类似的问题。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/certimate-go/certimate/issues)(包括已关闭的),没有类似的问题。
3. 我**确认**已阅读过[文档](https://docs.certimate.me/),没有类似的问题。
4. 请**务必**按照模板规范详细描述问题,否则 Issue 将会被直接关闭。
5. 请保持每个 Issue 只包含一个功能请求。如果有多个需求,请分别提交 Issue。
**Before you submit the issue, please make sure of the following checklist**:
1. Yes, I'm using the latest release.
2. Yes, I've searched for [existing issues](https://github.com/usual2970/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
2. Yes, I've searched for [existing issues](https://github.com/certimate-go/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar.
4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly.
5. Please limit one request per issue.
- type: textarea
attributes:
@@ -38,7 +40,7 @@ body:
- type: textarea
attributes:
label: 其他 / Miscellaneous
description: 在此处添加关于该 Issue 的任何其他信息。 / Add any other context about the problem here.
description: 在此处添加关于该 Issue 的任何其他信息(新增提供商请求请补充 API 文档链接等资料)。 / Add any other context about the problem here.
validations:
required: false
@@ -46,11 +48,5 @@ body:
attributes:
label: 贡献 / Contribution
options:
- label: 我乐意为此贡献代码 / I am interested in contributing to this feature!
- label: 我乐意为此贡献 PR / I am interested in contributing a PR for this!
required: false
- type: markdown
attributes:
value: |
请保持每个 Issue 只包含一个功能请求。
Please limit one request per issue.

View File

@@ -3,26 +3,28 @@ description: "遇到了困难需要求助? / Have problem in use and need help
title: "简要描述你遇到的问题"
body:
- type: markdown
attributes:
attributes:
value: |
## Welcome!
**在提交 Issue 之前,请确认以下事项**
1. 我**确认**正在使用的是当前最新版本。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/usual2970/certimate/issues)(包括已关闭的),没有类似的问题。
2. 我**确认**已搜索过[已有的 Issues](https://github.com/certimate-go/certimate/issues)(包括已关闭的),没有类似的问题。
3. 我**确认**已阅读过[文档](https://docs.certimate.me/),没有类似的问题。
4. 请**务必**按照模板规范详细描述问题,否则 Issue 将会被直接关闭。
5. 请保持每个 Issue 只包含一个问题求助。如果有多个问题,请分别提交 Issue。
**Before you submit the issue, please make sure of the following checklist**:
1. Yes, I'm using the latest release.
2. Yes, I've searched for [existing issues](https://github.com/usual2970/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
2. Yes, I've searched for [existing issues](https://github.com/certimate-go/certimate/issues) (including closed ones) on GitHub and didn't find any similar.
3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar.
4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly.
5. Please limit one question per issue.
- type: input
attributes:
label: 软件版本 / Release Version
description: 请提供 Certimate 的具体版本。 / Please provide the specific version of Certimate.
description: 请提供 Certimate 的具体版本(请不要填写 `latest` 之类的无效版本号)。 / Please provide the specific version of Certimate.
placeholder: (e.g. v1.0.0)
validations:
required: true
@@ -40,9 +42,3 @@ body:
description: 在此处添加关于该问题的任何其他信息。 / Add any other context about the problem here.
validations:
required: false
- type: markdown
attributes:
value: |
请保持每个 Issue 只包含一个问题求助。
Please limit one question per issue.

View File

@@ -32,8 +32,8 @@ jobs:
uses: docker/metadata-action@v5
with:
images: |
usual2970/certimate
registry.cn-shanghai.aliyuncs.com/usual2970/certimate
certimate/certimate
registry.cn-shanghai.aliyuncs.com/certimate/certimate
tags: |
type=ref,event=branch
type=ref,event=pr
@@ -61,4 +61,3 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -31,8 +31,8 @@ jobs:
uses: docker/metadata-action@v5
with:
images: |
usual2970/certimate
registry.cn-shanghai.aliyuncs.com/usual2970/certimate
certimate/certimate
registry.cn-shanghai.aliyuncs.com/certimate/certimate
tags: |
type=ref,event=tag,pattern={{version}}
flavor: |

View File

@@ -1,4 +1,4 @@
name: Base Build
name: Release
on:
push:
@@ -6,7 +6,7 @@ on:
- "v[0-9]*"
jobs:
goreleaser:
prepare-ui:
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -19,19 +19,190 @@ jobs:
with:
node-version: 20.11.0
- name: Build WebUI
run: |
npm --prefix=./ui ci
npm --prefix=./ui run build
- name: Upload UI build artifacts
uses: actions/upload-artifact@v4
with:
name: ui-build
path: ./ui/dist
retention-days: 1
build-linux:
needs: prepare-ui
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ">=1.23.0"
go-version-file: "go.mod"
- name: Build WebUI
run: npm --prefix=./ui ci && npm --prefix=./ui run build
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
- name: Download UI build artifacts
uses: actions/download-artifact@v4
with:
distribution: goreleaser
version: latest
args: release --clean
name: ui-build
path: ./ui/dist
- name: Build Linux binaries
env:
CGO_ENABLED: 0
GOOS: linux
run: |
mkdir -p dist/linux
for ARCH in amd64 arm64 armv7; do
if [ "$ARCH" == "armv7" ]; then
go env -w GOARCH=arm
go env -w GOARM=7
else
go env -w GOARCH=$ARCH
go env -u GOARM
fi
go build -ldflags="-s -w -X github.com/certimate-go/certimate.Version=${GITHUB_REF#refs/tags/}" -o dist/linux/certimate_${GITHUB_REF#refs/tags/}_linux_$ARCH
done
- name: Upload Linux binaries
uses: actions/upload-artifact@v4
with:
name: linux-binaries
path: dist/linux/
retention-days: 1
build-macos:
needs: prepare-ui
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Download UI build artifacts
uses: actions/download-artifact@v4
with:
name: ui-build
path: ./ui/dist
- name: Build macOS binaries
env:
CGO_ENABLED: 0
GOOS: darwin
run: |
mkdir -p dist/darwin
for ARCH in amd64 arm64; do
go env -w GOARCH=$ARCH
go build -ldflags="-s -w -X github.com/certimate-go/certimate.Version=${GITHUB_REF#refs/tags/}" -o dist/darwin/certimate_${GITHUB_REF#refs/tags/}_darwin_$ARCH
done
- name: Upload macOS binaries
uses: actions/upload-artifact@v4
with:
name: macos-binaries
path: dist/darwin/
retention-days: 1
build-windows:
needs: prepare-ui
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Download UI build artifacts
uses: actions/download-artifact@v4
with:
name: ui-build
path: ./ui/dist
- name: Build Windows binaries
env:
CGO_ENABLED: 0
GOOS: windows
run: |
mkdir -p dist/windows
for ARCH in amd64 arm64; do
go env -w GOARCH=$ARCH
go build -ldflags="-s -w -X github.com/certimate-go/certimate.Version=${GITHUB_REF#refs/tags/}" -o dist/windows/certimate_${GITHUB_REF#refs/tags/}_windows_$ARCH.exe
done
- name: Upload Windows binaries
uses: actions/upload-artifact@v4
with:
name: windows-binaries
path: dist/windows/
retention-days: 1
create-release:
needs: [build-linux, build-macos, build-windows]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all binaries
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Prepare release assets
run: |
mkdir -p dist
cp -r artifacts/linux-binaries/* dist/
cp -r artifacts/macos-binaries/* dist/
cp -r artifacts/windows-binaries/* dist/
find dist -type f -not -name "*.exe" -exec chmod +x {} \;
cd dist
for bin in certimate_*; do
if [[ "$bin" == *".exe" ]]; then
entrypoint="certimate.exe"
else
entrypoint="certimate"
fi
tmpdir=$(mktemp -d)
cp "$bin" "${tmpdir}/${entrypoint}"
cp ../LICENSE ../README.md ../CHANGELOG.md "$tmpdir"
if [[ "$bin" == *".exe" ]]; then
zip -j "${bin%.exe}.zip" "$tmpdir"/*
else
zip -j -X "${bin}.zip" "$tmpdir"/*
fi
rm -rf "$tmpdir"
done
sha256sum *.zip > checksums.txt
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.zip
dist/checksums.txt
draft: true
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

View File

@@ -11,7 +11,7 @@ builds:
main: ./
binary: certimate
ldflags:
- -s -w -X github.com/usual2970/certimate.Version={{ .Version }}
- -s -w -X github.com/certimate-go/certimate.Version={{ .Version }}
env:
- CGO_ENABLED=0
goos:
@@ -30,17 +30,20 @@ builds:
- goos: darwin
goarch: arm
# upx:
# - enabled: true
release:
draft: true
archives:
- id: archive_noncgo
builds: [build_noncgo]
format: zip
format: "zip"
files:
- CHANGELOG.md
- LICENSE.md
- LICENSE
- README.md
- CHANGELOG.md
checksum:
name_template: "checksums.txt"

View File

@@ -1 +1 @@
A full changelog of past releases is available on [GitHub Releases](https://github.com/usual2970/certimate/releases) page.
A full changelog of past releases is available on [GitHub Releases](https://github.com/certimate-go/certimate/releases) page.

View File

@@ -1,76 +1,106 @@
# 向 Certimate 贡献代码
# 贡献指南
感谢你抽出时间来改进 Certimate以下是向 Certimate 主仓库提交 PRPull Request时的操作指南。
非常感谢你抽出时间来帮助改进 Certimate以下是向 Certimate 提交 Pull Request 时的操作指南。
- [向 Certimate 贡献代码](#向-certimate-贡献代码)
- [前提条件](#前提条件)
- [修改 Go 代码](#修改-go-代码)
- [修改管理页面 (Admin UI)](#修改管理页面-admin-ui)
我们需要保持敏捷和快速迭代,同时也希望确保贡献者能获得尽可能流畅的参与体验。这份贡献指南旨在帮助你熟悉代码库和我们的工作方式,让你可以尽快进入有趣的开发环节。
## 前提条件
索引:
- Go 1.22+ (用于修改 Go 代码)
- Node 20+ (用于修改 UI)
- [开发](#开发)
- [要求](#要求)
- [后端代码](#后端代码)
- [前端代码](#前端代码)
- [提交 PR](#提交-pr)
- [提交流程](#提交流程)
- [获取帮助](#获取帮助)
如果还没有这样做,你可以 fork Certimate 的主仓库,并克隆到本地以便进行修改:
---
```bash
git clone https://github.com/your_username/certimate.git
```
## 开发
> **重要提示:**
> 建议为每个 Bug 修复或新功能创建一个从 `main` 分支派生的新分支。如果你计划提交多个 PR请保持不同的改动在独立分支中以便更容易进行代码审查并最终合并。
> 保持一个 PR 只实现一个功能。
### 要求
## 修改 Go 代码
- Go 1.24+(用于修改后端代码
- Node.js 22.0+(用于修改前端代码)
假设你已经对 Certimate 的 Go 代码做了一些修改,现在你想要运行它:
### 后端代码
1. 进入根目录
2. 运行以下命令启动服务:
Certimate 的后端代码是使用 Golang 开发的,是一个基于 [Pocketbase](https://github.com/pocketbase/pocketbase) 构建的单体应用。
假设你已经对 Certimate 的后端代码做出了一些修改,现在你想要运行它,请遵循以下步骤:
1. 进入根目录;
2. 安装依赖:
```bash
go mod vendor
```
3. 启动本地开发服务:
```bash
go run main.go serve
```
这将启动一个 Web 服务器,默认运行在 `http://localhost:8090`,并使用来自 `ui/dist` 的预构建管理页面。
这将启动一个 Web 服务器,默认运行在 `http://localhost:8090`,并使用来自 `/ui/dist` 的预构建管理页面。
**在向主仓库提交 PR 之前,建议你:**
> 如果你遇到报错 `ui/embed.go: pattern all:dist: no matching files found`,请参考“[前端代码](#前端代码)”这一小节构建 WebUI。
- 使用 [gofumpt](https://github.com/mvdan/gofumpt) 对你的代码进行格式化。
**在向主仓库提交 PR 之前,你应该:**
- 为你的改动添加单元测试或集成测试Certimate 使用 Go 的标准 `testing` 包)。你可以通过以下命令运行测试(在项目根目录下):
- 使用 [gofumpt](https://github.com/mvdan/gofumpt) 格式化你的代码。推荐使用 VSCode并安装 gofumpt 插件,以便在保存时自动格式化。
- 为你的改动添加单元测试或集成测试(使用 Go 标准库中的 `testing` 包)。
```bash
go test ./...
```
### 前端代码
## 修改管理页面 (Admin UI)
Certimate 的前端代码是使用 TypeScript 开发的,是一个基于 [React](https://github.com/facebook/react) 和 [Vite](https://github.com/vitejs/vite) 构建的单页应用。
Certimate 的管理页面是一个基于 React 和 Vite 构建的单页应用SPA
要启动 Admin UI
1. 进入 `ui` 项目目录。
2. 运行 `npm install` 安装依赖。
假设你已经对 Certimate 的前端代码做出了一些修改,现在你想要运行它,请遵循以下步骤:
1. 进入 `/ui` 目录;
2. 安装依赖:
```bash
npm install
```
3. 启动 Vite 开发服务器:
```bash
npm run dev
```
你可以通过浏览器访问 `http://localhost:5173` 来查看运行中的管理页面
这将启动一个 Web 服务器,默认运行在 `http://localhost:5173`,你可以通过浏览器访问来查看运行中的 WebUI
由于 Admin UI 只是一个客户端应用,运行时需要 Certimate 的后端服务作为支撑。你可以手动运行 Certimate或者使用预构建的可执行文件。
所有对 Admin UI 的修改将会自动反映在浏览器中,无需手动刷新页面。
完成修改后,运行以下命令来构建 Admin UI以便它能被嵌入到 Go 包中:
完成修改后,运行以下命令来构建 WebUI以便它能被嵌入到 Go 包中:
```bash
npm run build
```
完成所有步骤后,你可以将改动提交 PR 到 Certimate 主仓库。
**在向主仓库提交 PR 之前,你应该:**
- 使用 [ESLint](https://github.com/eslint/eslint) 格式化你的代码。推荐使用 VSCode并安装 ESLint+Prettier 插件,以便在保存时自动格式化。
## 提交 PR
在提交 PR 之前,请先创建一个 Issue 来讨论你的修改方案,并等待来自项目维护者的反馈。这样做有助于:
- 让我们充分理解你的修改内容;
- 评估修改是否符合项目路线图;
- 避免重复工作;
- 防止你投入时间到可能无法被合并的修改中。
### 提交流程
1. Fork 本仓库;
2. 在提交 PR 之前,请先发起 Issue 讨论你想要做的修改;
3. 为你的修改创建一个新的分支;
4. 请为你的修改添加相应的测试;
5. 确保你的代码能通过现有的测试;
6. 请在 PR 描述中关联相关 Issue
7. 等待合并!
> [!IMPORTANT]
>
> 建议为每个新功能或 Bug 修复创建一个从 `main` 分支派生的新分支。如果你计划提交多个 PR请保持不同的改动在独立分支中以便更容易进行代码审查并最终合并。
>
> 保持一个 PR 只实现一个功能或修复。
## 获取帮助
如果你在贡献过程中遇到困难或问题,可以通过 GitHub Issues 向我们提问。

View File

@@ -1,81 +1,106 @@
# Contributing to Certimate
# Contribution Guide
Thank you for taking the time to improve Certimate! Below is a guide for submitting a PR (Pull Request) to the main Certimate repository.
Thank you for taking the time to improve Certimate! Below is a guide for submitting a PR (Pull Request) to the Certimate repository.
- [Contributing to Certimate](#contributing-to-certimate)
We need to be nimble and ship fast given where we are, but we also want to make sure that contributors like you get as smooth an experience at contributing as possible. We've assembled this contribution guide for that purpose, aiming at getting you familiarized with the codebase & how we work with contributors, so you could quickly jump to the fun part.
Index:
- [Development](#development)
- [Prerequisites](#prerequisites)
- [Making Changes in the Go Code](#making-changes-in-the-go-code)
- [Making Changes in the Admin UI](#making-changes-in-the-admin-ui)
- [Backend Code](#backend-code)
- [Frontend Code](#frontend-code)
- [Submitting PR](#submitting-pr)
- [Pull Request Process](#pull-request-process)
- [Getting Help](#getting-help)
## Prerequisites
---
- Go 1.22+ (for Go code changes)
- Node 20+ (for Admin UI changes)
## Development
If you haven't done so already, you can fork the Certimate repository and clone your fork to work locally:
### Prerequisites
```bash
git clone https://github.com/your_username/certimate.git
```
- Go 1.24+ (for backend code changes)
- Node.js 22.0+ (for frontend code changes)
> **Important:**
> It is recommended to create a new branch from `main` for each bug fix or feature. If you plan to submit multiple PRs, ensure the changes are in separate branches for easier review and eventual merge.
> Keep each PR focused on a single feature or fix.
### Backend Code
## Making Changes in the Go Code
The backend code of Certimate is developed using Golang. It is a monolithic application based on [Pocketbase](https://github.com/pocketbase/pocketbase).
Once you have made changes to the Go code in Certimate, follow these steps to run the project:
Once you have made changes to the backend code in Certimate, follow these steps to run the project:
1. Navigate to the root directory.
2. Start the service by running:
2. Install dependencies:
```bash
go mod vendor
```
3. Start the local development server:
```bash
go run main.go serve
```
This will start a web server at `http://localhost:8090` using the prebuilt Admin UI located in `ui/dist`.
This will start a web server at `http://localhost:8090` using the prebuilt WebUI located in `/ui/dist`.
**Before submitting a PR to the main repository, consider:**
> If you encounter an error `ui/embed.go: pattern all:dist: no matching files found`, please refer to _[Frontend Code](#frontend-code)_ and build WebUI first.
- Format your source code by using [gofumpt](https://github.com/mvdan/gofumpt).
**Before submitting a PR to the main repository, you should:**
- Adding unit or integration tests for your changes. Certimate uses Gos standard `testing` package. You can run tests using the following command (while in the root project directory):
- Format your source code by using [gofumpt](https://github.com/mvdan/gofumpt). Recommended using VSCode and installing the gofumpt plugin to automatically format when saving.
- Adding unit or integration tests for your changes (with go standard library `testing` package).
```bash
go test ./...
```
### Frontend Code
## Making Changes in the Admin UI
The frontend code of Certimate is developed using TypeScript. It is a SPA based on [React](https://github.com/facebook/react) and [Vite](https://github.com/vitejs/vite).
Certimates Admin UI is a single-page application (SPA) built using React and Vite.
To start the Admin UI:
1. Navigate to the `ui` project directory.
2. Install the necessary dependencies by running:
Once you have made changes to the backend code in Certimate, follow these steps to run the project:
1. Navigate to the `/ui` directory.
2. Install dependencies:
```bash
npm install
```
3. Start the Vite development server:
3. Start the local development server:
```bash
npm run dev
```
You can now access the running Admin UI at `http://localhost:5173` in your browser.
This will start a web server at `http://localhost:5173`. You can now access the WebUI in your browser.
Since the Admin UI is a client-side application, you will also need to have the Certimate backend running. You can either manually run Certimate or use a prebuilt executable.
Any changes you make in the Admin UI will be automatically reflected in the browser without requiring a page reload.
After completing your changes, build the Admin UI so it can be embedded into the Go package:
After completing your changes, build the WebUI so it can be embedded into the Go package:
```bash
npm run build
```
Once all steps are completed, you are ready to submit a PR to the main Certimate repository.
**Before submitting a PR to the main repository, you should:**
- Format your source code by using [ESLint](https://github.com/eslint/eslint). Recommended using VSCode and installing the ESLint plugin to automatically format when saving.
## Submitting PR
Before opening a Pull Request, please open an issue to discuss the change and get feedback from the maintainers. This will helps us:
- To understand the context of the change.
- To ensure it fits into Certimate's roadmap.
- To prevent us from duplicating work.
- To prevent you from spending time on a change that we may not be able to accept.
### Pull Request Process
1. Fork the repository.
2. Before you draft a PR, please open an issue to discuss the changes you want to make.
3. Create a new branch for your changes.
4. Please add tests for your changes accordingly.
5. Ensure your code passes the existing tests.
6. Please link the issue in the PR description.
7. Get merged!
> [!IMPORTANT]
>
> It is recommended to create a new branch from `main` for each bug fix or feature. If you plan to submit multiple PRs, ensure the changes are in separate branches for easier review and eventual merge.
>
> Keep each PR focused on a single feature or fix.
## Getting Help
If you ever get stuck or get a burning question while contributing, simply shoot your queries our way via the GitHub issues.

View File

@@ -8,12 +8,13 @@ RUN \
FROM golang:1.23-alpine AS builder
FROM golang:1.24-alpine AS builder
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

@@ -1,5 +1,6 @@
MIT License
Copyright (c) 2025 certimate-go
Copyright (c) 2024 Yoan.Liu
Permission is hereby granted, free of charge, to any person obtaining a copy

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

@@ -2,11 +2,11 @@
<div align="center">
[![Stars](https://img.shields.io/github/stars/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Forks](https://img.shields.io/github/forks/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/usual2970/certimate?style=flat)](https://hub.docker.com/r/usual2970/certimate)
[![Release](https://img.shields.io/github/v/release/usual2970/certimate?sort=semver)](https://github.com/usual2970/certimate/releases)
[![License](https://img.shields.io/github/license/usual2970/certimate)](https://mit-license.org/)
[![Stars](https://img.shields.io/github/stars/certimate-go/certimate?style=flat)](https://github.com/certimate-go/certimate)
[![Forks](https://img.shields.io/github/forks/certimate-go/certimate?style=flat)](https://github.com/certimate-go/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/certimate/certimate?style=flat)](https://hub.docker.com/r/certimate/certimate)
[![Release](https://img.shields.io/github/v/release/certimate-go/certimate?style=flat&sort=semver)](https://github.com/certimate-go/certimate/releases)
[![License](https://img.shields.io/github/license/certimate-go/certimate?style=flat)](https://mit-license.org/)
</div>
@@ -22,7 +22,7 @@
做个人产品或者在中小企业里负责运维的同学,会遇到要管理多个域名的情况,需要给域名申请证书。但是手动申请证书有以下缺点:
- 😱 麻烦:申请证书并部署到服务的流程虽不复杂,但也挺麻烦的,其是你有多个域名需要维护的时候。
- 😱 麻烦:申请证书并部署到服务的流程虽不复杂,但也挺麻烦的,其是你有多个域名需要维护的时候。
- 😭 易忘:另外当前免费证书的有效期只有 90 天,这就要求你定期的操作,增加了工作量的同时,你也很容易忘掉续期,从而导致网站访问不了。
Certimate 就是为了解决上述问题而产生的,它具有以下优势:
@@ -38,8 +38,8 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
- 灵活的工作流编排方式,证书从申请到部署完全自动化;
- 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法;
- 支持 PEM、PFX、JKS 等多种格式输出证书;
- 支持 20+ 域名托管商如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)
- 支持 70+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers)
- 支持 30+ 域名托管商如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)
- 支持 100+ 部署目标(如 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 证书颁发机构;
- 更多特性等待探索。
@@ -48,7 +48,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
**5 分钟部署 Certimate**
以二进制部署为例,从 [GitHub Releases](https://github.com/usual2970/certimate/releases) 页面下载预先编译好的二进制可执行文件压缩包,解压缩后在终端中执行:
以二进制部署为例,从 [GitHub Releases](https://github.com/certimate-go/certimate/releases) 页面下载预先编译好的二进制可执行文件压缩包,解压缩后在终端中执行:
```bash
./certimate serve
@@ -71,10 +71,10 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
相关文章:
- [使用 CNAME 完成 ACME DNS-01 质询](https://docs.certimate.me/blog/cname)
- [v0.3.0:第二个不向后兼容的大版本](https://docs.certimate.me/blog/v0.3.0)
- [v0.2.0:第一个不向后兼容的大版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
- [使用 CNAME 完成 ACME DNS-01 质询](https://docs.certimate.me/blog/cname)
- [v0.3.0:第二个不向后兼容的大版本](https://docs.certimate.me/blog/v0.3.0)
- [v0.2.0:第一个不向后兼容的大版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
## ⭐ 运行界面
@@ -82,18 +82,19 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
## 🤝 参与贡献
Certimate 是一个免费且开源的项目,采用 [MIT License](./LICENSE.md)。你可以使用它做任何你想做的事,甚至把它当作一个付费服务提供给用户
Certimate 是一个免费且开源的项目。我们欢迎任何人为 Certimate 做出贡献,以帮助改善 Certimate。包括但不限于提交代码、反馈缺陷、交流想法或分享你基于 Certimate 的使用案例。同时,我们也欢迎用户在个人博客或社交媒体上分享 Certimate
你可以通过以下方式来支持 Certimate 的开发:
如果你想要贡献代码,请先阅读我们的[贡献指南](./CONTRIBUTING.md)。
- 提交代码:如果你发现了 Bug 或有新的功能需求,而你又有相关经验,可以[提交代码](CONTRIBUTING.md)给我们
- 提交 Issue功能建议或者 Bug 可以[提交 Issue](https://github.com/usual2970/certimate/issues) 给我们。
请在 https://github.com/certimate-go/certimate 提交 [Issues](https://github.com/certimate-go/certimate/issues) 和 [Pull Requests](https://github.com/certimate-go/certimate/pulls)
支持更多提供商、UI 的优化改进、Bug 修复、文档完善等,欢迎大家参与贡献
#### 感谢以下贡献者对 Certimate 做出的贡献
[![Contributors](https://contrib.rocks/image?repo=certimate-go/certimate)](https://github.com/certimate-go/certimate/graphs/contributors)
## ⛔ 免责声明
Certimate 基于 [MIT License](https://opensource.org/licenses/MIT) 发布,完全免费提供,旨在“按现状”供用户使用。作者及贡献者不对使用本软件所产生的任何直接或间接后果承担责任,包括但不限于性能下降、数据丢失、服务中断、或任何其他类型的损害。
Certimate 遵循 [MIT License](https://opensource.org/licenses/MIT) 开源协议,完全免费提供,旨在“按现状”供用户使用。作者及贡献者不对使用本软件所产生的任何直接或间接后果承担责任,包括但不限于性能下降、数据丢失、服务中断、或任何其他类型的损害。
**无任何保证**:本软件不提供任何明示或暗示的保证,包括但不限于对特定用途的适用性、无侵权性、商用性及可靠性的保证。
@@ -104,8 +105,8 @@ Certimate 基于 [MIT License](https://opensource.org/licenses/MIT) 发布,完
- [Telegram](https://t.me/+ZXphsppxUg41YmVl)
- 微信群聊(超 200 人需邀请入群,可先加作者好友)
<img src="https://i.imgur.com/8xwsLTA.png" width="240"/>
<img src="https://i.imgur.com/8xwsLTA.png" width="200"/>
## 🚀 Star 趋势图
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)
[![Stargazers over time](https://starchart.cc/certimate-go/certimate.svg?variant=adaptive)](https://starchart.cc/certimate-go/certimate)

View File

@@ -2,11 +2,11 @@
<div align="center">
[![Stars](https://img.shields.io/github/stars/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Forks](https://img.shields.io/github/forks/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/usual2970/certimate?style=flat)](https://hub.docker.com/r/usual2970/certimate)
[![Release](https://img.shields.io/github/v/release/usual2970/certimate?style=flat&sort=semver)](https://github.com/usual2970/certimate/releases)
[![License](https://img.shields.io/github/license/usual2970/certimate?style=flat)](https://mit-license.org/)
[![Stars](https://img.shields.io/github/stars/certimate-go/certimate?style=flat)](https://github.com/certimate-go/certimate)
[![Forks](https://img.shields.io/github/forks/certimate-go/certimate?style=flat)](https://github.com/certimate-go/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/certimate/certimate?style=flat)](https://hub.docker.com/r/certimate/certimate)
[![Release](https://img.shields.io/github/v/release/certimate-go/certimate?style=flat&sort=semver)](https://github.com/certimate-go/certimate/releases)
[![License](https://img.shields.io/github/license/certimate-go/certimate?style=flat)](https://mit-license.org/)
</div>
@@ -38,8 +38,8 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate
- Flexible workflow orchestration, fully automation from certificate application to deployment;
- 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 20+ 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 70+ 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 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 100+ 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.
@@ -48,7 +48,7 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate
**Deploy Certimate in 5 minutes!**
Download the archived package of precompiled binary files directly from [GitHub Releases](https://github.com/usual2970/certimate/releases), extract and then execute:
Download the archived package of precompiled binary files directly from [GitHub Releases](https://github.com/certimate-go/certimate/releases), extract and then execute:
```bash
./certimate serve
@@ -65,14 +65,14 @@ Work with Certimate right now. Or read other content in the documentation to lea
## 📄 Documentation
Please visit the documentation site [docs.certimate.me](https://docs.certimate.me/en/).
For full documentation, please visit [docs.certimate.me](https://docs.certimate.me/en/).
Related articles:
- [使用 CNAME 完成 ACME DNS-01 质询](https://docs.certimate.me/blog/cname)
- [v0.3.0:第二个不向后兼容的大版本](https://docs.certimate.me/blog/v0.3.0)
- [v0.2.0:第一个不向后兼容的大版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
- [_使用 CNAME 完成 ACME DNS-01 质询_](https://docs.certimate.me/blog/cname)
- [_v0.3.0:第二个不向后兼容的大版本_](https://docs.certimate.me/blog/v0.3.0)
- [_v0.2.0:第一个不向后兼容的大版本_](https://docs.certimate.me/blog/v0.2.0)
- [_Why Certimate?_](https://docs.certimate.me/blog/why-certimate)
## ⭐ Screenshot
@@ -80,18 +80,19 @@ Related articles:
## 🤝 Contributing
Certimate is a free and open-source project, licensed under the [MIT License](./LICENSE.md). You can use it for anything you want, even offering it as a paid service to users.
Certimate is a free and open-source project, and your feedback and contributions are needed and always welcome. Contributions include but are not limited to: submitting code, reporting bugs, sharing ideas, or showcasing your use cases based on Certimate. We also encourage users to share Certimate on personal blogs or social media.
You can support the development of Certimate in the following ways:
For those who'd like to contribute code, see our [Contribution Guide](./CONTRIBUTING_EN.md).
- **Submit Code**: If you find a bug or have new feature requests, and you have relevant experience, [you can submit code to us](CONTRIBUTING_EN.md).
- **Submit an Issue**: For feature suggestions or bugs, you can [submit an issue](https://github.com/usual2970/certimate/issues) to us.
[Issues](https://github.com/certimate-go/certimate/issues) and [Pull Requests](https://github.com/certimate-go/certimate/pulls) are opened at https://github.com/certimate-go/certimate.
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to contribute.
#### Contributors
[![Contributors](https://contrib.rocks/image?repo=certimate-go/certimate)](https://github.com/certimate-go/certimate/graphs/contributors)
## ⛔ Disclaimer
This software is provided under the [MIT License](https://opensource.org/licenses/MIT) and distributed “as-is” without any warranty of any kind. The authors and contributors are not responsible for any damages or losses resulting from the use or inability to use this software, including but not limited to data loss, business interruption, or any other potential harm.
This repository is available under the [MIT License](https://opensource.org/licenses/MIT), and distributed “as-is” without any warranty of any kind. The authors and contributors are not responsible for any damages or losses resulting from the use or inability to use this software, including but not limited to data loss, business interruption, or any other potential harm.
**No Warranties**: This software comes without any express or implied warranties, including but not limited to implied warranties of merchantability, fitness for a particular purpose, and non-infringement.
@@ -102,8 +103,8 @@ This software is provided under the [MIT License](https://opensource.org/license
- [Telegram](https://t.me/+ZXphsppxUg41YmVl)
- Wechat Group
<img src="https://i.imgur.com/zSHEoIm.png" width="240"/>
<img src="https://i.imgur.com/zSHEoIm.png" width="200"/>
## 🚀 Star History
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)
[![Stargazers over time](https://starchart.cc/certimate-go/certimate.svg?variant=adaptive)](https://starchart.cc/certimate-go/certimate)

View File

@@ -1,8 +1,8 @@
version: "3.0"
services:
certimate:
image: registry.cn-shanghai.aliyuncs.com/usual2970/certimate:latest
container_name: certimate_server
image: certimate/certimate:latest
container_name: certimate
ports:
- 8090:8090
volumes:

202
go.mod
View File

@@ -1,91 +1,98 @@
module github.com/usual2970/certimate
module github.com/certimate-go/certimate
go 1.23.0
go 1.24.0
toolchain go1.23.2
toolchain go1.24.3
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.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.28
github.com/G-Core/gcorelabscdn-go v1.0.31
github.com/alibabacloud-go/alb-20200616/v2 v2.2.8
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/darabonba-openapi/v2 v2.1.4
github.com/alibabacloud-go/esa-20240910/v2 v2.23.0
github.com/alibabacloud-go/fc-20230330/v4 v4.1.7
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.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.4
github.com/alibabacloud-go/vod-20170321/v4 v4.7.0
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.5
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.3
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go-v2/service/acm v1.31.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.45.2
github.com/baidubce/bce-sdk-go v0.9.221
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.42
github.com/go-acme/lego/v4 v4.22.2
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/aws/aws-sdk-go-v2/service/iam v1.42.0
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.141
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/nikoksr/notify v1.3.0
github.com/luthermonson/go-proxmox v0.2.2
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
github.com/pkg/sftp v1.13.8
github.com/pkg/sftp v1.13.9
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.26.1
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
github.com/qiniu/go-sdk/v7 v7.25.2
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1127
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1127
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1117
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1124
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1126
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1125
github.com/ucloud/ucloud-sdk-go v0.22.31
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.9
github.com/volcengine/volc-sdk-golang v1.0.199
github.com/volcengine/volcengine-go-sdk v1.0.187
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.1166
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1173
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1163
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1150
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.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.36.0
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
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
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2 // indirect
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.5 // indirect
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
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.48.1 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // 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
@@ -94,19 +101,19 @@ 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.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/jinzhu/copier v0.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
@@ -119,8 +126,7 @@ require (
github.com/qiniu/dyn v1.3.0 // indirect
github.com/qiniu/x v1.10.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // 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
@@ -128,15 +134,16 @@ require (
gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0
github.com/alibabacloud-go/debug v1.0.1 // indirect
@@ -144,35 +151,35 @@ require (
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
github.com/aliyun/credentials-go v1.4.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 // indirect
github.com/aliyun/credentials-go v1.4.6 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.58
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/cloudflare-go v0.114.0 // indirect
github.com/cloudflare/cloudflare-go v0.115.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -181,42 +188,39 @@ require (
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/miekg/dns v1.1.64 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
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
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/pkg/errors v0.9.1 // 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.1084 // 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.25.0 // indirect
golang.org/x/image v0.27.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.9.0
golang.org/x/tools v0.31.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0
golang.org/x/tools v0.33.0 // indirect
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.61.13 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.1 // 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/vendors/edgio-sdk/edgio-api@v0.0.0-workspace
replace github.com/Edgio/edgio-api v0.0.0-workspace => ./pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./pkg/sdk3rd/cmcc/ecloudsdkcore@v1.0.0
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./pkg/sdk3rd/cmcc/ecloudsdkclouddns@v1.0.1

434
go.sum
View File

@@ -34,18 +34,14 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
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.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.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0 h1:btEsytNrA4TG3edZnnUnzOz8W2MjOd6Bu3/7xyOXSOY=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0/go.mod h1:5SlTxxL1U4LLipEr7pAbnu6Ck5y3aIEu4L/tVbGmpsY=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
@@ -56,19 +52,23 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1 h1:HUJQzFYTv7t3V1dxPms52eEgl0l9xCNqutDrY45Lvmw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1/go.mod h1:ig/8nSkzmfxm5QGeIy5JYIEj8JEFy5JxvY3OB1YNRC4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/G-Core/gcorelabscdn-go v1.0.28 h1:6ymVMV3HPTICO5BWJCEcZZzgY+Pc/+/TQMzeXMN77GQ=
github.com/G-Core/gcorelabscdn-go v1.0.28/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpONcyLEijwh8WsXpE=
github.com/G-Core/gcorelabscdn-go v1.0.31 h1:0ulvvM3yMDQ+Q5089tLsQxffL7I4pqf2bFCurU+DeHw=
github.com/G-Core/gcorelabscdn-go v1.0.31/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpONcyLEijwh8WsXpE=
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
@@ -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.2 h1:Ug50clztqiQAy5t0R9Vejibz2Xgxm1Tpw2Y6A9eAwRE=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2/go.mod h1:l9Zd2FanDUO2UqHJSPnOv+cY9DVT+YXcr97zfpSHywo=
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,15 +112,19 @@ 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.1.4 h1:IGSZHlOnWwBbLtX5xDplQvZOH0nkrV7Wmq+Fto7JK5w=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.4/go.mod h1:Wxis0IBFusdbo44HO6KYYCJR1rRkoh47QQOYWvaheSU=
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=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7/go.mod h1:TBpgqm3XofZz2LCYjZhektGPU7ArEgascyzbm4SjFo4=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0 h1:EQmKhYju6y38kJ1ZvZROeJG2Q1Wk6hlc8KQrVhvGyaw=
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0/go.mod h1:b9qzvr/2V1f0r1Z6xUmkLqEouKcPGy4LCC22yV+6HQo=
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.0 h1:z9dPOvBRxzpD+FQ2uu/p2Z92I+PY9MUZMauwC+8AC6M=
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.0/go.mod h1:Cdg3Fu4jFByamRzt3AkeiBssoVPRNDs+EPYMP2fIj78=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
@@ -128,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.23.0 h1:Z/AALmxhOfzN+35tNCvm62/pET4IlxhDQn4nsdLqNzk=
github.com/alibabacloud-go/esa-20240910/v2 v2.23.0/go.mod h1:P1w/+i7dE2xSXVHJznEOVImlLtqqrzUJQQk2AsyBJ6o=
github.com/alibabacloud-go/fc-20230330/v4 v4.1.7 h1:rQvPfzPaouL/WGNgMDMCplA4wDscmVFff7aLCUkjv4g=
github.com/alibabacloud-go/fc-20230330/v4 v4.1.7/go.mod h1:ssEfKO6MskPtq7QaQnyiOHGWLXOZcl7a8YIf8u56DGc=
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=
@@ -157,13 +163,14 @@ github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea v1.3.2/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea v1.3.4 h1:QGTns2930y+ANmoNcUS74TgYpsoqusSrLIyYDOvIFFI=
github.com/alibabacloud-go/tea v1.3.4/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea v1.3.8/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea v1.3.9 h1:bjgt1bvdY780vz/17iWNNtbXl4A77HWntWMeaUF3So0=
github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 h1:EhAHI6edMeqgkZEqP7r4nc9iMWAUBKGxJHoBsOSKTtU=
github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
github.com/alibabacloud-go/tea-oss-sdk v1.1.5 h1:CFUFcqanvBaoGN/CyTHUZrVNtFZd1WTjem46m0HTTV0=
github.com/alibabacloud-go/tea-oss-sdk v1.1.5/go.mod h1:5fhlKMa/kWRJNgPYRt+5qSg3UidRvNbf9Z2bI8Dp5/s=
github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc=
github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
@@ -181,20 +188,21 @@ github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/vod-20170321/v4 v4.7.0 h1:hpsnJBX5EeMrFujopMCjfq+p8XbNvPhFw6LOTV/WHd8=
github.com/alibabacloud-go/vod-20170321/v4 v4.7.0/go.mod h1:TkgLKMSLu0qZN8Qdcu8svfHREyI64kjFvrp/GhrD4VQ=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.5 h1:ldAm1nvsCq66igjtcZyGhAoLClr+2eZ/pMIBUdKCOMM=
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.5/go.mod h1:DohGoS8BnMxHXghHebtjPP7+GMdxPsRN19T3nn2HcCU=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
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.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=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.3 h1:N3iHyvHRMyOwY1+0qBLSf3hb5JFiOujVSVuEpgeGttY=
github.com/aliyun/credentials-go v1.4.3/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8Y0ClOk=
github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -210,40 +218,42 @@ github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI=
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/service/acm v1.31.1 h1:FB1PgU6vlXbqehxZiHuYQRWo5Ou6sQrFJcUaRe27lRo=
github.com/aws/aws-sdk-go-v2/service/acm v1.31.1/go.mod h1:3sKYAgRbuBa2QMYGh/WEclwnmfx+QoPhhX25PdSQSQM=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.45.2 h1:S3JpsBLyn/jqSJ6GgsbDQHubmop6fshQk/iOaOeotsc=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.45.2/go.mod h1:FIBJ48TS+qJb+Ne4qJ+0NeIhtPTVXItXooTeNeVI4Po=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/acm v1.32.0 h1:Ik/TAn4TBw/t3JhQJKtwjgoOf6kg5nXc190TiGhNrmI=
github.com/aws/aws-sdk-go-v2/service/acm v1.32.0/go.mod h1:3sKYAgRbuBa2QMYGh/WEclwnmfx+QoPhhX25PdSQSQM=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1 h1:6xZNYtuVwzBs8k+TmraERt0vL68Ppg9aUi+aTQmPaVM=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1/go.mod h1:FIBJ48TS+qJb+Ne4qJ+0NeIhtPTVXItXooTeNeVI4Po=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 h1:njgAP7Rtt4DGdTGFPhJ4gaZXCD1CDj/SZDa5W4ZgSTs=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc=
github.com/aws/aws-sdk-go-v2/service/iam v1.42.0 h1:G6+UzGvubaet9QOh0664E9JeT+b6Zvop3AChozRqkrA=
github.com/aws/aws-sdk-go-v2/service/iam v1.42.0/go.mod h1:mPJkGQzeCoPs82ElNILor2JzZgYENr4UaSKUT8K27+c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 h1:/nkJHXtJXJeelXHqG0898+fWKgvfaXBhGzbCsSmn9j8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
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.221 h1:x5uTXND33m5TE3UBXYhlePuXcJi5rxNnBBt+bP7kPe0=
github.com/baidubce/bce-sdk-go v0.9.221/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=
@@ -251,8 +261,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.42 h1:Dm9FDjQP2SlAjUH7WAV7DFYDBFQz2uBxX6JULs0Sxs0=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.42/go.mod h1:CIL/T2dxgbIA79os+wl0Fq0vCbADTZNIddV6PNYB6DY=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.46 h1:Z0TagIjBDaoCZuJANP/P8+z+ef8dStVCBPjMZcI/y8s=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.46/go.mod h1:CIL/T2dxgbIA79os+wl0Fq0vCbADTZNIddV6PNYB6DY=
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@@ -275,8 +287,8 @@ github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgiM+tyKtf0HUA=
github.com/cloudflare/cloudflare-go v0.114.0/go.mod h1:O7fYfFfA6wKqKFn2QIR9lhj7FDw6VQCGOY6hd2TBtd0=
github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM=
github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@@ -294,6 +306,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/diskfs/go-diskfs v1.5.0 h1:0SANkrab4ifiZBytk380gIesYh5Gc+3i40l7qsrYP4s=
github.com/diskfs/go-diskfs v1.5.0/go.mod h1:bRFumZeGFCO8C2KNswrQeuj2m1WCVr4Ms5IjWMczMDk=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -304,6 +320,8 @@ github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -329,26 +347,26 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
github.com/go-acme/lego/v4 v4.23.1 h1:lZ5fGtGESA2L9FB8dNTvrQUq3/X4QOb8ExkKyY7LSV4=
github.com/go-acme/lego/v4 v4.23.1/go.mod h1:7UMVR7oQbIYw6V7mTgGwi4Er7B6Ww0c+c8feiBM0EgI=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
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=
@@ -387,13 +405,13 @@ 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=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
@@ -403,8 +421,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -434,8 +452,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -457,14 +473,13 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -474,8 +489,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20250315033105-103756e64e1d h1:tx51Lf+wdE+aavqH8TcPJoCjTf4cE8hrMzROghCely0=
github.com/google/pprof v0.0.0-20250315033105-103756e64e1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -491,8 +506,14 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -526,8 +547,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.141 h1:8i57QAi5u+iPAYze92bkIvZoHiS0J45ndul5glr/NE8=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141/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=
@@ -543,14 +564,14 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 h1:xZc/ZRcrOhDx9Ra9htu6ui2gUUttmLsXIqH61LcvY4U=
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/copier v0.3.4 h1:mfU6jI9PtCeUjkjQ322dlff9ELjGDu975C2p/nrubVI=
github.com/jinzhu/copier v0.3.4/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
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=
@@ -566,13 +587,15 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -599,6 +622,10 @@ github.com/libdns/dynv6 v1.0.0/go.mod h1:65PL/bAlyH0J+0WGlOJYnMpoIuXcg/FmW4dTBYW
github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/luthermonson/go-proxmox v0.2.2 h1:BZ7VEj302wxw2i/EwTcyEiBzQib8teocB2SSkLHyySY=
github.com/luthermonson/go-proxmox v0.2.2/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -619,8 +646,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
@@ -653,8 +680,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=
@@ -693,7 +718,10 @@ github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -702,19 +730,21 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/sftp v1.13.8 h1:Xt7eJ/xqXv7s0VuzFw7JXhZj6Oc1zI6l4GK8KP9sFB0=
github.com/pkg/sftp v1.13.8/go.mod h1:DmvEkvKE2lshEeuo2JMp06yqcx9HVnR7e3zqQl42F3U=
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
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.26.1 h1:0WBqIRKKPCqp+xHPVLB4fevkoT9HVlR4BSuNwAt5oJ0=
github.com/pocketbase/pocketbase v0.26.1/go.mod h1:t5y5pfnhrEg//RuSzSg0a926OLZ0oQj66jYs3BzDJwA=
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-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw=
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246/go.mod h1:i1Au86ZXK0ZalQNyBp2njCcyhSCR/QP/AMfILip+zNI=
github.com/povsister/scp v0.0.0-20250504051308-e467f71ea63c h1:1+j5JHz9mUzYSp0scuF6hzvJP28EDBFe5eBJb0xnGk4=
github.com/povsister/scp v0.0.0-20250504051308-e467f71ea63c/go.mod h1:CiJNEeV6v0tUCNul/+gTjl+FgjfImoiuptJB9AEzqjE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
@@ -737,22 +767,22 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/qiniu/dyn v1.3.0 h1:s+xPTeV0H8yikgM4ZMBc7Rrefam8UNI3asBlkaOQg5o=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.25.2 h1:URwgZpxySdiwu2yQpHk93X4LXWHyFRp1x3Vmlk/YWvo=
github.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o=
github.com/qiniu/go-sdk/v7 v7.25.3 h1:eYHh02q4i5MrlEn3qy823w7moieymFzb4dsP38Y43AI=
github.com/qiniu/go-sdk/v7 v7.25.3/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o=
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.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
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=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -764,15 +794,15 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
@@ -799,34 +829,37 @@ 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.1115 h1:HsrXyqKQB2mKfGq+ZkbylRCMrbtPCmmUBrwA8MhhEX0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1115/go.mod h1:5cz1DtLlXK98U1Hh36oW4PjVOU+mbKg5wtCDmCc9Fcs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1127 h1:PiLZflqaW0690YsqIM/hqaVYjZJ3+cCJp4NHfw7h/uw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1127/go.mod h1:V1+julLUOH0jKoVH6o6xgM4STWowzAL57M4VanUEEag=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1115/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1117/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/common v1.0.1125/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1126/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1127 h1:1uG8zc0b9gLbyTr27T0CzGtcdrL86CGGJ6Flkq867f4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1127/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 h1:kwctN0WQYt8/iKP+iRCTCwdzEMIXsXklbRIib5rjeQ8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084/go.mod h1:qE67ApiBzeRvzeDsV+GxyIDbVIDemsKpHXllQATz/Vw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1117 h1:Yc/r5zUAyukVI3huIuwE7koowCjDjOWqeRpBILCvOSE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1117/go.mod h1:YxsorHl3sTRw+2GsUObMqcumDqAQ3zo9rLMtf3Cxj8U=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115 h1:Qi7VWmJ0AQxEMlwKpbWfnsLA5QdNxekdcLJTBVdO85U=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115/go.mod h1:P16nIMvmpSY+arTc2m2HyJmrYQP6CFnr48glz0+abyw=
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.1126 h1:+CJQNXLdLP0GLaz2fnPECQsU+WdOmW3BQ54cNoQgMKA=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1126/go.mod h1:eY3GoWilNoCPOEw2Lp4o+h02nEfc+BoZnqK3TlK6F7I=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1115 h1:rO0LdbcNtT5VlL9sB/K3Ve848uLp1rgg3R8igT9xsFQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1115/go.mod h1:jJR8Y5sHuujSXZy0cpCgBk180TvPNsLw9hEoSH9w7iA=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1125 h1:IR9pJqHjHr7KyncRVxld9iltfnmy9sCC+0USZrs3rOw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1125/go.mod h1:5+5QrF7x+AW1KPM7F+YRzD74L88RXHZ6BxDF07b8QkE=
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.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.1163/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/gaap v1.0.1163 h1:putqrH5n1SVRqFWHOylVqYI5yLQUjRTkHqZPLT2yeVY=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1163/go.mod h1:aEWRXlAvovPUUoS3kVB/LVWEQ19WqzTj2lXGvR1YArY=
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.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=
@@ -835,16 +868,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.31 h1:izZK+Re9ZkJAd1fHSVpFzgh8uKda4f5G6++iUw4n/mE=
github.com/ucloud/ucloud-sdk-go v0.22.31/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.9 h1:fEnScn2dXfvfNcFnvJnpf/cYdj8kLIe5QC5qORlFO2c=
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.9/go.mod h1:IrjK84IJJTuOZOTMv/P18Ydjy/x+ow7fF7q11jAxXLM=
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.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc=
github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volcengine-go-sdk v1.0.187 h1:YpZjydoyHDA/ofnF6mYCelbOoo9pJsBEiQOOSJzGSOY=
github.com/volcengine/volcengine-go-sdk v1.0.187/go.mod h1:gfEDc1s7SYaGoY+WH2dRrS3qiuDJMkwqyfXWCa7+7oA=
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=
@@ -912,8 +947,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -927,14 +962,14 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1017,16 +1052,16 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1044,8 +1079,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1098,6 +1133,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1106,6 +1142,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1122,8 +1159,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1139,8 +1176,8 @@ golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1159,15 +1196,15 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1222,8 +1259,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1363,39 +1400,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.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
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-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
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.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
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/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
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.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.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
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.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
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=
@@ -1406,8 +1443,11 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -1,30 +1,31 @@
package applicant
package applicant
import "github.com/usual2970/certimate/internal/domain"
import "github.com/certimate-go/certimate/internal/domain"
const (
sslProviderLetsEncrypt = string(domain.ApplyCAProviderTypeLetsEncrypt)
sslProviderLetsEncryptStaging = string(domain.ApplyCAProviderTypeLetsEncryptStaging)
sslProviderBuypass = string(domain.ApplyCAProviderTypeBuypass)
sslProviderGoogleTrustServices = string(domain.ApplyCAProviderTypeGoogleTrustServices)
sslProviderSSLCom = string(domain.ApplyCAProviderTypeSSLCom)
sslProviderZeroSSL = string(domain.ApplyCAProviderTypeZeroSSL)
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 {
Config map[domain.ApplyCAProviderType]map[string]any `json:"config"`
Provider string `json:"provider"`
Config map[domain.CAProviderType]map[string]any `json:"config"`
Provider string `json:"provider"`
}

View File

@@ -1,4 +1,4 @@
package applicant
package applicant
import (
"context"
@@ -7,41 +7,51 @@ import (
"crypto/elliptic"
"crypto/rand"
"fmt"
"strings"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"golang.org/x/sync/singleflight"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/usual2970/certimate/internal/repository"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/repository"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
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 {
return nil, err
}
keyPEM, err := certutil.ConvertECPrivateKeyToPEM(key)
keyPEM, err := xcert.ConvertECPrivateKeyToPEM(key)
if err != nil {
return nil, err
}
@@ -65,7 +75,7 @@ func (u acmeUser) GetRegistration() *registration.Resource {
}
func (u *acmeUser) GetPrivateKey() crypto.PrivateKey {
rs, _ := certutil.ParseECPrivateKeyFromPEM(u.privkey)
rs, _ := xcert.ParseECPrivateKeyFromPEM(u.privkey)
return rs
}
@@ -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,19 +108,19 @@ 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 {
if err := xmaps.Populate(userRegisterOptions, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -117,10 +131,10 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case sslProviderSSLCom:
case caSSLCom:
{
access := domain.AccessConfigForSSLCom{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
if err := xmaps.Populate(userRegisterOptions, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -131,10 +145,10 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case sslProviderZeroSSL:
case caZeroSSL:
{
access := domain.AccessConfigForZeroSSL{}
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
if err := xmaps.Populate(userRegisterOptions, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -145,8 +159,28 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m
})
}
case caCustom:
{
access := domain.AccessConfigForACMECA{}
if err := xmaps.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)
err = fmt.Errorf("unsupported ca provider '%s'", user.CA)
}
if err != nil {
return nil, err

View File

@@ -4,10 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
@@ -17,77 +19,71 @@ import (
"golang.org/x/exp/slices"
"golang.org/x/time/rate"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/utils/sliceutil"
"github.com/usual2970/certimate/internal/repository"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/repository"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
xslices "github.com/certimate-go/certimate/pkg/utils/slices"
)
type ApplyCertResult struct {
CertificateFullChain string
type ApplyResult struct {
CSR string
FullChainCertificate string
IssuerCertificate string
PrivateKey string
ACMEAccountUrl string
ACMECertUrl string
ACMECertStableUrl string
CSR string
ARIReplaced bool
}
type Applicant interface {
Apply() (*ApplyCertResult, error)
Apply(ctx context.Context) (*ApplyResult, error)
}
type applicantOptions struct {
Domains []string
ContactEmail string
Provider domain.ApplyDNSProviderType
ProviderAccessConfig map[string]any
ProviderExtendedConfig map[string]any
CAProvider domain.ApplyCAProviderType
CAProviderAccessConfig map[string]any
CAProviderExtendedConfig map[string]any
KeyAlgorithm string
Nameservers []string
DnsPropagationTimeout int32
DnsTTL int32
DisableFollowCNAME bool
ReplacedARIAcct string
ReplacedARICert string
type ApplicantWithWorkflowNodeConfig struct {
Node *domain.WorkflowNode
Logger *slog.Logger
}
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
if node.Type != domain.WorkflowNodeTypeApply {
return nil, fmt.Errorf("node type is not apply")
func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, error) {
if config.Node == nil {
return nil, fmt.Errorf("node is nil")
}
if config.Node.Type != domain.WorkflowNodeTypeApply {
return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeApply))
}
nodeConfig := node.GetConfigForApply()
options := &applicantOptions{
Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeConfig.ContactEmail,
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderExtendedConfig: nodeConfig.ProviderConfig,
CAProvider: domain.ApplyCAProviderType(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 != "" }),
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
DnsTTL: nodeConfig.DnsTTL,
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
nodeCfg := config.Node.GetConfigForApply()
options := &applicantProviderOptions{
Domains: xslices.Filter(strings.Split(nodeCfg.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeCfg.ContactEmail,
Provider: domain.ACMEDns01ProviderType(nodeCfg.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeCfg.ProviderConfig,
CAProvider: domain.CAProviderType(nodeCfg.CAProvider),
CAProviderAccessConfig: make(map[string]any),
CAProviderServiceConfig: nodeCfg.CAProviderConfig,
KeyAlgorithm: nodeCfg.KeyAlgorithm,
Nameservers: xslices.Filter(strings.Split(nodeCfg.Nameservers, ";"), func(s string) bool { return s != "" }),
DnsPropagationWait: nodeCfg.DnsPropagationWait,
DnsPropagationTimeout: nodeCfg.DnsPropagationTimeout,
DnsTTL: nodeCfg.DnsTTL,
DisableFollowCNAME: nodeCfg.DisableFollowCNAME,
}
accessRepo := repository.NewAccessRepository()
if nodeConfig.ProviderAccessId != "" {
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
if nodeCfg.ProviderAccessId != "" {
if access, err := accessRepo.GetById(context.Background(), nodeCfg.ProviderAccessId); err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeCfg.ProviderAccessId, err)
} else {
options.ProviderAccessConfig = access.Config
}
}
if nodeConfig.CAProviderAccessId != "" {
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)
if nodeCfg.CAProviderAccessId != "" {
if access, err := accessRepo.GetById(context.Background(), nodeCfg.CAProviderAccessId); err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeCfg.CAProviderAccessId, err)
} else {
options.CAProviderAccessId = access.Id
options.CAProviderAccessConfig = access.Config
}
}
@@ -97,24 +93,24 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider")
sslProviderConfig := &acmeSSLProviderConfig{
Config: make(map[domain.ApplyCAProviderType]map[string]any),
Provider: sslProviderDefault,
Config: make(map[domain.CAProviderType]map[string]any),
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
}
}
options.CAProvider = domain.ApplyCAProviderType(sslProviderConfig.Provider)
options.CAProvider = domain.CAProviderType(sslProviderConfig.Provider)
options.CAProviderAccessConfig = sslProviderConfig.Config[options.CAProvider]
}
certRepo := repository.NewCertificateRepository()
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id)
if lastCertificate != nil {
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id)
if lastCertificate != nil && !lastCertificate.ACMERenewed {
newCertSan := slices.Clone(options.Domains)
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
slices.Sort(newCertSan)
@@ -124,25 +120,53 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
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
}
}
}
applicant, err := createApplicant(options)
applicant, err := createApplicantProvider(options)
if err != nil {
return nil, err
}
return &proxyApplicant{
return &applicantImpl{
applicant: applicant,
options: options,
}, nil
}
func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) {
user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail)
type applicantImpl struct {
applicant challenge.Provider
options *applicantProviderOptions
}
var _ Applicant = (*applicantImpl)(nil)
func (d *applicantImpl) Apply(ctx context.Context) (*ApplyResult, error) {
limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail))
if err := limiter.Wait(ctx); err != nil {
return nil, err
}
return applyUseLego(d.applicant, d.options)
}
const (
limitBurst = 300
limitRate float64 = float64(1) / float64(36)
)
var limiters sync.Map
func getLimiter(key string) *rate.Limiter {
limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300))
return limiter.(*rate.Limiter)
}
func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) {
user, err := newAcmeUser(string(options.CAProvider), options.CAProviderAccessId, options.ContactEmail)
if err != nil {
return nil, err
}
@@ -153,14 +177,27 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
// Create an ACME client config
config := lego.NewConfig(user)
config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
config.CADirURL = sslProviderUrls[user.CA]
if user.CA == sslProviderSSLCom {
config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
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 := xmaps.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
@@ -170,18 +207,26 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
}
// Set the DNS01 challenge provider
challengeOptions := make([]dns01.ChallengeOption, 0)
if len(options.Nameservers) > 0 {
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)))
challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement())
}
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
client.Challenge.SetDNS01Provider(legoProvider,
dns01.CondOption(
len(options.Nameservers) > 0,
dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)),
),
dns01.CondOption(
options.DnsPropagationWait > 0,
dns01.PropagationWait(time.Duration(options.DnsPropagationWait)*time.Second, true),
),
dns01.CondOption(
len(options.Nameservers) > 0 || options.DnsPropagationWait > 0,
dns01.DisableAuthoritativeNssPropagationRequirement(),
),
)
// New users need to register first
if !user.hasRegistration() {
reg, err := registerAcmeUserWithSingleFlight(client, user, options.CAProviderAccessConfig)
if err != nil {
return nil, fmt.Errorf("failed to register: %w", err)
return nil, fmt.Errorf("failed to register acme user: %w", err)
}
user.Registration = reg
}
@@ -191,66 +236,41 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
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 &ApplyCertResult{
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
return &ApplyResult{
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
}
func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType {
switch algo {
case domain.CertificateKeyAlgorithmTypeRSA2048:
return certcrypto.RSA2048
case domain.CertificateKeyAlgorithmTypeRSA3072:
return certcrypto.RSA3072
case domain.CertificateKeyAlgorithmTypeRSA4096:
return certcrypto.RSA4096
case domain.CertificateKeyAlgorithmTypeRSA8192:
return certcrypto.RSA8192
case domain.CertificateKeyAlgorithmTypeEC256:
return certcrypto.EC256
case domain.CertificateKeyAlgorithmTypeEC384:
return certcrypto.EC384
case domain.CertificateKeyAlgorithmTypeEC512:
return certcrypto.KeyType("P512")
func parseLegoKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType {
alogMap := map[domain.CertificateKeyAlgorithmType]certcrypto.KeyType{
domain.CertificateKeyAlgorithmTypeRSA2048: certcrypto.RSA2048,
domain.CertificateKeyAlgorithmTypeRSA3072: certcrypto.RSA3072,
domain.CertificateKeyAlgorithmTypeRSA4096: certcrypto.RSA4096,
domain.CertificateKeyAlgorithmTypeRSA8192: certcrypto.RSA8192,
domain.CertificateKeyAlgorithmTypeEC256: certcrypto.EC256,
domain.CertificateKeyAlgorithmTypeEC384: certcrypto.EC384,
domain.CertificateKeyAlgorithmTypeEC512: certcrypto.KeyType("P512"),
}
if keyType, ok := alogMap[algo]; ok {
return keyType
}
return certcrypto.RSA2048
}
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
type proxyApplicant struct {
applicant challenge.Provider
options *applicantOptions
}
var limiters sync.Map
const (
limitBurst = 300
limitRate float64 = float64(1) / float64(36)
)
func getLimiter(key string) *rate.Limiter {
limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300))
return limiter.(*rate.Limiter)
}
func (d *proxyApplicant) Apply() (*ApplyCertResult, error) {
limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail))
limiter.Wait(context.Background())
return apply(d.applicant, d.options)
}

View File

@@ -5,49 +5,78 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/usual2970/certimate/internal/domain"
pACMEHttpReq "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/acmehttpreq"
pAliyun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun"
pAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53"
pAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
pBaiduCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud"
pBunny "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/bunny"
pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
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"
pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla"
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"
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"
pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
pPorkbun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/porkbun"
pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
pRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun"
pTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
pTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo"
pVercel "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel"
pVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
pWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/certimate-go/certimate/internal/domain"
pACMEHttpReq "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmehttpreq"
pAliyun "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun"
pAliyunESA "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa"
pAWSRoute53 "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aws-route53"
pAzureDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/azure-dns"
pBaiduCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud"
pBunny "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/bunny"
pCloudflare "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/cloudflare"
pClouDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/cloudns"
pCMCCCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/cmcccloud"
pConstellix "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/constellix"
pCTCCCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ctcccloud"
pDeSEC "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/desec"
pDigitalOcean "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/digitalocean"
pDNSLA "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/dnsla"
pDuckDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/duckdns"
pDynv6 "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/dynv6"
pGcore "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/gcore"
pGname "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/gname"
pGoDaddy "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/godaddy"
pHetzner "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/hetzner"
pHuaweiCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/huaweicloud"
pJDCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud"
pNamecheap "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/namecheap"
pNameDotCom "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/namedotcom"
pNameSilo "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/namesilo"
pNetcup "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/netcup"
pNetlify "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/netlify"
pNS1 "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ns1"
pPorkbun "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/porkbun"
pPowerDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/powerdns"
pRainYun "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/rainyun"
pTencentCloud "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud"
pTencentCloudEO "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/tencentcloud-eo"
pUCloudUDNR "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr"
pVercel "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/vercel"
pVolcEngine "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/volcengine"
pWestcn "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/westcn"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func createApplicant(options *applicantOptions) (challenge.Provider, error) {
type applicantProviderOptions struct {
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) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch options.Provider {
case domain.ApplyDNSProviderTypeACMEHttpReq:
case domain.ACMEDns01ProviderTypeACMEHttpReq:
{
access := domain.AccessConfigForACMEHttpReq{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -61,44 +90,60 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS:
case domain.ACMEDns01ProviderTypeAliyun, domain.ACMEDns01ProviderTypeAliyunDNS, domain.ACMEDns01ProviderTypeAliyunESA:
{
access := domain.AccessConfigForAliyun{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pAliyun.NewChallengeProvider(&pAliyun.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
switch options.Provider {
case domain.ACMEDns01ProviderTypeAliyun, domain.ACMEDns01ProviderTypeAliyunDNS:
applicant, err := pAliyun.NewChallengeProvider(&pAliyun.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
case domain.ACMEDns01ProviderTypeAliyunESA:
applicant, err := pAliyunESA.NewChallengeProvider(&pAliyunESA.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: xmaps.GetString(options.ProviderServiceConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
default:
break
}
}
case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53:
case domain.ACMEDns01ProviderTypeAWS, domain.ACMEDns01ProviderTypeAWSRoute53:
{
access := domain.AccessConfigForAWS{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
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: xmaps.GetString(options.ProviderServiceConfig, "region"),
HostedZoneId: xmaps.GetString(options.ProviderServiceConfig, "hostedZoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeAzure, domain.ApplyDNSProviderTypeAzureDNS:
case domain.ACMEDns01ProviderTypeAzure, domain.ACMEDns01ProviderTypeAzureDNS:
{
access := domain.AccessConfigForAzure{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -113,10 +158,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeBaiduCloud, domain.ApplyDNSProviderTypeBaiduCloudDNS:
case domain.ACMEDns01ProviderTypeBaiduCloud, domain.ACMEDns01ProviderTypeBaiduCloudDNS:
{
access := domain.AccessConfigForBaiduCloud{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -129,10 +174,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeBunny:
case domain.ACMEDns01ProviderTypeBunny:
{
access := domain.AccessConfigForBunny{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -144,10 +189,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeCloudflare:
case domain.ACMEDns01ProviderTypeCloudflare:
{
access := domain.AccessConfigForCloudflare{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -160,10 +205,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeClouDNS:
case domain.ACMEDns01ProviderTypeClouDNS:
{
access := domain.AccessConfigForClouDNS{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -176,10 +221,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeCMCCCloud:
case domain.ACMEDns01ProviderTypeCMCCCloud, domain.ACMEDns01ProviderTypeCMCCCloudDNS:
{
access := domain.AccessConfigForCMCCCloud{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -192,10 +237,42 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeDeSEC:
case domain.ACMEDns01ProviderTypeConstellix:
{
access := domain.AccessConfigForConstellix{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pConstellix.NewChallengeProvider(&pConstellix.ChallengeProviderConfig{
ApiKey: access.ApiKey,
SecretKey: access.SecretKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeCTCCCloud, domain.ACMEDns01ProviderTypeCTCCCloudSmartDNS:
{
access := domain.AccessConfigForCTCCCloud{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pCTCCCloud.NewChallengeProvider(&pCTCCCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeDeSEC:
{
access := domain.AccessConfigForDeSEC{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -207,10 +284,25 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeDNSLA:
case domain.ACMEDns01ProviderTypeDigitalOcean:
{
access := domain.AccessConfigForDigitalOcean{}
if err := xmaps.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{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -223,10 +315,24 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeDynv6:
case domain.ACMEDns01ProviderTypeDuckDNS:
{
access := domain.AccessConfigForDuckDNS{}
if err := xmaps.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{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -238,10 +344,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeGcore:
case domain.ACMEDns01ProviderTypeGcore:
{
access := domain.AccessConfigForGcore{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -253,10 +359,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeGname:
case domain.ACMEDns01ProviderTypeGname:
{
access := domain.AccessConfigForGname{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -269,10 +375,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeGoDaddy:
case domain.ACMEDns01ProviderTypeGoDaddy:
{
access := domain.AccessConfigForGoDaddy{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -285,44 +391,59 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS:
case domain.ACMEDns01ProviderTypeHetzner:
{
access := domain.AccessConfigForHetzner{}
if err := xmaps.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{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
Region: xmaps.GetString(options.ProviderServiceConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeJDCloud, domain.ApplyDNSProviderTypeJDCloudDNS:
case domain.ACMEDns01ProviderTypeJDCloud, domain.ACMEDns01ProviderTypeJDCloudDNS:
{
access := domain.AccessConfigForJDCloud{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"),
RegionId: xmaps.GetString(options.ProviderServiceConfig, "regionId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeNamecheap:
case domain.ACMEDns01ProviderTypeNamecheap:
{
access := domain.AccessConfigForNamecheap{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -335,10 +456,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeNameDotCom:
case domain.ACMEDns01ProviderTypeNameDotCom:
{
access := domain.AccessConfigForNameDotCom{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -351,10 +472,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeNameSilo:
case domain.ACMEDns01ProviderTypeNameSilo:
{
access := domain.AccessConfigForNameSilo{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -366,10 +487,42 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeNS1:
case domain.ACMEDns01ProviderTypeNetcup:
{
access := domain.AccessConfigForNetcup{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNetcup.NewChallengeProvider(&pNetcup.ChallengeProviderConfig{
CustomerNumber: access.CustomerNumber,
ApiKey: access.ApiKey,
ApiPassword: access.ApiPassword,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeNetlify:
{
access := domain.AccessConfigForNetlify{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNetlify.NewChallengeProvider(&pNetlify.ChallengeProviderConfig{
ApiToken: access.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeNS1:
{
access := domain.AccessConfigForNS1{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -381,10 +534,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypePorkbun:
case domain.ACMEDns01ProviderTypePorkbun:
{
access := domain.AccessConfigForPorkbun{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -397,26 +550,27 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypePowerDNS:
case domain.ACMEDns01ProviderTypePowerDNS:
{
access := domain.AccessConfigForPowerDNS{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pPowerDNS.NewChallengeProvider(&pPowerDNS.ChallengeProviderConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
ServerUrl: access.ServerUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeRainYun:
case domain.ACMEDns01ProviderTypeRainYun:
{
access := domain.AccessConfigForRainYun{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -428,15 +582,15 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS, domain.ApplyDNSProviderTypeTencentCloudEO:
case domain.ACMEDns01ProviderTypeTencentCloud, domain.ACMEDns01ProviderTypeTencentCloudDNS, domain.ACMEDns01ProviderTypeTencentCloudEO:
{
access := domain.AccessConfigForTencentCloud{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
case domain.ACMEDns01ProviderTypeTencentCloud, domain.ACMEDns01ProviderTypeTencentCloudDNS:
applicant, err := pTencentCloud.NewChallengeProvider(&pTencentCloud.ChallengeProviderConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
@@ -445,11 +599,11 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
})
return applicant, err
case domain.ApplyDNSProviderTypeTencentCloudEO:
case domain.ACMEDns01ProviderTypeTencentCloudEO:
applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"),
ZoneId: xmaps.GetString(options.ProviderServiceConfig, "zoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
@@ -460,10 +614,26 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
}
case domain.ApplyDNSProviderTypeVercel:
case domain.ACMEDns01ProviderTypeUCloudUDNR:
{
access := domain.AccessConfigForUCloud{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pUCloudUDNR.NewChallengeProvider(&pUCloudUDNR.ChallengeProviderConfig{
PrivateKey: access.PrivateKey,
PublicKey: access.PublicKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ACMEDns01ProviderTypeVercel:
{
access := domain.AccessConfigForVercel{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -476,10 +646,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS:
case domain.ACMEDns01ProviderTypeVolcEngine, domain.ACMEDns01ProviderTypeVolcEngineDNS:
{
access := domain.AccessConfigForVolcEngine{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -492,10 +662,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
case domain.ApplyDNSProviderTypeWestcn:
case domain.ACMEDns01ProviderTypeWestcn:
{
access := domain.AccessConfigForWestcn{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
@@ -509,5 +679,5 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
}
return nil, fmt.Errorf("unsupported applicant provider: %s", string(options.Provider))
return nil, fmt.Errorf("unsupported applicant provider '%s'", string(options.Provider))
}

View File

@@ -13,12 +13,12 @@ import (
"github.com/go-acme/lego/v4/certcrypto"
"github.com/pocketbase/dbx"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
"github.com/usual2970/certimate/internal/repository"
"github.com/certimate-go/certimate/internal/app"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/domain/dtos"
"github.com/certimate-go/certimate/internal/notify"
"github.com/certimate-go/certimate/internal/repository"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
const (
@@ -145,7 +145,7 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
{
const pfxPassword = "certimate"
certPFX, err := certutil.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword)
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword)
if err != nil {
return nil, err
}
@@ -183,7 +183,7 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
{
const jksPassword = "certimate"
certJKS, err := certutil.TransformCertificateFromPEMToJKS(certificate.Certificate, certificate.PrivateKey, jksPassword, jksPassword, jksPassword)
certJKS, err := xcert.TransformCertificateFromPEMToJKS(certificate.Certificate, certificate.PrivateKey, jksPassword, jksPassword, jksPassword)
if err != nil {
return nil, err
}
@@ -223,7 +223,7 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
}
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
certX509, err := certutil.ParseCertificateFromPEM(req.Certificate)
certX509, err := xcert.ParseCertificateFromPEM(req.Certificate)
if err != nil {
return nil, err
} else if time.Now().After(certX509.NotAfter) {

View File

@@ -5,77 +5,70 @@ import (
"fmt"
"log/slog"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/repository"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/repository"
"github.com/certimate-go/certimate/pkg/core"
)
type Deployer interface {
SetLogger(*slog.Logger)
Deploy(ctx context.Context) error
}
type deployerOptions struct {
Provider domain.DeployProviderType
ProviderAccessConfig map[string]any
ProviderDeployConfig map[string]any
type DeployerWithWorkflowNodeConfig struct {
Node *domain.WorkflowNode
Logger *slog.Logger
CertificatePEM string
PrivateKeyPEM string
}
func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
Certificate string
PrivateKey string
},
) (Deployer, error) {
if node.Type != domain.WorkflowNodeTypeDeploy {
return nil, fmt.Errorf("node type is not deploy")
func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error) {
if config.Node == nil {
return nil, fmt.Errorf("node is nil")
}
if config.Node.Type != domain.WorkflowNodeTypeDeploy {
return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeDeploy))
}
nodeConfig := node.GetConfigForDeploy()
options := &deployerOptions{
Provider: domain.DeployProviderType(nodeConfig.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderDeployConfig: nodeConfig.ProviderConfig,
nodeCfg := config.Node.GetConfigForDeploy()
options := &deployerProviderOptions{
Provider: domain.DeploymentProviderType(nodeCfg.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeCfg.ProviderConfig,
}
accessRepo := repository.NewAccessRepository()
if nodeConfig.ProviderAccessId != "" {
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
if nodeCfg.ProviderAccessId != "" {
access, err := accessRepo.GetById(context.Background(), nodeCfg.ProviderAccessId)
if err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeCfg.ProviderAccessId, err)
} else {
options.ProviderAccessConfig = access.Config
}
}
deployer, err := createDeployer(options)
deployer, err := createSSLDeployerProvider(options)
if err != nil {
return nil, err
} else {
deployer.SetLogger(config.Logger)
}
return &proxyDeployer{
deployer: deployer,
deployCertificate: certdata.Certificate,
deployPrivateKey: certdata.PrivateKey,
return &deployerImpl{
provider: deployer,
certPEM: config.CertificatePEM,
privkeyPEM: config.PrivateKeyPEM,
}, nil
}
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
type proxyDeployer struct {
deployer deployer.Deployer
deployCertificate string
deployPrivateKey string
type deployerImpl struct {
provider core.SSLDeployer
certPEM string
privkeyPEM string
}
func (d *proxyDeployer) SetLogger(logger *slog.Logger) {
if logger == nil {
panic("logger is nil")
}
var _ Deployer = (*deployerImpl)(nil)
d.deployer.WithLogger(logger)
}
func (d *proxyDeployer) Deploy(ctx context.Context) error {
_, err := d.deployer.Deploy(ctx, d.deployCertificate, d.deployPrivateKey)
func (d *deployerImpl) Deploy(ctx context.Context) error {
_, err := d.provider.Deploy(ctx, d.certPEM, d.privkeyPEM)
return err
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -11,15 +11,23 @@ type Access struct {
Name string `json:"name" db:"name"`
Provider string `json:"provider" db:"provider"`
Config map[string]any `json:"config" db:"config"`
Reserve string `json:"reserve,omitempty" db:"reserve"`
DeletedAt *time.Time `json:"deleted" db:"deleted"`
}
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"`
@@ -30,6 +38,13 @@ type AccessConfigForACMEHttpReq struct {
type AccessConfigForAliyun struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
ResourceGroupId string `json:"resourceGroupId,omitempty"`
}
type AccessConfigForAPISIX struct {
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForAWS struct {
@@ -54,7 +69,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"`
}
@@ -73,9 +94,10 @@ type AccessConfigForCacheFly struct {
}
type AccessConfigForCdnfly struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForCloudflare struct {
@@ -93,10 +115,34 @@ type AccessConfigForCMCCCloud struct {
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForConstellix struct {
ApiKey string `json:"apiKey"`
SecretKey string `json:"secretKey"`
}
type AccessConfigForCTCCCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
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"`
@@ -107,6 +153,10 @@ type AccessConfigForDogeCloud struct {
SecretKey string `json:"secretKey"`
}
type AccessConfigForDuckDNS struct {
Token string `json:"token"`
}
type AccessConfigForDynv6 struct {
HttpToken string `json:"httpToken"`
}
@@ -116,6 +166,25 @@ type AccessConfigForEdgio struct {
ClientSecret string `json:"clientSecret"`
}
type AccessConfigForEmail struct {
SmtpHost string `json:"smtpHost"`
SmtpPort int32 `json:"smtpPort"`
SmtpTls bool `json:"smtpTls"`
Username string `json:"username"`
Password string `json:"password"`
DefaultSenderAddress string `json:"defaultSenderAddress,omitempty"`
DefaultSenderName string `json:"defaultSenderName,omitempty"`
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"`
}
@@ -130,14 +199,27 @@ type AccessConfigForGoDaddy struct {
ApiSecret string `json:"apiSecret"`
}
type AccessConfigForGoEdge struct {
ServerUrl string `json:"serverUrl"`
ApiRole string `json:"apiRole"`
AccessKeyId string `json:"accessKeyId"`
AccessKey string `json:"accessKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForGoogleTrustServices struct {
EabKid string `json:"eabKid"`
EabHmacKey string `json:"eabHmacKey"`
}
type AccessConfigForHetzner struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForHuaweiCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
}
type AccessConfigForJDCloud struct {
@@ -149,6 +231,26 @@ type AccessConfigForKubernetes struct {
KubeConfig string `json:"kubeConfig,omitempty"`
}
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"`
Password string `json:"password"`
DefaultChannelId string `json:"defaultChannelId,omitempty"`
}
type AccessConfigForNamecheap struct {
Username string `json:"username"`
ApiKey string `json:"apiKey"`
@@ -163,6 +265,16 @@ type AccessConfigForNameSilo struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForNetcup struct {
CustomerNumber string `json:"customerNumber"`
ApiKey string `json:"apiKey"`
ApiPassword string `json:"apiPassword"`
}
type AccessConfigForNetlify struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForNS1 struct {
ApiKey string `json:"apiKey"`
}
@@ -173,8 +285,16 @@ type AccessConfigForPorkbun struct {
}
type AccessConfigForPowerDNS struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForProxmoxVE struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForQiniu struct {
@@ -186,19 +306,41 @@ 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"`
Username string `json:"username"`
AuthMethod string `json:"authMethod,omitempty"`
Username string `json:"username,omitempty"`
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"`
AuthMethod string `json:"authMethod,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
} `json:"jumpServers,omitempty"`
}
type AccessConfigForSSLCom struct {
@@ -206,6 +348,11 @@ type AccessConfigForSSLCom struct {
EabHmacKey string `json:"eabHmacKey"`
}
type AccessConfigForTelegramBot struct {
BotToken string `json:"botToken"`
DefaultChatId int64 `json:"defaultChatId,omitempty"`
}
type AccessConfigForTencentCloud struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
@@ -217,6 +364,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"`
@@ -239,13 +391,21 @@ type AccessConfigForWangsu struct {
}
type AccessConfigForWebhook struct {
Url string `json:"url"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
Url string `json:"url"`
Method string `json:"method,omitempty"`
HeadersString string `json:"headers,omitempty"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
DefaultDataForDeployment string `json:"defaultDataForDeployment,omitempty"`
DefaultDataForNotification string `json:"defaultDataForNotification,omitempty"`
}
type AccessConfigForWeComBot struct {
WebhookUrl string `json:"webhookUrl"`
}
type AccessConfigForWestcn struct {
Username string `json:"username"`
ApiPassword string `json:"password"`
ApiPassword string `json:"apiPassword"`
}
type AccessConfigForZeroSSL struct {

View File

@@ -8,7 +8,7 @@ import (
"strings"
"time"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
const CollectionNameCertificate = "certificate"
@@ -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
@@ -105,10 +106,10 @@ func (c *Certificate) PopulateFromPEM(certPEM, privkeyPEM string) *Certificate {
c.Certificate = certPEM
c.PrivateKey = privkeyPEM
_, issuerCertPEM, _ := certutil.ExtractCertificatesFromPEM(certPEM)
_, issuerCertPEM, _ := xcert.ExtractCertificatesFromPEM(certPEM)
c.IssuerCertificate = issuerCertPEM
certX509, _ := certutil.ParseCertificateFromPEM(certPEM)
certX509, _ := xcert.ParseCertificateFromPEM(certPEM)
if certX509 != nil {
c.PopulateFromX509(certX509)
}

View File

@@ -1,4 +1,4 @@
package dtos
package dtos
type CertificateArchiveFileReq struct {
CertificateId string `json:"-"`

View File

@@ -1,6 +1,6 @@
package dtos
package dtos
import "github.com/usual2970/certimate/internal/domain"
import "github.com/certimate-go/certimate/internal/domain"
type NotifyTestPushReq struct {
Channel domain.NotifyChannelType `json:"channel"`

View File

@@ -1,6 +1,6 @@
package dtos
package dtos
import "github.com/usual2970/certimate/internal/domain"
import "github.com/certimate-go/certimate/internal/domain"
type WorkflowStartRunReq struct {
WorkflowId string `json:"-"`

View File

@@ -0,0 +1,630 @@
package expr
import (
"encoding/json"
"fmt"
"strconv"
)
type (
ExprType string
ExprComparisonOperator string
ExprLogicalOperator string
ExprValueType string
)
const (
GreaterThan ExprComparisonOperator = "gt"
GreaterOrEqual ExprComparisonOperator = "gte"
LessThan ExprComparisonOperator = "lt"
LessOrEqual ExprComparisonOperator = "lte"
Equal ExprComparisonOperator = "eq"
NotEqual ExprComparisonOperator = "neq"
And ExprLogicalOperator = "and"
Or ExprLogicalOperator = "or"
Not ExprLogicalOperator = "not"
Number ExprValueType = "number"
String ExprValueType = "string"
Boolean ExprValueType = "boolean"
ConstantExprType ExprType = "const"
VariantExprType ExprType = "var"
ComparisonExprType ExprType = "comparison"
LogicalExprType ExprType = "logical"
NotExprType ExprType = "not"
)
type EvalResult struct {
Type ExprValueType
Value any
}
func (e *EvalResult) GetFloat64() (float64, error) {
if e.Type != Number {
return 0, fmt.Errorf("type mismatch: %s", e.Type)
}
stringValue, ok := e.Value.(string)
if !ok {
return 0, fmt.Errorf("value is not a string: %v", e.Value)
}
floatValue, err := strconv.ParseFloat(stringValue, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse float64: %v", err)
}
return floatValue, nil
}
func (e *EvalResult) GetBool() (bool, error) {
if e.Type != Boolean {
return false, fmt.Errorf("type mismatch: %s", e.Type)
}
strValue, ok := e.Value.(string)
if ok {
if strValue == "true" {
return true, nil
} else if strValue == "false" {
return false, nil
}
return false, fmt.Errorf("value is not a boolean: %v", e.Value)
}
boolValue, ok := e.Value.(bool)
if !ok {
return false, fmt.Errorf("value is not a boolean: %v", e.Value)
}
return boolValue, nil
}
func (e *EvalResult) GreaterThan(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) > other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left > right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) GreaterOrEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) >= other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left >= right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) LessThan(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) < other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left < right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) LessOrEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) <= other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left <= right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) Equal(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) == other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left == right,
}, nil
case Boolean:
left, err := e.GetBool()
if err != nil {
return nil, err
}
right, err := other.GetBool()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left == right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) NotEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case String:
return &EvalResult{
Type: Boolean,
Value: e.Value.(string) != other.Value.(string),
}, nil
case Number:
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left != right,
}, nil
case Boolean:
left, err := e.GetBool()
if err != nil {
return nil, err
}
right, err := other.GetBool()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left != right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) And(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case Boolean:
left, err := e.GetBool()
if err != nil {
return nil, err
}
right, err := other.GetBool()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left && right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) Or(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case Boolean:
left, err := e.GetBool()
if err != nil {
return nil, err
}
right, err := other.GetBool()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: left || right,
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s", e.Type)
}
}
func (e *EvalResult) Not() (*EvalResult, error) {
if e.Type != Boolean {
return nil, fmt.Errorf("type mismatch: %s", e.Type)
}
boolValue, err := e.GetBool()
if err != nil {
return nil, err
}
return &EvalResult{
Type: Boolean,
Value: !boolValue,
}, nil
}
type Expr interface {
GetType() ExprType
Eval(variables map[string]map[string]any) (*EvalResult, error)
}
type ExprValueSelector struct {
Id string `json:"id"`
Name string `json:"name"`
Type ExprValueType `json:"type"`
}
type ConstantExpr struct {
Type ExprType `json:"type"`
Value string `json:"value"`
ValueType ExprValueType `json:"valueType"`
}
func (c ConstantExpr) GetType() ExprType { return c.Type }
func (c ConstantExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
return &EvalResult{
Type: c.ValueType,
Value: c.Value,
}, nil
}
type VariantExpr struct {
Type ExprType `json:"type"`
Selector ExprValueSelector `json:"selector"`
}
func (v VariantExpr) GetType() ExprType { return v.Type }
func (v VariantExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
if v.Selector.Id == "" {
return nil, fmt.Errorf("node id is empty")
}
if v.Selector.Name == "" {
return nil, fmt.Errorf("name is empty")
}
if _, ok := variables[v.Selector.Id]; !ok {
return nil, fmt.Errorf("node %s not found", v.Selector.Id)
}
if _, ok := variables[v.Selector.Id][v.Selector.Name]; !ok {
return nil, fmt.Errorf("variable %s not found in node %s", v.Selector.Name, v.Selector.Id)
}
return &EvalResult{
Type: v.Selector.Type,
Value: variables[v.Selector.Id][v.Selector.Name],
}, nil
}
type ComparisonExpr struct {
Type ExprType `json:"type"` // compare
Operator ExprComparisonOperator `json:"operator"`
Left Expr `json:"left"`
Right Expr `json:"right"`
}
func (c ComparisonExpr) GetType() ExprType { return c.Type }
func (c ComparisonExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
left, err := c.Left.Eval(variables)
if err != nil {
return nil, err
}
right, err := c.Right.Eval(variables)
if err != nil {
return nil, err
}
switch c.Operator {
case GreaterThan:
return left.GreaterThan(right)
case LessThan:
return left.LessThan(right)
case GreaterOrEqual:
return left.GreaterOrEqual(right)
case LessOrEqual:
return left.LessOrEqual(right)
case Equal:
return left.Equal(right)
case NotEqual:
return left.NotEqual(right)
default:
return nil, fmt.Errorf("unknown expression operator: %s", c.Operator)
}
}
type LogicalExpr struct {
Type ExprType `json:"type"` // logical
Operator ExprLogicalOperator `json:"operator"`
Left Expr `json:"left"`
Right Expr `json:"right"`
}
func (l LogicalExpr) GetType() ExprType { return l.Type }
func (l LogicalExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
left, err := l.Left.Eval(variables)
if err != nil {
return nil, err
}
right, err := l.Right.Eval(variables)
if err != nil {
return nil, err
}
switch l.Operator {
case And:
return left.And(right)
case Or:
return left.Or(right)
default:
return nil, fmt.Errorf("unknown expression operator: %s", l.Operator)
}
}
type NotExpr struct {
Type ExprType `json:"type"` // not
Expr Expr `json:"expr"`
}
func (n NotExpr) GetType() ExprType { return n.Type }
func (n NotExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
inner, err := n.Expr.Eval(variables)
if err != nil {
return nil, err
}
return inner.Not()
}
type rawExpr struct {
Type ExprType `json:"type"`
}
func MarshalExpr(e Expr) ([]byte, error) {
return json.Marshal(e)
}
func UnmarshalExpr(data []byte) (Expr, error) {
var typ rawExpr
if err := json.Unmarshal(data, &typ); err != nil {
return nil, err
}
switch typ.Type {
case ConstantExprType:
var e ConstantExpr
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
return e, nil
case VariantExprType:
var e VariantExpr
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
return e, nil
case ComparisonExprType:
var e ComparisonExprRaw
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
return e.ToComparisonExpr()
case LogicalExprType:
var e LogicalExprRaw
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
return e.ToLogicalExpr()
case NotExprType:
var e NotExprRaw
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
return e.ToNotExpr()
default:
return nil, fmt.Errorf("unknown expression type: %s", typ.Type)
}
}
type ComparisonExprRaw struct {
Type ExprType `json:"type"`
Operator ExprComparisonOperator `json:"operator"`
Left json.RawMessage `json:"left"`
Right json.RawMessage `json:"right"`
}
func (r ComparisonExprRaw) ToComparisonExpr() (ComparisonExpr, error) {
leftExpr, err := UnmarshalExpr(r.Left)
if err != nil {
return ComparisonExpr{}, err
}
rightExpr, err := UnmarshalExpr(r.Right)
if err != nil {
return ComparisonExpr{}, err
}
return ComparisonExpr{
Type: r.Type,
Operator: r.Operator,
Left: leftExpr,
Right: rightExpr,
}, nil
}
type LogicalExprRaw struct {
Type ExprType `json:"type"`
Operator ExprLogicalOperator `json:"operator"`
Left json.RawMessage `json:"left"`
Right json.RawMessage `json:"right"`
}
func (r LogicalExprRaw) ToLogicalExpr() (LogicalExpr, error) {
left, err := UnmarshalExpr(r.Left)
if err != nil {
return LogicalExpr{}, err
}
right, err := UnmarshalExpr(r.Right)
if err != nil {
return LogicalExpr{}, err
}
return LogicalExpr{
Type: r.Type,
Operator: r.Operator,
Left: left,
Right: right,
}, nil
}
type NotExprRaw struct {
Type ExprType `json:"type"`
Expr json.RawMessage `json:"expr"`
}
func (r NotExprRaw) ToNotExpr() (NotExpr, error) {
inner, err := UnmarshalExpr(r.Expr)
if err != nil {
return NotExpr{}, err
}
return NotExpr{
Type: r.Type,
Expr: inner,
}, nil
}

View File

@@ -0,0 +1,127 @@
package expr
import (
"testing"
)
func TestLogicalEval(t *testing.T) {
// 测试逻辑表达式 and
logicalExpr := LogicalExpr{
Left: ConstantExpr{
Type: "const",
Value: "true",
ValueType: "boolean",
},
Operator: And,
Right: ConstantExpr{
Type: "const",
Value: "true",
ValueType: "boolean",
},
}
result, err := logicalExpr.Eval(nil)
if err != nil {
t.Errorf("failed to evaluate logical expression: %v", err)
}
if result.Value != true {
t.Errorf("expected true, got %v", result)
}
// 测试逻辑表达式 or
orExpr := LogicalExpr{
Left: ConstantExpr{
Type: "const",
Value: "true",
ValueType: "boolean",
},
Operator: Or,
Right: ConstantExpr{
Type: "const",
Value: "true",
ValueType: "boolean",
},
}
result, err = orExpr.Eval(nil)
if err != nil {
t.Errorf("failed to evaluate logical expression: %v", err)
}
if result.Value != true {
t.Errorf("expected true, got %v", result)
}
}
func TestUnmarshalExpr(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want Expr
wantErr bool
}{
{
name: "test1",
args: args{
data: []byte(`{"left":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.validity","type":"boolean"},"type":"var"},"operator":"is","right":{"type":"const","value":true,"valueType":"boolean"},"type":"comparison"},"operator":"and","right":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.daysLeft","type":"number"},"type":"var"},"operator":"eq","right":{"type":"const","value":2,"valueType":"number"},"type":"comparison"},"type":"logical"}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := UnmarshalExpr(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalExpr() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got == nil {
t.Errorf("UnmarshalExpr() got = nil, want %v", tt.want)
return
}
})
}
}
func TestExpr_Eval(t *testing.T) {
type args struct {
variables map[string]map[string]any
data []byte
}
tests := []struct {
name string
args args
want *EvalResult
wantErr bool
}{
{
name: "test1",
args: args{
variables: map[string]map[string]any{
"ODnYSOXB6HQP2_vz6JcZE": {
"certificate.validity": true,
"certificate.daysLeft": 2,
},
},
data: []byte(`{"left":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.validity","type":"boolean"},"type":"var"},"operator":"is","right":{"type":"const","value":true,"valueType":"boolean"},"type":"comparison"},"operator":"and","right":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.daysLeft","type":"number"},"type":"var"},"operator":"eq","right":{"type":"const","value":2,"valueType":"number"},"type":"comparison"},"type":"logical"}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, err := UnmarshalExpr(tt.args.data)
if err != nil {
t.Errorf("UnmarshalExpr() error = %v", err)
return
}
got, err := c.Eval(tt.args.variables)
t.Log("got:", got)
if (err != nil) != tt.wantErr {
t.Errorf("ConstExpr.Eval() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got.Value != true {
t.Errorf("ConstExpr.Eval() got = %v, want %v", got.Value, true)
}
})
}
}

View File

@@ -8,6 +8,7 @@ type NotifyChannelType string
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
// Deprecated: v0.4.x 将废弃
const (
NotifyChannelTypeBark = NotifyChannelType("bark")
NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk")

View File

@@ -1,4 +1,4 @@
package domain
package domain
type AccessProviderType string
@@ -10,131 +10,166 @@ type AccessProviderType string
*/
const (
AccessProviderType1Panel = AccessProviderType("1panel")
AccessProviderTypeACMECA = AccessProviderType("acmeca")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai预留
AccessProviderTypeAliyun = AccessProviderType("aliyun")
AccessProviderTypeAPISIX = AccessProviderType("apisix")
AccessProviderTypeAWS = AccessProviderType("aws")
AccessProviderTypeAzure = AccessProviderType("azure")
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBaishan = AccessProviderType("baishan")
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeBunny = AccessProviderType("bunny")
AccessProviderTypeBuypass = AccessProviderType("buypass")
AccessProviderTypeCacheFly = AccessProviderType("cachefly")
AccessProviderTypeCdnfly = AccessProviderType("cdnfly")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留)
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留)
AccessProviderTypeConstellix = AccessProviderType("constellix")
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")
AccessProviderTypeGname = AccessProviderType("gname")
AccessProviderTypeGcore = AccessProviderType("gcore")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge预留
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")
AccessProviderTypeLocal = AccessProviderType("local")
AccessProviderTypeMattermost = AccessProviderType("mattermost")
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
AccessProviderTypeNetcup = AccessProviderType("netcup")
AccessProviderTypeNetlify = AccessProviderType("netlify")
AccessProviderTypeNS1 = AccessProviderType("ns1")
AccessProviderTypePorkbun = AccessProviderType("porkbun")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSlackBot = AccessProviderType("slackbot")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeSSLCOM = AccessProviderType("sslcom")
AccessProviderTypeTelegramBot = AccessProviderType("telegrambot")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
AccessProviderTypeUniCloud = AccessProviderType("unicloud")
AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWangsu = AccessProviderType("wangsu")
AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWeComBot = AccessProviderType("wecombot")
AccessProviderTypeWestcn = AccessProviderType("westcn")
AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
)
type ApplyCAProviderType string
type CAProviderType string
/*
申请证书 CA 提供商常量值。
始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
ApplyCAProviderTypeBuypass = ApplyCAProviderType(string(AccessProviderTypeBuypass))
ApplyCAProviderTypeGoogleTrustServices = ApplyCAProviderType(string(AccessProviderTypeGoogleTrustServices))
ApplyCAProviderTypeLetsEncrypt = ApplyCAProviderType(string(AccessProviderTypeLetsEncrypt))
ApplyCAProviderTypeLetsEncryptStaging = ApplyCAProviderType(string(AccessProviderTypeLetsEncryptStaging))
ApplyCAProviderTypeSSLCom = ApplyCAProviderType(string(AccessProviderTypeSSLCOM))
ApplyCAProviderTypeZeroSSL = ApplyCAProviderType(string(AccessProviderTypeZeroSSL))
)
type ApplyDNSProviderType string
/*
申请证书 DNS 提供商常量值。
证书颁发机构提供商常量值。
短横线前的部分始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
ApplyDNSProviderTypeACMEHttpReq = ApplyDNSProviderType("acmehttpreq")
ApplyDNSProviderTypeAliyun = ApplyDNSProviderType("aliyun") // 兼容旧值,等同于 [ApplyDNSProviderTypeAliyunDNS]
ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType("aliyun-dns")
ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53]
ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53")
ApplyDNSProviderTypeAzure = ApplyDNSProviderType("azure") // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure]
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS]
ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns")
ApplyDNSProviderTypeBunny = ApplyDNSProviderType("bunny")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud")
ApplyDNSProviderTypeDeSEC = ApplyDNSProviderType("desec")
ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla")
ApplyDNSProviderTypeDynv6 = ApplyDNSProviderType("dynv6")
ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore")
ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname")
ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy")
ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS]
ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns")
ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS]
ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns")
ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType("namecheap")
ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom")
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
ApplyDNSProviderTypePorkbun = ApplyDNSProviderType("porkbun")
ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns")
ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun")
ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS]
ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns")
ApplyDNSProviderTypeTencentCloudEO = ApplyDNSProviderType("tencentcloud-eo")
ApplyDNSProviderTypeVercel = ApplyDNSProviderType("vercel")
ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS]
ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns")
ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn")
CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA)
CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass)
CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices)
CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt)
CAProviderTypeLetsEncryptStaging = CAProviderType(AccessProviderTypeLetsEncryptStaging)
CAProviderTypeSSLCom = CAProviderType(AccessProviderTypeSSLCOM)
CAProviderTypeZeroSSL = CAProviderType(AccessProviderTypeZeroSSL)
)
type DeployProviderType string
type ACMEDns01ProviderType string
/*
ACME DNS-01 提供商常量值。
短横线前的部分始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq)
ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS]
ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns")
ACMEDns01ProviderTypeAliyunESA = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-esa")
ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53]
ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53")
ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure]
ACMEDns01ProviderTypeAzureDNS = ACMEDns01ProviderType(AccessProviderTypeAzure + "-dns")
ACMEDns01ProviderTypeBaiduCloud = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeBaiduCloudDNS]
ACMEDns01ProviderTypeBaiduCloudDNS = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud + "-dns")
ACMEDns01ProviderTypeBunny = ACMEDns01ProviderType(AccessProviderTypeBunny)
ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare)
ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCMCCCloudDNS]
ACMEDns01ProviderTypeCMCCCloudDNS = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud + "-dns")
ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix)
ACMEDns01ProviderTypeCTCCCloud = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCTCCCloudSmartDNS]
ACMEDns01ProviderTypeCTCCCloudSmartDNS = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud + "-smartdns")
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]
ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns")
ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap)
ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom)
ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo)
ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup)
ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify)
ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns")
ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo")
ACMEDns01ProviderTypeUCloudUDNR = ACMEDns01ProviderType(AccessProviderTypeUCloud + "-udnr")
ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
)
type DeploymentProviderType string
/*
部署证书主机提供商常量值。
@@ -144,78 +179,122 @@ type DeployProviderType string
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
DeployProviderType1PanelConsole = DeployProviderType("1panel-console")
DeployProviderType1PanelSite = DeployProviderType("1panel-site")
DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
DeployProviderTypeAliyunAPIGW = DeployProviderType("aliyun-apigw")
DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas")
DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy")
DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb")
DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn")
DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa")
DeployProviderTypeAliyunFC = DeployProviderType("aliyun-fc")
DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
DeployProviderTypeAliyunVOD = DeployProviderType("aliyun-vod")
DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf")
DeployProviderTypeAWSACM = DeployProviderType("aws-acm")
DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront")
DeployProviderTypeAzureKeyVault = DeployProviderType("azure-keyvault")
DeployProviderTypeBaiduCloudAppBLB = DeployProviderType("baiducloud-appblb")
DeployProviderTypeBaiduCloudBLB = DeployProviderType("baiducloud-blb")
DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn")
DeployProviderTypeBaiduCloudCert = DeployProviderType("baiducloud-cert")
DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn")
DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console")
DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site")
DeployProviderTypeBunnyCDN = DeployProviderType("bunny-cdn")
DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
DeployProviderTypeCacheFly = DeployProviderType("cachefly")
DeployProviderTypeCdnfly = DeployProviderType("cdnfly")
DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn")
DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications")
DeployProviderTypeGcoreCDN = DeployProviderType("gcore-cdn")
DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn")
DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb")
DeployProviderTypeHuaweiCloudSCM = DeployProviderType("huaweicloud-scm")
DeployProviderTypeHuaweiCloudWAF = DeployProviderType("huaweicloud-waf")
DeployProviderTypeJDCloudALB = DeployProviderType("jdcloud-alb")
DeployProviderTypeJDCloudCDN = DeployProviderType("jdcloud-cdn")
DeployProviderTypeJDCloudLive = DeployProviderType("jdcloud-live")
DeployProviderTypeJDCloudVOD = DeployProviderType("jdcloud-vod")
DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret")
DeployProviderTypeLocal = DeployProviderType("local")
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo")
DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn")
DeployProviderTypeSafeLine = DeployProviderType("safeline")
DeployProviderTypeSSH = DeployProviderType("ssh")
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb")
DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos")
DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css")
DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn")
DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo")
DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf")
DeployProviderTypeTencentCloudSSL = DeployProviderType("tencentcloud-ssl")
DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy")
DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod")
DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf")
DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn")
DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3")
DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn")
DeployProviderTypeUpyunFile = DeployProviderType("upyun-file")
DeployProviderTypeVolcEngineALB = DeployProviderType("volcengine-alb")
DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn")
DeployProviderTypeVolcEngineCertCenter = DeployProviderType("volcengine-certcenter")
DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb")
DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn")
DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex")
DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro")
DeployProviderTypeWebhook = DeployProviderType("webhook")
DeploymentProviderType1PanelConsole = DeploymentProviderType(AccessProviderType1Panel + "-console")
DeploymentProviderType1PanelSite = DeploymentProviderType(AccessProviderType1Panel + "-site")
DeploymentProviderTypeAliyunALB = DeploymentProviderType(AccessProviderTypeAliyun + "-alb")
DeploymentProviderTypeAliyunAPIGW = DeploymentProviderType(AccessProviderTypeAliyun + "-apigw")
DeploymentProviderTypeAliyunCAS = DeploymentProviderType(AccessProviderTypeAliyun + "-cas")
DeploymentProviderTypeAliyunCASDeploy = DeploymentProviderType(AccessProviderTypeAliyun + "-casdeploy")
DeploymentProviderTypeAliyunCDN = DeploymentProviderType(AccessProviderTypeAliyun + "-cdn")
DeploymentProviderTypeAliyunCLB = DeploymentProviderType(AccessProviderTypeAliyun + "-clb")
DeploymentProviderTypeAliyunDCDN = DeploymentProviderType(AccessProviderTypeAliyun + "-dcdn")
DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos")
DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa")
DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc")
DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga")
DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live")
DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb")
DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss")
DeploymentProviderTypeAliyunVOD = DeploymentProviderType(AccessProviderTypeAliyun + "-vod")
DeploymentProviderTypeAliyunWAF = DeploymentProviderType(AccessProviderTypeAliyun + "-waf")
DeploymentProviderTypeAPISIX = DeploymentProviderType(AccessProviderTypeAWS + "-apisix")
DeploymentProviderTypeAWSACM = DeploymentProviderType(AccessProviderTypeAWS + "-acm")
DeploymentProviderTypeAWSCloudFront = DeploymentProviderType(AccessProviderTypeAWS + "-cloudfront")
DeploymentProviderTypeAWSIAM = DeploymentProviderType(AccessProviderTypeAWS + "-iam")
DeploymentProviderTypeAzureKeyVault = DeploymentProviderType(AccessProviderTypeAzure + "-keyvault")
DeploymentProviderTypeBaiduCloudAppBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-appblb")
DeploymentProviderTypeBaiduCloudBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-blb")
DeploymentProviderTypeBaiduCloudCDN = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-cdn")
DeploymentProviderTypeBaiduCloudCert = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-cert")
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)
DeploymentProviderTypeCTCCCloudAO = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-ao")
DeploymentProviderTypeCTCCCloudCDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-cdn")
DeploymentProviderTypeCTCCCloudCMS = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-cms")
DeploymentProviderTypeCTCCCloudELB = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-elb")
DeploymentProviderTypeCTCCCloudICDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-icdn")
DeploymentProviderTypeCTCCCloudLVDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-ldvn")
DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn")
DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications")
DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN)
DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn")
DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge)
DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn")
DeploymentProviderTypeHuaweiCloudELB = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-elb")
DeploymentProviderTypeHuaweiCloudSCM = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-scm")
DeploymentProviderTypeHuaweiCloudWAF = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-waf")
DeploymentProviderTypeJDCloudALB = DeploymentProviderType(AccessProviderTypeJDCloud + "-alb")
DeploymentProviderTypeJDCloudCDN = DeploymentProviderType(AccessProviderTypeJDCloud + "-cdn")
DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live")
DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod")
DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret")
DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN)
DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal)
DeploymentProviderTypeNetlifySite = DeploymentProviderType(AccessProviderTypeNetlify + "-site")
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn")
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")
DeploymentProviderTypeTencentCloudCLB = DeploymentProviderType(AccessProviderTypeTencentCloud + "-clb")
DeploymentProviderTypeTencentCloudCOS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cos")
DeploymentProviderTypeTencentCloudCSS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-css")
DeploymentProviderTypeTencentCloudECDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ecdn")
DeploymentProviderTypeTencentCloudEO = DeploymentProviderType(AccessProviderTypeTencentCloud + "-eo")
DeploymentProviderTypeTencentCloudGAAP = DeploymentProviderType(AccessProviderTypeTencentCloud + "-gaap")
DeploymentProviderTypeTencentCloudSCF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-scf")
DeploymentProviderTypeTencentCloudSSL = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssl")
DeploymentProviderTypeTencentCloudSSLDeploy = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssldeploy")
DeploymentProviderTypeTencentCloudVOD = DeploymentProviderType(AccessProviderTypeTencentCloud + "-vod")
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")
DeploymentProviderTypeVolcEngineCDN = DeploymentProviderType(AccessProviderTypeVolcEngine + "-cdn")
DeploymentProviderTypeVolcEngineCertCenter = DeploymentProviderType(AccessProviderTypeVolcEngine + "-certcenter")
DeploymentProviderTypeVolcEngineCLB = DeploymentProviderType(AccessProviderTypeVolcEngine + "-clb")
DeploymentProviderTypeVolcEngineDCDN = DeploymentProviderType(AccessProviderTypeVolcEngine + "-dcdn")
DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex")
DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live")
DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos")
DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn")
DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro")
DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate")
DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook)
)
type NotificationProviderType string
/*
消息通知提供商常量值。
短横线前的部分始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
NotificationProviderTypeDingTalkBot = NotificationProviderType(AccessProviderTypeDingTalkBot)
NotificationProviderTypeDiscordBot = NotificationProviderType(AccessProviderTypeDiscordBot)
NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail)
NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot)
NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost)
NotificationProviderTypeSlackBot = NotificationProviderType(AccessProviderTypeSlackBot)
NotificationProviderTypeTelegramBot = NotificationProviderType(AccessProviderTypeTelegramBot)
NotificationProviderTypeWebhook = NotificationProviderType(AccessProviderTypeWebhook)
NotificationProviderTypeWeComBot = NotificationProviderType(AccessProviderTypeWeComBot)
)

View File

@@ -13,6 +13,7 @@ type Settings struct {
Content string `json:"content" db:"content"`
}
// Deprecated: v0.4.x 将废弃
type NotifyTemplatesSettingsContent struct {
NotifyTemplates []struct {
Subject string `json:"subject"`
@@ -20,8 +21,10 @@ type NotifyTemplatesSettingsContent struct {
} `json:"notifyTemplates"`
}
// Deprecated: v0.4.x 将废弃
type NotifyChannelsSettingsContent map[string]map[string]any
// Deprecated: v0.4.x 将废弃
func (s *Settings) GetNotifyChannelConfig(channel string) (map[string]any, error) {
conf := &NotifyChannelsSettingsContent{}
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {

View File

@@ -1,9 +1,11 @@
package domain
import (
"encoding/json"
"time"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/certimate-go/certimate/internal/domain/expr"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
const CollectionNameWorkflow = "workflow"
@@ -30,6 +32,7 @@ const (
WorkflowNodeTypeEnd = WorkflowNodeType("end")
WorkflowNodeTypeApply = WorkflowNodeType("apply")
WorkflowNodeTypeUpload = WorkflowNodeType("upload")
WorkflowNodeTypeMonitor = WorkflowNodeType("monitor")
WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
WorkflowNodeTypeNotify = WorkflowNodeType("notify")
WorkflowNodeTypeBranch = WorkflowNodeType("branch")
@@ -68,22 +71,30 @@ type WorkflowNodeConfigForApply struct {
Provider string `json:"provider"` // DNS 提供商
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值使用全局配置)
CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值使用全局配置)
CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID
CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置
KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法
KeyAlgorithm string `json:"keyAlgorithm"` // 证书算法
Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` // DNS 传播超时时间(零值取决于提供商的默认值)
DnsTTL int32 `json:"dnsTTL,omitempty"` // DNS TTL零值取决于提供商的默认值)
DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` // DNS 传播检查超时时间(零值时使用提供商的默认值)
DnsTTL int32 `json:"dnsTTL,omitempty"` // DNS 解析记录 TTL零值时使用提供商的默认值
DisableFollowCNAME bool `json:"disableFollowCNAME,omitempty"` // 是否关闭 CNAME 跟随
DisableARI bool `json:"disableARI,omitempty"` // 是否关闭 ARI
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays,omitempty"` // 证书到期前多少天前跳过续期(零值将使用默认值 30
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays,omitempty"` // 证书到期前多少天前跳过续期(零值默认值 30
}
type WorkflowNodeConfigForUpload struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Domains string `json:"domains"`
Certificate string `json:"certificate"` // 证书 PEM 内容
PrivateKey string `json:"privateKey"` // 私钥 PEM 内容
Domains string `json:"domains,omitempty"`
}
type WorkflowNodeConfigForMonitor struct {
Host string `json:"host"` // 主机地址
Port int32 `json:"port,omitempty"` // 端口(零值时默认值 443
Domain string `json:"domain,omitempty"` // 域名(零值时默认值 [Host]
RequestPath string `json:"requestPath,omitempty"` // 请求路径
}
type WorkflowNodeConfigForDeploy struct {
@@ -95,59 +106,94 @@ type WorkflowNodeConfigForDeploy struct {
}
type WorkflowNodeConfigForNotify struct {
Channel string `json:"channel"` // 通知渠道
Subject string `json:"subject"` // 通知主题
Message string `json:"message"` // 通知内容
Channel string `json:"channel,omitempty"` // Deprecated: v0.4.x 将废弃
Provider string `json:"provider"` // 通知提供商
ProviderAccessId string `json:"providerAccessId"` // 通知提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 通知提供商额外配置
Subject string `json:"subject"` // 通知主题
Message string `json:"message"` // 通知内容
SkipOnAllPrevSkipped bool `json:"skipOnAllPrevSkipped"` // 前序节点均已跳过时是否跳过
}
type WorkflowNodeConfigForCondition struct {
Expression expr.Expr `json:"expression"` // 条件表达式
}
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"),
Provider: maputil.GetString(n.Config, "provider"),
ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"),
ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"),
CAProvider: maputil.GetString(n.Config, "caProvider"),
CAProviderAccessId: maputil.GetString(n.Config, "caProviderAccessId"),
CAProviderConfig: maputil.GetAnyMap(n.Config, "caProviderConfig"),
KeyAlgorithm: maputil.GetString(n.Config, "keyAlgorithm"),
Nameservers: maputil.GetString(n.Config, "nameservers"),
DnsPropagationTimeout: maputil.GetInt32(n.Config, "dnsPropagationTimeout"),
DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"),
DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"),
DisableARI: maputil.GetBool(n.Config, "disableARI"),
SkipBeforeExpiryDays: skipBeforeExpiryDays,
Domains: xmaps.GetString(n.Config, "domains"),
ContactEmail: xmaps.GetString(n.Config, "contactEmail"),
Provider: xmaps.GetString(n.Config, "provider"),
ProviderAccessId: xmaps.GetString(n.Config, "providerAccessId"),
ProviderConfig: xmaps.GetKVMapAny(n.Config, "providerConfig"),
CAProvider: xmaps.GetString(n.Config, "caProvider"),
CAProviderAccessId: xmaps.GetString(n.Config, "caProviderAccessId"),
CAProviderConfig: xmaps.GetKVMapAny(n.Config, "caProviderConfig"),
KeyAlgorithm: xmaps.GetOrDefaultString(n.Config, "keyAlgorithm", string(CertificateKeyAlgorithmTypeRSA2048)),
Nameservers: xmaps.GetString(n.Config, "nameservers"),
DnsPropagationWait: xmaps.GetInt32(n.Config, "dnsPropagationWait"),
DnsPropagationTimeout: xmaps.GetInt32(n.Config, "dnsPropagationTimeout"),
DnsTTL: xmaps.GetInt32(n.Config, "dnsTTL"),
DisableFollowCNAME: xmaps.GetBool(n.Config, "disableFollowCNAME"),
DisableARI: xmaps.GetBool(n.Config, "disableARI"),
SkipBeforeExpiryDays: xmaps.GetOrDefaultInt32(n.Config, "skipBeforeExpiryDays", 30),
}
}
func (n *WorkflowNode) GetConfigForUpload() WorkflowNodeConfigForUpload {
return WorkflowNodeConfigForUpload{
Certificate: maputil.GetString(n.Config, "certificate"),
PrivateKey: maputil.GetString(n.Config, "privateKey"),
Domains: maputil.GetString(n.Config, "domains"),
Certificate: xmaps.GetString(n.Config, "certificate"),
PrivateKey: xmaps.GetString(n.Config, "privateKey"),
Domains: xmaps.GetString(n.Config, "domains"),
}
}
func (n *WorkflowNode) GetConfigForMonitor() WorkflowNodeConfigForMonitor {
host := xmaps.GetString(n.Config, "host")
return WorkflowNodeConfigForMonitor{
Host: host,
Port: xmaps.GetOrDefaultInt32(n.Config, "port", 443),
Domain: xmaps.GetOrDefaultString(n.Config, "domain", host),
RequestPath: xmaps.GetString(n.Config, "path"),
}
}
func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy {
return WorkflowNodeConfigForDeploy{
Certificate: maputil.GetString(n.Config, "certificate"),
Provider: maputil.GetString(n.Config, "provider"),
ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"),
ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"),
SkipOnLastSucceeded: maputil.GetBool(n.Config, "skipOnLastSucceeded"),
Certificate: xmaps.GetString(n.Config, "certificate"),
Provider: xmaps.GetString(n.Config, "provider"),
ProviderAccessId: xmaps.GetString(n.Config, "providerAccessId"),
ProviderConfig: xmaps.GetKVMapAny(n.Config, "providerConfig"),
SkipOnLastSucceeded: xmaps.GetBool(n.Config, "skipOnLastSucceeded"),
}
}
func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify {
return WorkflowNodeConfigForNotify{
Channel: maputil.GetString(n.Config, "channel"),
Subject: maputil.GetString(n.Config, "subject"),
Message: maputil.GetString(n.Config, "message"),
Channel: xmaps.GetString(n.Config, "channel"),
Provider: xmaps.GetString(n.Config, "provider"),
ProviderAccessId: xmaps.GetString(n.Config, "providerAccessId"),
ProviderConfig: xmaps.GetKVMapAny(n.Config, "providerConfig"),
Subject: xmaps.GetString(n.Config, "subject"),
Message: xmaps.GetString(n.Config, "message"),
SkipOnAllPrevSkipped: xmaps.GetBool(n.Config, "skipOnAllPrevSkipped"),
}
}
func (n *WorkflowNode) GetConfigForCondition() WorkflowNodeConfigForCondition {
expression := n.Config["expression"]
if expression == nil {
return WorkflowNodeConfigForCondition{}
}
exprRaw, _ := json.Marshal(expression)
expr, err := expr.UnmarshalExpr([]byte(exprRaw))
if err != nil {
return WorkflowNodeConfigForCondition{}
}
return WorkflowNodeConfigForCondition{
Expression: expr,
}
}
@@ -160,9 +206,6 @@ type WorkflowNodeIO struct {
ValueSelector WorkflowNodeIOValueSelector `json:"valueSelector"`
}
type WorkflowNodeIOValueSelector struct {
Id string `json:"id"`
Name string `json:"name"`
}
type WorkflowNodeIOValueSelector = expr.ExprValueSelector
const WorkflowNodeIONameCertificate string = "certificate"

View File

@@ -0,0 +1,74 @@
package notify
import (
"context"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/repository"
"github.com/certimate-go/certimate/pkg/core"
)
type Notifier interface {
Notify(ctx context.Context) error
}
type NotifierWithWorkflowNodeConfig struct {
Node *domain.WorkflowNode
Logger *slog.Logger
Subject string
Message string
}
func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error) {
if config.Node == nil {
return nil, fmt.Errorf("node is nil")
}
if config.Node.Type != domain.WorkflowNodeTypeNotify {
return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeNotify))
}
nodeCfg := config.Node.GetConfigForNotify()
options := &notifierProviderOptions{
Provider: domain.NotificationProviderType(nodeCfg.Provider),
ProviderAccessConfig: make(map[string]any),
ProviderServiceConfig: nodeCfg.ProviderConfig,
}
accessRepo := repository.NewAccessRepository()
if nodeCfg.ProviderAccessId != "" {
access, err := accessRepo.GetById(context.Background(), nodeCfg.ProviderAccessId)
if err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeCfg.ProviderAccessId, err)
} else {
options.ProviderAccessConfig = access.Config
}
}
notifier, err := createNotifierProvider(options)
if err != nil {
return nil, err
} else {
notifier.SetLogger(config.Logger)
}
return &notifierImpl{
provider: notifier,
subject: config.Subject,
message: config.Message,
}, nil
}
type notifierImpl struct {
provider core.Notifier
subject string
message string
}
var _ Notifier = (*notifierImpl)(nil)
func (n *notifierImpl) Notify(ctx context.Context) error {
_, err := n.provider.Notify(ctx, n.subject, n.message)
return err
}

View File

@@ -7,12 +7,13 @@ import (
"golang.org/x/sync/errgroup"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/usual2970/certimate/internal/repository"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/repository"
"github.com/certimate-go/certimate/pkg/core"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
// Deprecated: v0.4.x 将废弃
func SendToAllChannels(subject, message string) error {
notifiers, err := getEnabledNotifiers()
if err != nil {
@@ -38,8 +39,9 @@ func SendToAllChannels(subject, message string) error {
return err
}
// Deprecated: v0.4.x 将废弃
func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error {
notifier, err := createNotifier(domain.NotifyChannelType(channel), channelConfig)
notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(channel), channelConfig)
if err != nil {
return err
}
@@ -48,7 +50,8 @@ func SendToChannel(subject, message string, channel string, channelConfig map[st
return err
}
func getEnabledNotifiers() ([]notifier.Notifier, error) {
// Deprecated: v0.4.x 将废弃
func getEnabledNotifiers() ([]core.Notifier, error) {
settingsRepo := repository.NewSettingsRepository()
settings, err := settingsRepo.GetByName(context.Background(), "notifyChannels")
if err != nil {
@@ -60,13 +63,13 @@ func getEnabledNotifiers() ([]notifier.Notifier, error) {
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
}
notifiers := make([]notifier.Notifier, 0)
notifiers := make([]core.Notifier, 0)
for k, v := range rs {
if !maputil.GetBool(v, "enabled") {
if !xmaps.GetBool(v, "enabled") {
continue
}
notifier, err := createNotifier(domain.NotifyChannelType(k), v)
notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(k), v)
if err != nil {
continue
}

View File

@@ -2,105 +2,181 @@ package notify
import (
"fmt"
"net/http"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
pDingTalkBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/dingtalkbot"
pDiscordBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/discordbot"
pEmail "github.com/certimate-go/certimate/pkg/core/notifier/providers/email"
pLarkBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/larkbot"
pMattermost "github.com/certimate-go/certimate/pkg/core/notifier/providers/mattermost"
pSlackBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/slackbot"
pTelegramBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/telegrambot"
pWebhook "github.com/certimate-go/certimate/pkg/core/notifier/providers/webhook"
pWeComBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/wecombot"
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) {
type notifierProviderOptions struct {
Provider domain.NotificationProviderType
ProviderAccessConfig map[string]any
ProviderServiceConfig map[string]any
}
func createNotifierProvider(options *notifierProviderOptions) (core.Notifier, error) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch channel {
case domain.NotifyChannelTypeBark:
return pBark.NewNotifier(&pBark.NotifierConfig{
DeviceKey: maputil.GetString(channelConfig, "deviceKey"),
ServerUrl: maputil.GetString(channelConfig, "serverUrl"),
})
switch options.Provider {
case domain.NotificationProviderTypeDingTalkBot:
{
access := domain.AccessConfigForDingTalkBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
case domain.NotifyChannelTypeDingTalk:
return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{
AccessToken: maputil.GetString(channelConfig, "accessToken"),
Secret: maputil.GetString(channelConfig, "secret"),
})
return pDingTalkBot.NewNotifierProvider(&pDingTalkBot.NotifierProviderConfig{
WebhookUrl: access.WebhookUrl,
Secret: access.Secret,
})
}
case domain.NotifyChannelTypeEmail:
return pEmail.NewNotifier(&pEmail.NotifierConfig{
SmtpHost: maputil.GetString(channelConfig, "smtpHost"),
SmtpPort: maputil.GetInt32(channelConfig, "smtpPort"),
SmtpTLS: maputil.GetOrDefaultBool(channelConfig, "smtpTLS", true),
Username: maputil.GetOrDefaultString(channelConfig, "username", maputil.GetString(channelConfig, "senderAddress")),
Password: maputil.GetString(channelConfig, "password"),
SenderAddress: maputil.GetString(channelConfig, "senderAddress"),
ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"),
})
case domain.NotificationProviderTypeDiscordBot:
{
access := domain.AccessConfigForDiscordBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
case domain.NotifyChannelTypeGotify:
return pGotify.NewNotifier(&pGotify.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
Token: maputil.GetString(channelConfig, "token"),
Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1),
})
return pDiscordBot.NewNotifierProvider(&pDiscordBot.NotifierProviderConfig{
BotToken: access.BotToken,
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotifyChannelTypeLark:
return pLark.NewNotifier(&pLark.NotifierConfig{
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
})
case domain.NotificationProviderTypeEmail:
{
access := domain.AccessConfigForEmail{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
case domain.NotifyChannelTypeMattermost:
return pMattermost.NewNotifier(&pMattermost.NotifierConfig{
ServerUrl: maputil.GetString(channelConfig, "serverUrl"),
ChannelId: maputil.GetString(channelConfig, "channelId"),
Username: maputil.GetString(channelConfig, "username"),
Password: maputil.GetString(channelConfig, "password"),
})
case domain.NotifyChannelTypePushover:
return pPushover.NewNotifier(&pPushover.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"),
User: maputil.GetString(channelConfig, "user"),
})
return pEmail.NewNotifierProvider(&pEmail.NotifierProviderConfig{
SmtpHost: access.SmtpHost,
SmtpPort: access.SmtpPort,
SmtpTls: access.SmtpTls,
Username: access.Username,
Password: access.Password,
SenderAddress: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "senderAddress", access.DefaultSenderAddress),
SenderName: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "senderName", access.DefaultSenderName),
ReceiverAddress: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "receiverAddress", access.DefaultReceiverAddress),
})
}
case domain.NotifyChannelTypePushPlus:
return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"),
})
case domain.NotificationProviderTypeLarkBot:
{
access := domain.AccessConfigForLarkBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
case domain.NotifyChannelTypeServerChan:
return pServerChan.NewNotifier(&pServerChan.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
})
return pLarkBot.NewNotifierProvider(&pLarkBot.NotifierProviderConfig{
WebhookUrl: access.WebhookUrl,
})
}
case domain.NotifyChannelTypeTelegram:
return pTelegram.NewNotifier(&pTelegram.NotifierConfig{
ApiToken: maputil.GetString(channelConfig, "apiToken"),
ChatId: maputil.GetInt64(channelConfig, "chatId"),
})
case domain.NotificationProviderTypeMattermost:
{
access := domain.AccessConfigForMattermost{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
case domain.NotifyChannelTypeWebhook:
return pWebhook.NewNotifier(&pWebhook.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"),
})
return pMattermost.NewNotifierProvider(&pMattermost.NotifierProviderConfig{
ServerUrl: access.ServerUrl,
Username: access.Username,
Password: access.Password,
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotifyChannelTypeWeCom:
return pWeCom.NewNotifier(&pWeCom.NotifierConfig{
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
})
case domain.NotificationProviderTypeSlackBot:
{
access := domain.AccessConfigForSlackBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pSlackBot.NewNotifierProvider(&pSlackBot.NotifierProviderConfig{
BotToken: access.BotToken,
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotificationProviderTypeTelegramBot:
{
access := domain.AccessConfigForTelegramBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pTelegramBot.NewNotifierProvider(&pTelegramBot.NotifierProviderConfig{
BotToken: access.BotToken,
ChatId: xmaps.GetOrDefaultInt64(options.ProviderServiceConfig, "chatId", access.DefaultChatId),
})
}
case domain.NotificationProviderTypeWebhook:
{
access := domain.AccessConfigForWebhook{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
mergedHeaders := make(map[string]string)
if defaultHeadersString := access.HeadersString; defaultHeadersString != "" {
h, err := xhttp.ParseHeaders(defaultHeadersString)
if err != nil {
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
}
for key := range h {
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
}
}
if extendedHeadersString := xmaps.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" {
h, err := xhttp.ParseHeaders(extendedHeadersString)
if err != nil {
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
}
for key := range h {
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
}
}
return pWebhook.NewNotifierProvider(&pWebhook.NotifierProviderConfig{
WebhookUrl: access.Url,
WebhookData: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForNotification),
Method: access.Method,
Headers: mergedHeaders,
AllowInsecureConnections: access.AllowInsecureConnections,
})
}
case domain.NotificationProviderTypeWeComBot:
{
access := domain.AccessConfigForWeComBot{}
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pWeComBot.NewNotifierProvider(&pWeComBot.NotifierProviderConfig{
WebhookUrl: access.WebhookUrl,
})
}
}
return nil, fmt.Errorf("unsupported notifier channel: %s", channelConfig)
return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider)
}

View File

@@ -0,0 +1,108 @@
package notify
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
pBark "github.com/certimate-go/certimate/pkg/core/notifier/providers/bark"
pDingTalk "github.com/certimate-go/certimate/pkg/core/notifier/providers/dingtalkbot"
pEmail "github.com/certimate-go/certimate/pkg/core/notifier/providers/email"
pGotify "github.com/certimate-go/certimate/pkg/core/notifier/providers/gotify"
pLark "github.com/certimate-go/certimate/pkg/core/notifier/providers/larkbot"
pMattermost "github.com/certimate-go/certimate/pkg/core/notifier/providers/mattermost"
pPushover "github.com/certimate-go/certimate/pkg/core/notifier/providers/pushover"
pPushPlus "github.com/certimate-go/certimate/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/certimate-go/certimate/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/certimate-go/certimate/pkg/core/notifier/providers/telegrambot"
pWebhook "github.com/certimate-go/certimate/pkg/core/notifier/providers/webhook"
pWeCom "github.com/certimate-go/certimate/pkg/core/notifier/providers/wecombot"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
// Deprecated: v0.4.x 将废弃
func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, channelConfig map[string]any) (core.Notifier, error) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch channel {
case domain.NotifyChannelTypeBark:
return pBark.NewNotifierProvider(&pBark.NotifierProviderConfig{
DeviceKey: xmaps.GetString(channelConfig, "deviceKey"),
ServerUrl: xmaps.GetString(channelConfig, "serverUrl"),
})
case domain.NotifyChannelTypeDingTalk:
return pDingTalk.NewNotifierProvider(&pDingTalk.NotifierProviderConfig{
WebhookUrl: "https://oapi.dingtalk.com/robot/send?access_token=" + xmaps.GetString(channelConfig, "accessToken"),
Secret: xmaps.GetString(channelConfig, "secret"),
})
case domain.NotifyChannelTypeEmail:
return pEmail.NewNotifierProvider(&pEmail.NotifierProviderConfig{
SmtpHost: xmaps.GetString(channelConfig, "smtpHost"),
SmtpPort: xmaps.GetInt32(channelConfig, "smtpPort"),
SmtpTls: xmaps.GetOrDefaultBool(channelConfig, "smtpTLS", true),
Username: xmaps.GetOrDefaultString(channelConfig, "username", xmaps.GetString(channelConfig, "senderAddress")),
Password: xmaps.GetString(channelConfig, "password"),
SenderAddress: xmaps.GetString(channelConfig, "senderAddress"),
ReceiverAddress: xmaps.GetString(channelConfig, "receiverAddress"),
})
case domain.NotifyChannelTypeGotify:
return pGotify.NewNotifierProvider(&pGotify.NotifierProviderConfig{
ServerUrl: xmaps.GetString(channelConfig, "url"),
Token: xmaps.GetString(channelConfig, "token"),
Priority: xmaps.GetOrDefaultInt64(channelConfig, "priority", 1),
})
case domain.NotifyChannelTypeLark:
return pLark.NewNotifierProvider(&pLark.NotifierProviderConfig{
WebhookUrl: xmaps.GetString(channelConfig, "webhookUrl"),
})
case domain.NotifyChannelTypeMattermost:
return pMattermost.NewNotifierProvider(&pMattermost.NotifierProviderConfig{
ServerUrl: xmaps.GetString(channelConfig, "serverUrl"),
ChannelId: xmaps.GetString(channelConfig, "channelId"),
Username: xmaps.GetString(channelConfig, "username"),
Password: xmaps.GetString(channelConfig, "password"),
})
case domain.NotifyChannelTypePushover:
return pPushover.NewNotifierProvider(&pPushover.NotifierProviderConfig{
Token: xmaps.GetString(channelConfig, "token"),
User: xmaps.GetString(channelConfig, "user"),
})
case domain.NotifyChannelTypePushPlus:
return pPushPlus.NewNotifierProvider(&pPushPlus.NotifierProviderConfig{
Token: xmaps.GetString(channelConfig, "token"),
})
case domain.NotifyChannelTypeServerChan:
return pServerChan.NewNotifierProvider(&pServerChan.NotifierProviderConfig{
ServerUrl: xmaps.GetString(channelConfig, "url"),
})
case domain.NotifyChannelTypeTelegram:
return pTelegram.NewNotifierProvider(&pTelegram.NotifierProviderConfig{
BotToken: xmaps.GetString(channelConfig, "apiToken"),
ChatId: xmaps.GetInt64(channelConfig, "chatId"),
})
case domain.NotifyChannelTypeWebhook:
return pWebhook.NewNotifierProvider(&pWebhook.NotifierProviderConfig{
WebhookUrl: xmaps.GetString(channelConfig, "url"),
AllowInsecureConnections: xmaps.GetBool(channelConfig, "allowInsecureConnections"),
})
case domain.NotifyChannelTypeWeCom:
return pWeCom.NewNotifierProvider(&pWeCom.NotifierProviderConfig{
WebhookUrl: xmaps.GetString(channelConfig, "webhookUrl"),
})
}
return nil, fmt.Errorf("unsupported notifier channel '%s'", channelConfig)
}

View File

@@ -4,29 +4,34 @@ import (
"context"
"fmt"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/internal/domain/dtos"
)
// Deprecated: v0.4.x 将废弃
const (
notifyTestTitle = "测试通知"
notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。"
)
// Deprecated: v0.4.x 将废弃
type settingsRepository interface {
GetByName(ctx context.Context, name string) (*domain.Settings, error)
}
// Deprecated: v0.4.x 将废弃
type NotifyService struct {
settingsRepo settingsRepository
}
// Deprecated: v0.4.x 将废弃
func NewNotifyService(settingsRepo settingsRepository) *NotifyService {
return &NotifyService{
settingsRepo: settingsRepo,
}
}
// Deprecated: v0.4.x 将废弃
func (n *NotifyService) Test(ctx context.Context, req *dtos.NotifyTestPushReq) error {
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
if err != nil {

View File

@@ -1,29 +0,0 @@
package deployer
import (
"context"
"log/slog"
)
// 表示定义证书部署器的抽象类型接口。
// 注意与 `Uploader` 区分,“部署”通常为“上传”的后置操作。
type Deployer interface {
WithLogger(logger *slog.Logger) Deployer
// 部署证书。
//
// 入参:
// - ctx上下文。
// - certPem证书 PEM 内容。
// - privkeyPem私钥 PEM 内容。
//
// 出参:
// - res部署结果。
// - err: 错误。
Deploy(ctx context.Context, certPem string, privkeyPem string) (res *DeployResult, err error)
}
// 表示证书部署结果的数据结构。
type DeployResult struct {
ExtendedData map[string]any `json:"extendedData,omitempty"`
}

View File

@@ -1,98 +0,0 @@
package onepanelconsole
import (
"context"
"crypto/tls"
"errors"
"log/slog"
"net/url"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 是否自动重启。
AutoRestart bool `json:"autoRestart"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *opsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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 证书
updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{
Cert: certPem,
Key: privkeyPem,
SSL: "enable",
SSLType: "import-paste",
}
if d.config.AutoRestart {
updateSystemSSLReq.AutoRestart = "true"
} else {
updateSystemSSLReq.AutoRestart = "false"
}
updateSystemSSLResp, err := d.sdkClient.UpdateSystemSSL(updateSystemSSLReq)
d.logger.Debug("sdk request '1panel.UpdateSystemSSL'", slog.Any("request", updateSystemSSLReq), slog.Any("response", updateSystemSSLResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateSystemSSL'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
if allowInsecure {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -1,193 +0,0 @@
package onepanelsite
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
"strconv"
xerrors "github.com/pkg/errors"
"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/vendors/1panel-sdk"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 网站 ID。
// 部署资源类型为 [RESOURCE_TYPE_WEBSITE] 时必填。
WebsiteId int64 `json:"websiteId,omitempty"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId int64 `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *opsdk.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.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
ApiUrl: config.ApiUrl,
ApiKey: config.ApiKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_WEBSITE:
if err := d.deployToWebsite(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
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) deployToWebsite(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.WebsiteId == 0 {
return errors.New("config `websiteId` is required")
}
// 获取网站 HTTPS 配置
getHttpsConfReq := &opsdk.GetHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
}
getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq)
d.logger.Debug("sdk request '1panel.GetHttpsConf'", slog.Any("request", getHttpsConfReq), slog.Any("response", getHttpsConfResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'")
}
// 上传证书到面板
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 修改网站 HTTPS 配置
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
Type: "existed",
WebsiteSSLID: certId,
Enable: getHttpsConfResp.Data.Enable,
HttpConfig: getHttpsConfResp.Data.HttpConfig,
SSLProtocol: getHttpsConfResp.Data.SSLProtocol,
Algorithm: getHttpsConfResp.Data.Algorithm,
Hsts: getHttpsConfResp.Data.Hsts,
}
updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq)
d.logger.Debug("sdk request '1panel.UpdateHttpsConf'", slog.Any("request", updateHttpsConfReq), slog.Any("response", updateHttpsConfResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'")
}
return 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")
}
// 获取证书详情
getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{
SSLID: d.config.CertificateId,
}
getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq)
d.logger.Debug("sdk request '1panel.GetWebsiteSSL'", slog.Any("request", getWebsiteSSLReq), slog.Any("response", getWebsiteSSLResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetWebsiteSSL'")
}
// 更新证书
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
Type: "paste",
SSLID: d.config.CertificateId,
Description: getWebsiteSSLResp.Data.Description,
Certificate: certPem,
PrivateKey: privkeyPem,
}
uploadWebsiteSSLResp, err := d.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
d.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'")
}
return nil
}
func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
if allowInsecure {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -1,72 +0,0 @@
package aliyuncas
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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"
)
type DeployerConfig struct {
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云地域。
Region string `json:"region"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,72 +0,0 @@
package awsacm
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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/aws-acm"
)
type DeployerConfig struct {
// AWS AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// AWS SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
Region: config.Region,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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) {
// 上传证书到 ACM
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,133 +0,0 @@
package awscloudfront
import (
"context"
"errors"
"log/slog"
aws "github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
awscred "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
"github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
xerrors "github.com/pkg/errors"
"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/aws-acm"
)
type DeployerConfig struct {
// AWS AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// AWS SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
// AWS CloudFront 分配 ID。
DistributionId string `json:"distributionId"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *cloudfront.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.SecretAccessKey, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
Region: config.Region,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
if d.config.DistributionId == "" {
return nil, errors.New("config `distribuitionId` is required")
}
// 上传证书到 ACM
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 获取分配配置
// REF: https://docs.aws.amazon.com/en_us/cloudfront/latest/APIReference/API_GetDistributionConfig.html
getDistributionConfigReq := &cloudfront.GetDistributionConfigInput{
Id: aws.String(d.config.DistributionId),
}
getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(context.TODO(), getDistributionConfigReq)
d.logger.Debug("sdk request 'cloudfront.GetDistributionConfig'", slog.Any("request", getDistributionConfigReq), slog.Any("response", getDistributionConfigResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.GetDistributionConfig'")
}
// 更新分配配置
// REF: https://docs.aws.amazon.com/zh_cn/cloudfront/latest/APIReference/API_UpdateDistribution.html
updateDistributionReq := &cloudfront.UpdateDistributionInput{
Id: aws.String(d.config.DistributionId),
DistributionConfig: getDistributionConfigResp.DistributionConfig,
IfMatch: getDistributionConfigResp.ETag,
}
if updateDistributionReq.DistributionConfig.ViewerCertificate == nil {
updateDistributionReq.DistributionConfig.ViewerCertificate = &types.ViewerCertificate{}
}
updateDistributionReq.DistributionConfig.ViewerCertificate.CloudFrontDefaultCertificate = aws.Bool(false)
updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = aws.String(upres.CertId)
updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq)
d.logger.Debug("sdk request 'cloudfront.UpdateDistribution'", slog.Any("request", updateDistributionReq), slog.Any("response", updateDistributionResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.UpdateDistribution'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, secretAccessKey, region string) (*cloudfront.Client, error) {
cfg, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := cloudfront.NewFromConfig(cfg, func(o *cloudfront.Options) {
o.Region = region
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
})
return client, nil
}

View File

@@ -1,10 +0,0 @@
package baiducloudappblb
type ResourceType string
const (
// 资源类型:部署到指定负载均衡器。
RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
// 资源类型:部署到指定监听器。
RESOURCE_TYPE_LISTENER = ResourceType("listener")
)

View File

@@ -1,68 +0,0 @@
package baiducloudcert
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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/baiducloud-cert"
)
type DeployerConfig struct {
// 百度智能云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 百度智能云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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) {
// 上传证书到 CAS
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,153 +0,0 @@
package baishancdn
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"regexp"
"strings"
"time"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
bssdk "github.com/usual2970/certimate/internal/pkg/vendors/baishan-sdk"
)
type DeployerConfig struct {
// 白山云 API Token。
ApiToken string `json:"apiToken"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
// 证书 ID。
// 选填。
CertificateId string `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *bssdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiToken)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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.Domain == "" {
return nil, errors.New("config `domain` is required")
}
if d.config.CertificateId == "" {
// 新增证书
// REF: https://portal.baishancloud.com/track/document/downloadPdf/1441
certificateId := ""
createCertificateReq := &bssdk.CreateCertificateRequest{
Certificate: certPem,
Key: privkeyPem,
Name: fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'baishan.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
if createCertificateResp != nil {
if createCertificateResp.GetCode() == 400699 && strings.Contains(createCertificateResp.GetMessage(), "this certificate is exists") {
// 证书已存在,忽略新增证书接口错误
re := regexp.MustCompile(`\d+`)
certificateId = re.FindString(createCertificateResp.GetMessage())
}
}
if certificateId == "" {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'baishan.CreateCertificate'")
}
} else {
certificateId = createCertificateResp.Data.CertId.String()
}
// 查询域名配置
// REF: https://portal.baishancloud.com/track/document/api/1/1065
getDomainConfigReq := &bssdk.GetDomainConfigRequest{
Domains: d.config.Domain,
Config: []string{"https"},
}
getDomainConfigResp, err := d.sdkClient.GetDomainConfig(getDomainConfigReq)
d.logger.Debug("sdk request 'baishan.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'baishan.GetDomainConfig'")
} else if len(getDomainConfigResp.Data) == 0 {
return nil, errors.New("domain config not found")
}
// 设置域名配置
// REF: https://portal.baishancloud.com/track/document/api/1/1045
setDomainConfigReq := &bssdk.SetDomainConfigRequest{
Domains: d.config.Domain,
Config: &bssdk.DomainConfig{
Https: &bssdk.DomainConfigHttps{
CertId: json.Number(certificateId),
ForceHttps: getDomainConfigResp.Data[0].Config.Https.ForceHttps,
EnableHttp2: getDomainConfigResp.Data[0].Config.Https.EnableHttp2,
EnableOcsp: getDomainConfigResp.Data[0].Config.Https.EnableOcsp,
},
},
}
setDomainConfigResp, err := d.sdkClient.SetDomainConfig(setDomainConfigReq)
d.logger.Debug("sdk request 'baishan.SetDomainConfig'", slog.Any("request", setDomainConfigReq), slog.Any("response", setDomainConfigResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'baishan.SetDomainConfig'")
}
} else {
// 替换证书
// REF: https://portal.baishancloud.com/track/document/downloadPdf/1441
createCertificateReq := &bssdk.CreateCertificateRequest{
CertificateId: &d.config.CertificateId,
Certificate: certPem,
Key: privkeyPem,
Name: fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'baishan.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'baishan.CreateCertificate'")
}
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiToken string) (*bssdk.Client, error) {
if apiToken == "" {
return nil, errors.New("invalid baishan api token")
}
client := bssdk.NewClient(apiToken)
return client, nil
}

View File

@@ -1,101 +0,0 @@
package baotapanelconsole
import (
"context"
"crypto/tls"
"errors"
"log/slog"
"net/url"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
btsdk "github.com/usual2970/certimate/internal/pkg/vendors/btpanel-sdk"
)
type DeployerConfig struct {
// 宝塔面板地址。
ApiUrl string `json:"apiUrl"`
// 宝塔面板接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 是否自动重启。
AutoRestart bool `json:"autoRestart"`
}
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.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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 证书
configSavePanelSSLReq := &btsdk.ConfigSavePanelSSLRequest{
PrivateKey: privkeyPem,
Certificate: certPem,
}
configSavePanelSSLResp, err := d.sdkClient.ConfigSavePanelSSL(configSavePanelSSLReq)
d.logger.Debug("sdk request 'bt.ConfigSavePanelSSL'", slog.Any("request", configSavePanelSSLReq), slog.Any("response", configSavePanelSSLResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'bt.ConfigSavePanelSSL'")
}
if d.config.AutoRestart {
// 重启面板(无需关心响应,因为宝塔重启时会断开连接产生 error
systemServiceAdminReq := &btsdk.SystemServiceAdminRequest{
Name: "nginx",
Type: "restart",
}
systemServiceAdminResp, _ := d.sdkClient.SystemServiceAdmin(systemServiceAdminReq)
d.logger.Debug("sdk request 'bt.SystemServiceAdmin'", slog.Any("request", systemServiceAdminReq), slog.Any("response", systemServiceAdminResp))
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*btsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid baota api url")
}
if apiKey == "" {
return nil, errors.New("invalid baota api key")
}
client := btsdk.NewClient(apiUrl, apiKey)
if allowInsecure {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -1,70 +0,0 @@
package bunnycdn
import (
"context"
"encoding/base64"
"log/slog"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
bunnysdk "github.com/usual2970/certimate/internal/pkg/vendors/bunny-sdk"
)
type DeployerConfig struct {
// Bunny API Key
ApiKey string `json:"apiKey"`
// Bunny Pull Zone ID
PullZoneId string `json:"pullZoneId"`
// Bunny CDN Hostname支持泛域名
HostName string `json:"hostName"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *bunnysdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: bunnysdk.NewClient(config.ApiKey),
}, 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) {
// Prepare
certPemBase64 := base64.StdEncoding.EncodeToString([]byte(certPem))
privkeyPemBase64 := base64.StdEncoding.EncodeToString([]byte(privkeyPem))
// 上传证书
createCertificateReq := &bunnysdk.AddCustomCertificateRequest{
Hostname: d.config.HostName,
PullZoneId: d.config.PullZoneId,
Certificate: certPemBase64,
CertificateKey: privkeyPemBase64,
}
createCertificateResp, err := d.sdkClient.AddCustomCertificate(createCertificateReq)
d.logger.Debug("sdk request 'bunny-cdn.AddCustomCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'bunny-cdn.AddCustomCertificate'")
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,75 +0,0 @@
package cachefly
import (
"context"
"errors"
"log/slog"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
cfsdk "github.com/usual2970/certimate/internal/pkg/vendors/cachefly-sdk"
)
type DeployerConfig struct {
// CacheFly API Token。
ApiToken string `json:"apiToken"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *cfsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiToken)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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) {
// 上传证书
createCertificateReq := &cfsdk.CreateCertificateRequest{
Certificate: certPem,
CertificateKey: privkeyPem,
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'cachefly.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cachefly.CreateCertificate'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiToken string) (*cfsdk.Client, error) {
if apiToken == "" {
return nil, errors.New("invalid cachefly api token")
}
client := cfsdk.NewClient(apiToken)
return client, nil
}

View File

@@ -1,177 +0,0 @@
package cdnfly
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/url"
"time"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
cfsdk "github.com/usual2970/certimate/internal/pkg/vendors/cdnfly-sdk"
)
type DeployerConfig struct {
// Cdnfly 地址。
ApiUrl string `json:"apiUrl"`
// Cdnfly 用户端 API Key。
ApiKey string `json:"apiKey"`
// Cdnfly 用户端 API Secret。
ApiSecret string `json:"apiSecret"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 网站 ID。
// 部署资源类型为 [RESOURCE_TYPE_SITE] 时必填。
SiteId string `json:"siteId,omitempty"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId string `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *cfsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.ApiSecret)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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_SITE:
if err := d.deployToSite(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
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) deployToSite(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.SiteId == "" {
return errors.New("config `siteId` is required")
}
// 获取单个网站详情
// REF: https://doc.cdnfly.cn/wangzhanguanli-v1-sites.html#%E8%8E%B7%E5%8F%96%E5%8D%95%E4%B8%AA%E7%BD%91%E7%AB%99%E8%AF%A6%E6%83%85
getSiteReq := &cfsdk.GetSiteRequest{
Id: d.config.SiteId,
}
getSiteResp, err := d.sdkClient.GetSite(getSiteReq)
d.logger.Debug("sdk request 'cdnfly.GetSite'", slog.Any("request", getSiteReq), slog.Any("response", getSiteResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'cdnfly.GetSite'")
}
// 添加单个证书
// REF: https://doc.cdnfly.cn/wangzhanzhengshu-v1-certs.html#%E6%B7%BB%E5%8A%A0%E5%8D%95%E4%B8%AA%E6%88%96%E5%A4%9A%E4%B8%AA%E8%AF%81%E4%B9%A6-%E5%A4%9A%E4%B8%AA%E8%AF%81%E4%B9%A6%E6%97%B6%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E4%B8%BA%E6%95%B0%E7%BB%84
createCertificateReq := &cfsdk.CreateCertificateRequest{
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
Type: "custom",
Cert: certPem,
Key: privkeyPem,
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'cdnfly.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'cdnfly.CreateCertificate'")
}
// 修改单个网站
// REF: https://doc.cdnfly.cn/wangzhanguanli-v1-sites.html#%E4%BF%AE%E6%94%B9%E5%8D%95%E4%B8%AA%E7%BD%91%E7%AB%99
updateSiteHttpsListenMap := make(map[string]any)
_ = json.Unmarshal([]byte(getSiteResp.Data.HttpsListen), &updateSiteHttpsListenMap)
updateSiteHttpsListenMap["cert"] = createCertificateResp.Data
updateSiteHttpsListenData, _ := json.Marshal(updateSiteHttpsListenMap)
updateSiteHttpsListen := string(updateSiteHttpsListenData)
updateSiteReq := &cfsdk.UpdateSiteRequest{
Id: d.config.SiteId,
HttpsListen: &updateSiteHttpsListen,
}
updateSiteResp, err := d.sdkClient.UpdateSite(updateSiteReq)
d.logger.Debug("sdk request 'cdnfly.UpdateSite'", slog.Any("request", updateSiteReq), slog.Any("response", updateSiteResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'cdnfly.UpdateSite'")
}
return nil
}
func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.CertificateId == "" {
return errors.New("config `certificateId` is required")
}
// 修改单个证书
// REF: https://doc.cdnfly.cn/wangzhanzhengshu-v1-certs.html#%E4%BF%AE%E6%94%B9%E5%8D%95%E4%B8%AA%E8%AF%81%E4%B9%A6
updateCertificateType := "custom"
updateCertificateReq := &cfsdk.UpdateCertificateRequest{
Id: d.config.CertificateId,
Type: &updateCertificateType,
Cert: &certPem,
Key: &privkeyPem,
}
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
d.logger.Debug("sdk request 'cdnfly.UpdateCertificate'", slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'cdnfly.UpdateCertificate'")
}
return nil
}
func createSdkClient(apiUrl, apiKey, apiSecret string) (*cfsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid cachefly api url")
}
if apiKey == "" {
return nil, errors.New("invalid cachefly api key")
}
if apiSecret == "" {
return nil, errors.New("invalid cachefly api secret")
}
client := cfsdk.NewClient(apiUrl, apiKey, apiSecret)
return client, nil
}

View File

@@ -1,86 +0,0 @@
package dogecloudcdn
import (
"context"
"log/slog"
"strconv"
xerrors "github.com/pkg/errors"
"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/dogecloud"
dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
)
type DeployerConfig struct {
// 多吉云 AccessKey。
AccessKey string `json:"accessKey"`
// 多吉云 SecretKey。
SecretKey string `json:"secretKey"`
// 加速域名(不支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *dogesdk.Client
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client := dogesdk.NewClient(config.AccessKey, config.SecretKey)
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
// 上传证书到 CDN
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 绑定证书
// REF: https://docs.dogecloud.com/cdn/api-cert-bind
bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.config.Domain)
d.logger.Debug("sdk request 'cdn.BindCdnCert'", slog.Int64("request.certId", bindCdnCertId), slog.String("request.domain", d.config.Domain), slog.Any("response", bindCdnCertResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'")
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,85 +0,0 @@
package edgioapplications
import (
"context"
"log/slog"
edgio "github.com/Edgio/edgio-api/applications/v7"
edgiodtos "github.com/Edgio/edgio-api/applications/v7/dtos"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
)
type DeployerConfig struct {
// Edgio ClientId。
ClientId string `json:"clientId"`
// Edgio ClientSecret。
ClientSecret string `json:"clientSecret"`
// Edgio 环境 ID。
EnvironmentId string `json:"environmentId"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *edgio.EdgioClient
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ClientId, config.ClientSecret)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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) {
// 提取 Edgio 所需的服务端证书和中间证书内容
privateCertPem, intermediateCertPem, err := certutil.ExtractCertificatesFromPEM(certPem)
if err != nil {
return nil, err
}
// 上传 TLS 证书
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
EnvironmentID: d.config.EnvironmentId,
PrimaryCert: privateCertPem,
IntermediateCert: intermediateCertPem,
PrivateKey: privkeyPem,
}
uploadTlsCertResp, err := d.sdkClient.UploadTlsCert(uploadTlsCertReq)
d.logger.Debug("sdk request 'edgio.UploadTlsCert'", slog.Any("request", uploadTlsCertReq), slog.Any("response", uploadTlsCertResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'edgio.UploadTlsCert'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(clientId, clientSecret string) (*edgio.EdgioClient, error) {
client := edgio.NewEdgioClient(clientId, clientSecret, "", "")
return client, nil
}

View File

@@ -1,133 +0,0 @@
package gcorecdn
import (
"context"
"errors"
"log/slog"
"strconv"
gprovider "github.com/G-Core/gcorelabscdn-go/gcore/provider"
gresources "github.com/G-Core/gcorelabscdn-go/resources"
xerrors "github.com/pkg/errors"
"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/gcore-cdn"
gcoresdk "github.com/usual2970/certimate/internal/pkg/vendors/gcore-sdk/common"
)
type DeployerConfig struct {
// Gcore API Token。
ApiToken string `json:"apiToken"`
// CDN 资源 ID。
ResourceId int64 `json:"resourceId"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *gresources.Service
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.ApiToken)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
ApiToken: config.ApiToken,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
if d.config.ResourceId == 0 {
return nil, errors.New("config `resourceId` is required")
}
// 上传证书到 CDN
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 获取 CDN 资源详情
// REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/paths/~1cdn~1resources~1%7Bresource_id%7D/get
getResourceResp, err := d.sdkClient.Get(context.TODO(), d.config.ResourceId)
d.logger.Debug("sdk request 'resources.Get'", slog.Any("resourceId", d.config.ResourceId), slog.Any("response", getResourceResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'resources.Get'")
}
// 更新 CDN 资源详情
// REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/operation/change_cdn_resource
updateResourceCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
updateResourceReq := &gresources.UpdateRequest{
Description: getResourceResp.Description,
Active: getResourceResp.Active,
OriginGroup: int(getResourceResp.OriginGroup),
OriginProtocol: getResourceResp.OriginProtocol,
SecondaryHostnames: getResourceResp.SecondaryHostnames,
SSlEnabled: true,
SSLData: int(updateResourceCertId),
ProxySSLEnabled: getResourceResp.ProxySSLEnabled,
}
if getResourceResp.ProxySSLCA != 0 {
updateResourceReq.ProxySSLCA = &getResourceResp.ProxySSLCA
}
if getResourceResp.ProxySSLData != 0 {
updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData
}
if getResourceResp.Options != nil {
updateResourceReq.Options = getResourceResp.Options
}
updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq)
d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'resources.Update'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiToken string) (*gresources.Service, error) {
if apiToken == "" {
return nil, errors.New("invalid gcore api token")
}
requester := gprovider.NewClient(
gcoresdk.BASE_URL,
gprovider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)),
)
service := gresources.NewService(requester)
return service, nil
}

View File

@@ -1,69 +0,0 @@
package huaweicloudscm
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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/huaweicloud-scm"
)
type DeployerConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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) {
// 上传证书到 SCM
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,170 +0,0 @@
package local
import (
"bytes"
"context"
"fmt"
"log/slog"
"os/exec"
"runtime"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
"github.com/usual2970/certimate/internal/pkg/utils/fileutil"
)
type DeployerConfig struct {
// Shell 执行环境。
// 零值时默认根据操作系统决定。
ShellEnv ShellEnvType `json:"shellEnv,omitempty"`
// 前置命令。
PreCommand string `json:"preCommand,omitempty"`
// 后置命令。
PostCommand string `json:"postCommand,omitempty"`
// 输出证书格式。
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
// 输出证书文件路径。
OutputCertPath string `json:"outputCertPath,omitempty"`
// 输出私钥文件路径。
OutputKeyPath string `json:"outputKeyPath,omitempty"`
// PFX 导出密码。
// 证书格式为 PFX 时必填。
PfxPassword string `json:"pfxPassword,omitempty"`
// JKS 别名。
// 证书格式为 JKS 时必填。
JksAlias string `json:"jksAlias,omitempty"`
// JKS 密钥密码。
// 证书格式为 JKS 时必填。
JksKeypass string `json:"jksKeypass,omitempty"`
// JKS 存储密码。
// 证书格式为 JKS 时必填。
JksStorepass string `json:"jksStorepass,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
}, 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.PreCommand != "" {
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand)
d.logger.Debug("run pre-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
if err != nil {
return nil, xerrors.Wrapf(err, "failed to execute pre-command, stdout: %s, stderr: %s", stdout, stderr)
}
}
// 写入证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
if err := fileutil.WriteString(d.config.OutputCertPath, certPem); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
if err := fileutil.WriteString(d.config.OutputKeyPath, privkeyPem); err != nil {
return nil, xerrors.Wrap(err, "failed to save private key file")
}
d.logger.Info("ssl private key file saved", slog.String("path", d.config.OutputKeyPath))
case OUTPUT_FORMAT_PFX:
pfxData, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
}
d.logger.Info("ssl certificate transformed to pfx")
if err := fileutil.Write(d.config.OutputCertPath, pfxData); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
case OUTPUT_FORMAT_JKS:
jksData, err := certutil.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
}
d.logger.Info("ssl certificate transformed to jks")
if err := fileutil.Write(d.config.OutputCertPath, jksData); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
default:
return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat)
}
// 执行后置命令
if d.config.PostCommand != "" {
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PostCommand)
d.logger.Debug("run post-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
if err != nil {
return nil, xerrors.Wrapf(err, "failed to execute post-command, stdout: %s, stderr: %s", stdout, stderr)
}
}
return &deployer.DeployResult{}, nil
}
func execCommand(shellEnv ShellEnvType, command string) (string, string, error) {
var cmd *exec.Cmd
switch shellEnv {
case SHELL_ENV_SH:
cmd = exec.Command("sh", "-c", command)
case SHELL_ENV_CMD:
cmd = exec.Command("cmd", "/C", command)
case SHELL_ENV_POWERSHELL:
cmd = exec.Command("powershell", "-Command", command)
case ShellEnvType(""):
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/C", command)
} else {
cmd = exec.Command("sh", "-c", command)
}
default:
return "", "", fmt.Errorf("unsupported shell env: %s", shellEnv)
}
stdoutBuf := bytes.NewBuffer(nil)
cmd.Stdout = stdoutBuf
stderrBuf := bytes.NewBuffer(nil)
cmd.Stderr = stderrBuf
err := cmd.Run()
if err != nil {
return stdoutBuf.String(), stderrBuf.String(), xerrors.Wrap(err, "failed to execute command")
}
return stdoutBuf.String(), stderrBuf.String(), nil
}

View File

@@ -1,91 +0,0 @@
package qiniupili
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"github.com/qiniu/go-sdk/v7/pili"
"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/qiniu-sslcert"
)
type DeployerConfig struct {
// 七牛云 AccessKey。
AccessKey string `json:"accessKey"`
// 七牛云 SecretKey。
SecretKey string `json:"secretKey"`
// 直播空间名。
Hub string `json:"hub"`
// 直播流域名(不支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *pili.Manager
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
manager := pili.NewManager(pili.ManagerConfig{AccessKey: config.AccessKey, SecretKey: config.SecretKey})
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: manager,
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) {
// 上传证书到 CDN
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 修改域名证书配置
// REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#66
setDomainCertReq := pili.SetDomainCertRequest{
Hub: d.config.Hub,
Domain: d.config.Domain,
CertName: upres.CertName,
}
err = d.sdkClient.SetDomainCert(context.TODO(), setDomainCertReq)
d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'pili.SetDomainCert'")
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,102 +0,0 @@
package rainyunrcdn
import (
"context"
"errors"
"log/slog"
"strconv"
xerrors "github.com/pkg/errors"
"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/rainyun-sslcenter"
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
)
type DeployerConfig struct {
// 雨云 API 密钥。
ApiKey string `json:"apiKey"`
// RCDN 实例 ID。
InstanceId int32 `json:"instanceId"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *rainyunsdk.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.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
ApiKey: config.ApiKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
// 上传证书到 SSL 证书
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// RCDN SSL 绑定域名
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
certId, _ := strconv.Atoi(upres.CertId)
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
CertId: int32(certId),
Domains: []string{d.config.Domain},
}
rcdnInstanceSslBindResp, err := d.sdkClient.RcdnInstanceSslBind(d.config.InstanceId, rcdnInstanceSslBindReq)
d.logger.Debug("sdk request 'rcdn.InstanceSslBind'", slog.Any("instanceId", d.config.InstanceId), slog.Any("request", rcdnInstanceSslBindReq), slog.Any("response", rcdnInstanceSslBindResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'rcdn.InstanceSslBind'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
if apiKey == "" {
return nil, errors.New("invalid rainyun api key")
}
client := rainyunsdk.NewClient(apiKey)
return client, nil
}

View File

@@ -1,118 +0,0 @@
package safeline
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/url"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
safelinesdk "github.com/usual2970/certimate/internal/pkg/vendors/safeline-sdk"
)
type DeployerConfig struct {
// 雷池 URL。
ApiUrl string `json:"apiUrl"`
// 雷池 API Token。
ApiToken string `json:"apiToken"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId int32 `json:"certificateId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *safelinesdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiToken, config.AllowInsecureConnections)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
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")
}
// 更新证书
updateCertificateReq := &safelinesdk.UpdateCertificateRequest{
Id: d.config.CertificateId,
Type: 2,
Manual: &safelinesdk.UpdateCertificateRequestBodyManul{
Crt: certPem,
Key: privkeyPem,
},
}
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
d.logger.Debug("sdk request 'safeline.UpdateCertificate'", slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'safeline.UpdateCertificate'")
}
return nil
}
func createSdkClient(apiUrl, apiToken string, allowInsecure bool) (*safelinesdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid safeline api url")
}
if apiToken == "" {
return nil, errors.New("invalid safeline api token")
}
client := safelinesdk.NewClient(apiUrl, apiToken)
if allowInsecure {
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}

View File

@@ -1,283 +0,0 @@
package ssh
import (
"bytes"
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
xerrors "github.com/pkg/errors"
"github.com/pkg/sftp"
"github.com/povsister/scp"
"golang.org/x/crypto/ssh"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
)
type DeployerConfig 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"`
// 是否回退使用 SCP。
UseSCP bool `json:"useSCP,omitempty"`
// 前置命令。
PreCommand string `json:"preCommand,omitempty"`
// 后置命令。
PostCommand string `json:"postCommand,omitempty"`
// 输出证书格式。
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
// 输出证书文件路径。
OutputCertPath string `json:"outputCertPath,omitempty"`
// 输出私钥文件路径。
OutputKeyPath string `json:"outputKeyPath,omitempty"`
// PFX 导出密码。
// 证书格式为 PFX 时必填。
PfxPassword string `json:"pfxPassword,omitempty"`
// JKS 别名。
// 证书格式为 JKS 时必填。
JksAlias string `json:"jksAlias,omitempty"`
// JKS 密钥密码。
// 证书格式为 JKS 时必填。
JksKeypass string `json:"jksKeypass,omitempty"`
// JKS 存储密码。
// 证书格式为 JKS 时必填。
JksStorepass string `json:"jksStorepass,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
}, 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) {
// 连接
client, err := createSshClient(
d.config.SshHost,
d.config.SshPort,
d.config.SshUsername,
d.config.SshPassword,
d.config.SshKey,
d.config.SshKeyPassphrase,
)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssh client")
}
defer client.Close()
d.logger.Info("ssh connected")
// 执行前置命令
if d.config.PreCommand != "" {
stdout, stderr, err := execSshCommand(client, d.config.PreCommand)
d.logger.Debug("run pre-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
if err != nil {
return nil, xerrors.Wrapf(err, "failed to execute pre-command: stdout: %s, stderr: %s", stdout, stderr)
}
}
// 上传证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
if err := writeFileString(client, d.config.UseSCP, d.config.OutputCertPath, certPem); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPem); err != nil {
return nil, xerrors.Wrap(err, "failed to upload private key file")
}
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
case OUTPUT_FORMAT_PFX:
pfxData, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
}
d.logger.Info("ssl certificate transformed to pfx")
if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, pfxData); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
case OUTPUT_FORMAT_JKS:
jksData, err := certutil.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
}
d.logger.Info("ssl certificate transformed to jks")
if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, jksData); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
default:
return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat)
}
// 执行后置命令
if d.config.PostCommand != "" {
stdout, stderr, err := execSshCommand(client, d.config.PostCommand)
d.logger.Debug("run post-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
if err != nil {
return nil, xerrors.Wrapf(err, "failed to execute post-command, stdout: %s, stderr: %s", stdout, stderr)
}
}
return &deployer.DeployResult{}, nil
}
func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
if host == "" {
host = "localhost"
}
if port == 0 {
port = 22
}
var authMethod ssh.AuthMethod
if key != "" {
var signer ssh.Signer
var err error
if keyPassphrase != "" {
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase))
} else {
signer, err = ssh.ParsePrivateKey([]byte(key))
}
if err != nil {
return nil, err
}
authMethod = ssh.PublicKeys(signer)
} else {
authMethod = ssh.Password(password)
}
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
}
func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) {
session, err := sshCli.NewSession()
if err != nil {
return "", "", err
}
defer session.Close()
stdoutBuf := bytes.NewBuffer(nil)
session.Stdout = stdoutBuf
stderrBuf := bytes.NewBuffer(nil)
session.Stderr = stderrBuf
err = session.Run(command)
if err != nil {
return stdoutBuf.String(), stderrBuf.String(), xerrors.Wrap(err, "failed to execute ssh command")
}
return stdoutBuf.String(), stderrBuf.String(), nil
}
func writeFileString(sshCli *ssh.Client, useSCP bool, path string, content string) error {
if useSCP {
return writeFileStringWithSCP(sshCli, path, content)
}
return writeFileStringWithSFTP(sshCli, path, content)
}
func writeFile(sshCli *ssh.Client, useSCP bool, path string, data []byte) error {
if useSCP {
return writeFileWithSCP(sshCli, path, data)
}
return writeFileWithSFTP(sshCli, path, data)
}
func writeFileStringWithSCP(sshCli *ssh.Client, path string, content string) error {
return writeFileWithSCP(sshCli, path, []byte(content))
}
func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error {
scpCli, err := scp.NewClientFromExistingSSH(sshCli, &scp.ClientOption{})
if err != nil {
return xerrors.Wrap(err, "failed to create scp client")
}
reader := bytes.NewReader(data)
err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{})
if err != nil {
return xerrors.Wrap(err, "failed to write to remote file")
}
return nil
}
func writeFileStringWithSFTP(sshCli *ssh.Client, path string, content string) error {
return writeFileWithSFTP(sshCli, path, []byte(content))
}
func writeFileWithSFTP(sshCli *ssh.Client, path string, data []byte) error {
sftpCli, err := sftp.NewClient(sshCli)
if err != nil {
return xerrors.Wrap(err, "failed to create sftp client")
}
defer sftpCli.Close()
if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
return xerrors.Wrap(err, "failed to create remote directory")
}
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return xerrors.Wrap(err, "failed to open remote file")
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
return xerrors.Wrap(err, "failed to write to remote file")
}
return nil
}

View File

@@ -1,117 +0,0 @@
package tencentcloudcos
import (
"context"
"errors"
"fmt"
"log/slog"
xerrors "github.com/pkg/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type DeployerConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
// 腾讯云地域。
Region string `json:"region"`
// 存储桶名。
Bucket string `json:"bucket"`
// 自定义域名(不支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *tcssl.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.SecretId, config.SecretKey, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
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) {
if d.config.Bucket == "" {
return nil, errors.New("config `bucket` is required")
}
if d.config.Domain == "" {
return nil, errors.New("config `domain` is required")
}
// 上传证书到 SSL
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 证书部署到 COS 实例
// REF: https://cloud.tencent.com/document/product/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
deployCertificateInstanceReq.ResourceType = common.StringPtr("cos")
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", d.config.Region, d.config.Bucket, d.config.Domain)})
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)
d.logger.Debug("sdk request 'ssl.DeployCertificateInstance'", slog.Any("request", deployCertificateInstanceReq), slog.Any("response", deployCertificateInstanceResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(secretId, secretKey, region string) (*tcssl.Client, error) {
credential := common.NewCredential(secretId, secretKey)
client, err := tcssl.NewClient(credential, region, profile.NewClientProfile())
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -1,162 +0,0 @@
package tencentcloudecdn
import (
"context"
"log/slog"
"strings"
xerrors "github.com/pkg/errors"
tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"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/tencentcloud-ssl"
)
type DeployerConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClients *wSdkClients
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
type wSdkClients struct {
SSL *tcssl.Client
CDN *tccdn.Client
}
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
clients, err := createSdkClients(config.SecretId, config.SecretKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClients: clients,
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) {
// 上传证书到 SSL
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 获取待部署的 CDN 实例
// 如果是泛域名,根据证书匹配 CDN 实例
instanceIds := make([]string, 0)
if strings.HasPrefix(d.config.Domain, "*.") {
domains, err := d.getDomainsByCertificateId(upres.CertId)
if err != nil {
return nil, err
}
instanceIds = domains
} else {
instanceIds = append(instanceIds, d.config.Domain)
}
if len(instanceIds) == 0 {
d.logger.Info("no ecdn instances to deploy")
} else {
d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds))
// 证书部署到 ECDN 实例
// REF: https://cloud.tencent.com/document/product/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
deployCertificateInstanceResp, err := d.sdkClients.SSL.DeployCertificateInstance(deployCertificateInstanceReq)
d.logger.Debug("sdk request 'ssl.DeployCertificateInstance'", slog.Any("request", deployCertificateInstanceReq), slog.Any("response", deployCertificateInstanceResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
}
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) getDomainsByCertificateId(cloudCertId string) ([]string, error) {
// 获取证书中的可用域名
// REF: https://cloud.tencent.com/document/product/228/42491
describeCertDomainsReq := tccdn.NewDescribeCertDomainsRequest()
describeCertDomainsReq.CertId = common.StringPtr(cloudCertId)
describeCertDomainsReq.Product = common.StringPtr("ecdn")
describeCertDomainsResp, err := d.sdkClients.CDN.DescribeCertDomains(describeCertDomainsReq)
d.logger.Debug("sdk request 'cdn.DescribeCertDomains'", slog.Any("request", describeCertDomainsReq), slog.Any("response", describeCertDomainsResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
}
domains := make([]string, 0)
if describeCertDomainsResp.Response.Domains != nil {
for _, domain := range describeCertDomainsResp.Response.Domains {
domains = append(domains, *domain)
}
}
return domains, nil
}
func createSdkClients(secretId, secretKey string) (*wSdkClients, error) {
credential := common.NewCredential(secretId, secretKey)
sslClient, err := tcssl.NewClient(credential, "", profile.NewClientProfile())
if err != nil {
return nil, err
}
cdnClient, err := tccdn.NewClient(credential, "", profile.NewClientProfile())
if err != nil {
return nil, err
}
return &wSdkClients{
SSL: sslClient,
CDN: cdnClient,
}, nil
}

View File

@@ -1,69 +0,0 @@
package tencentcloudssl
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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/tencentcloud-ssl"
)
type DeployerConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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) {
// 上传证书到 SSL
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,10 +0,0 @@
package volcenginealb
type ResourceType string
const (
// 资源类型:部署到指定负载均衡器。
RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
// 资源类型:部署到指定监听器。
RESOURCE_TYPE_LISTENER = ResourceType("listener")
)

View File

@@ -1,72 +0,0 @@
package volcenginecertcenter
import (
"context"
"log/slog"
xerrors "github.com/pkg/errors"
"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/volcengine-certcenter"
)
type DeployerConfig struct {
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 火山引擎地域。
Region string `json:"region"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
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) {
// 上传证书到证书中心
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &deployer.DeployResult{}, nil
}

View File

@@ -1,10 +0,0 @@
package volcengineclb
type ResourceType string
const (
// 资源类型:部署到指定负载均衡器。
RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
// 资源类型:部署到指定监听器。
RESOURCE_TYPE_LISTENER = ResourceType("listener")
)

View File

@@ -1,112 +0,0 @@
package webhook
import (
"context"
"crypto/tls"
"encoding/json"
"log/slog"
"strings"
"time"
"github.com/go-resty/resty/v2"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
)
type DeployerConfig struct {
// Webhook URL。
WebhookUrl string `json:"webhookUrl"`
// Webhook 回调数据JSON 格式)。
WebhookData string `json:"webhookData,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client := resty.New().
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second)
if config.AllowInsecureConnections {
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
httpClient: 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) {
certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to parse x509")
}
var webhookData interface{}
err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData)
if err != nil {
return nil, xerrors.Wrap(err, "failed to unmarshall webhook data")
}
replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName)
replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";"))
replaceJsonValueRecursively(webhookData, "${SUBJECT_ALT_NAMES}", strings.Join(certX509.DNSNames, ";"))
replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPem)
replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPem)
resp, err := d.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(webhookData).
Post(d.config.WebhookUrl)
if err != nil {
return nil, xerrors.Wrap(err, "failed to send webhook request")
} else if resp.StatusCode() != 200 {
return nil, xerrors.Errorf("unexpected webhook response status code: %d", resp.StatusCode())
}
d.logger.Debug("webhook responded", slog.Any("response", resp.String()))
return &deployer.DeployResult{}, nil
}
func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} {
switch v := data.(type) {
case map[string]any:
for k, val := range v {
v[k] = replaceJsonValueRecursively(val, oldStr, newStr)
}
case []any:
for i, val := range v {
v[i] = replaceJsonValueRecursively(val, oldStr, newStr)
}
case string:
return strings.ReplaceAll(v, oldStr, newStr)
}
return data
}

View File

@@ -1,61 +0,0 @@
package bark
import (
"context"
"log/slog"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/bark"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Bark 服务地址。
// 零值时默认使用官方服务器。
ServerUrl string `json:"serverUrl"`
// Bark 设备密钥。
DeviceKey string `json:"deviceKey"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
var srv notify.Notifier
if n.config.ServerUrl == "" {
srv = bark.New(n.config.DeviceKey)
} else {
srv = bark.NewWithServers(n.config.DeviceKey, n.config.ServerUrl)
}
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,57 +0,0 @@
package dingtalk
import (
"context"
"log/slog"
"github.com/nikoksr/notify/service/dingding"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// 钉钉机器人的 Token。
AccessToken string `json:"accessToken"`
// 钉钉机器人的 Secret。
Secret string `json:"secret"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv := dingding.New(&dingding.Config{
Token: n.config.AccessToken,
Secret: n.config.Secret,
})
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,104 +0,0 @@
package gotify
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Gotify 服务地址
// 示例https://gotify.example.com
Url string `json:"url"`
// Gotify Token
Token string `json:"token"`
// Gotify 消息优先级
Priority int64 `json:"priority"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
// 未来将移除
httpClient *http.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
httpClient: http.DefaultClient,
}, 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) {
// Gotify 原生实现, notify 库没有实现, 等待合并
reqBody := &struct {
Title string `json:"title"`
Message string `json:"message"`
Priority int64 `json:"priority"`
}{
Title: subject,
Message: message,
Priority: n.config.Priority,
}
// Make request
body, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.Wrap(err, "encode message body")
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
fmt.Sprintf("%s/message", n.config.Url),
bytes.NewReader(body),
)
if err != nil {
return nil, errors.Wrap(err, "create new request")
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.config.Token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
// Send request to gotify service
resp, err := n.httpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "send request to gotify server")
}
defer resp.Body.Close()
// Read response and verify success
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "read response")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gotify returned status code %d: %s", resp.StatusCode, string(result))
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,52 +0,0 @@
package lark
import (
"context"
"log/slog"
"github.com/nikoksr/notify/service/lark"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// 飞书机器人 Webhook 地址。
WebhookUrl string `json:"webhookUrl"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv := lark.NewWebhookService(n.config.WebhookUrl)
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,89 +0,0 @@
package mattermost
import (
"bytes"
"context"
"encoding/json"
"github.com/nikoksr/notify/service/mattermost"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
"io"
"log/slog"
"net/http"
)
type NotifierConfig struct {
// Mattermost 服务地址。
ServerUrl string `json:"serverUrl"`
// 频道ID
ChannelId string `json:"channelId"`
// 用户名
Username string `json:"username"`
// 密码
Password string `json:"password"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv := mattermost.New(n.config.ServerUrl)
if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil {
return nil, err
}
srv.AddReceivers(n.config.ChannelId)
// 复写消息样式
srv.PreSend(func(req *http.Request) error {
m := map[string]interface{}{
"channel_id": n.config.ChannelId,
"props": map[string]interface{}{
"attachments": []map[string]interface{}{
{
"title": subject,
"text": message,
},
},
},
}
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
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,102 +0,0 @@
package pushover
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
Token string `json:"token"` // 应用 API Token
User string `json:"user"` // 用户/分组 Key
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
// 未来将移除
httpClient *http.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
httpClient: http.DefaultClient,
}, nil
}
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
if logger == nil {
n.logger = slog.Default()
} else {
n.logger = logger
}
return n
}
// Notify 发送通知
// 参考文档https://pushover.net/api
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// 请求体
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,
}
// Make request
body, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.Wrap(err, "encode message body")
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"https://api.pushover.net/1/messages.json",
bytes.NewReader(body),
)
if err != nil {
return nil, errors.Wrap(err, "create new request")
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
// Send request to pushover service
resp, err := n.httpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "send request to pushover server")
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "read response")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("pushover returned status code %d: %s", resp.StatusCode, string(result))
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,113 +0,0 @@
package pushplus
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// PushPlus Token
Token string `json:"token"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
// 未来将移除
httpClient *http.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
httpClient: http.DefaultClient,
}, nil
}
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
if logger == nil {
n.logger = slog.Default()
} else {
n.logger = logger
}
return n
}
// Notify 发送通知
// 参考文档https://pushplus.plus/doc/guide/api.html
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// 请求体
reqBody := &struct {
Token string `json:"token"`
Title string `json:"title"`
Content string `json:"content"`
}{
Token: n.config.Token,
Title: subject,
Content: message,
}
// Make request
body, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.Wrap(err, "encode message body")
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"https://www.pushplus.plus/send",
bytes.NewReader(body),
)
if err != nil {
return nil, errors.Wrap(err, "create new request")
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
// Send request to pushplus service
resp, err := n.httpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "send request to pushplus server")
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "read response")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("pushplus returned status code %d: %s", resp.StatusCode, string(result))
}
// 解析响应
var errorResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
if err := json.Unmarshal(result, &errorResponse); err != nil {
return nil, errors.Wrap(err, "decode response")
}
if errorResponse.Code != 200 {
return nil, fmt.Errorf("pushplus returned error: %s", errorResponse.Msg)
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,66 +0,0 @@
package serverchan
import (
"context"
"log/slog"
"net/http"
notifyHttp "github.com/nikoksr/notify/service/http"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// ServerChan 服务地址。
Url string `json:"url"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
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)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,59 +0,0 @@
package telegram
import (
"context"
"log/slog"
"github.com/nikoksr/notify/service/telegram"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Telegram API Token。
ApiToken string `json:"apiToken"`
// Telegram Chat ID。
ChatId int64 `json:"chatId"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv, err := telegram.New(n.config.ApiToken)
if err != nil {
return nil, err
}
srv.AddReceivers(n.config.ChatId)
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,64 +0,0 @@
package webhook
import (
"context"
"crypto/tls"
"log/slog"
"net/http"
webhook "github.com/nikoksr/notify/service/http"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Webhook URL。
Url string `json:"url"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv := webhook.New()
srv.AddReceiversURLs(n.config.Url)
if n.config.AllowInsecureConnections {
tlsConfig := &tls.Config{InsecureSkipVerify: true}
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
srv.WithClient(client)
}
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,68 +0,0 @@
package serverchan
import (
"context"
"log/slog"
"net/http"
notifyHttp "github.com/nikoksr/notify/service/http"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// 企业微信机器人 Webhook 地址。
WebhookUrl string `json:"webhookUrl"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
}, 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) {
srv := notifyHttp.New()
srv.AddReceivers(&notifyHttp.Webhook{
URL: n.config.WebhookUrl,
Header: http.Header{},
ContentType: "application/json",
Method: http.MethodPost,
BuildPayload: func(subject, message string) (payload any) {
return map[string]any{
"msgtype": "text",
"text": map[string]string{
"content": subject + "\n\n" + message,
},
}
},
})
err = srv.Send(ctx, subject, message)
if err != nil {
return nil, err
}
return &notifier.NotifyResult{}, nil
}

View File

@@ -1,140 +0,0 @@
package onepanelssl
import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"strings"
"time"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk"
)
type UploaderConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *opsdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 遍历证书列表,避免重复上传
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
return nil, err
} else if res != nil {
u.logger.Info("ssl certificate already exists")
return res, nil
}
// 生成新证书名(需符合 1Panel 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传证书
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
Type: "paste",
Description: certName,
Certificate: certPem,
PrivateKey: privkeyPem,
}
uploadWebsiteSSLResp, err := u.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
u.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'")
}
// 遍历证书列表,获取刚刚上传证书 ID
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
return nil, err
} else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage())
} else {
return res, nil
}
}
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
searchWebsiteSSLPageNumber := int32(1)
searchWebsiteSSLPageSize := int32(100)
for {
searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{
Page: searchWebsiteSSLPageNumber,
PageSize: searchWebsiteSSLPageSize,
}
searchWebsiteSSLResp, err := u.sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq)
u.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.SearchWebsiteSSL'")
}
for _, sslItem := range searchWebsiteSSLResp.Data.Items {
if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPem) &&
strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPem) {
// 如果已存在相同证书,直接返回
return &uploader.UploadResult{
CertId: fmt.Sprintf("%d", sslItem.ID),
CertName: sslItem.Description,
}, nil
}
}
if len(searchWebsiteSSLResp.Data.Items) < int(searchWebsiteSSLPageSize) {
break
} else {
searchWebsiteSSLPageNumber++
}
}
return nil, nil
}
func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
return client, nil
}

View File

@@ -1,79 +0,0 @@
package dogecloud
import (
"context"
"fmt"
"log/slog"
"time"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
)
type UploaderConfig struct {
// 多吉云 AccessKey。
AccessKey string `json:"accessKey"`
// 多吉云 SecretKey。
SecretKey string `json:"secretKey"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *dogesdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKey, config.SecretKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 生成新证书名(需符合多吉云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://docs.dogecloud.com/cdn/api-cert-upload
uploadSslCertResp, err := u.sdkClient.UploadCdnCert(certName, certPem, privkeyPem)
u.logger.Debug("sdk request 'cdn.UploadCdnCert'", slog.Any("response", uploadSslCertResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadCdnCert'")
}
certId = fmt.Sprintf("%d", uploadSslCertResp.Data.Id)
return &uploader.UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func createSdkClient(accessKey, secretKey string) (*dogesdk.Client, error) {
client := dogesdk.NewClient(accessKey, secretKey)
return client, nil
}

View File

@@ -1,96 +0,0 @@
package gcorecdn
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
gprovider "github.com/G-Core/gcorelabscdn-go/gcore/provider"
gsslcerts "github.com/G-Core/gcorelabscdn-go/sslcerts"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
gcoresdk "github.com/usual2970/certimate/internal/pkg/vendors/gcore-sdk/common"
)
type UploaderConfig struct {
// Gcore API Token。
ApiToken string `json:"apiToken"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *gsslcerts.Service
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiToken)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 生成新证书名(需符合 Gcore 命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 新增证书
// REF: https://api.gcore.com/docs/cdn#tag/CA-certificates/operation/ca_certificates-add
createCertificateReq := &gsslcerts.CreateRequest{
Name: certName,
Cert: certPem,
PrivateKey: privkeyPem,
Automated: false,
ValidateRootCA: false,
}
createCertificateResp, err := u.sdkClient.Create(context.TODO(), createCertificateReq)
u.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcerts.Create'")
}
certId = fmt.Sprintf("%d", createCertificateResp.ID)
certName = createCertificateResp.Name
return &uploader.UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func createSdkClient(apiToken string) (*gsslcerts.Service, error) {
if apiToken == "" {
return nil, errors.New("invalid gcore api token")
}
requester := gprovider.NewClient(
gcoresdk.BASE_URL,
gprovider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)),
)
service := gsslcerts.NewService(requester)
return service, nil
}

View File

@@ -1,97 +0,0 @@
package qiniusslcert
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
xerrors "github.com/pkg/errors"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
qiniusdk "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
)
type UploaderConfig struct {
// 七牛云 AccessKey。
AccessKey string `json:"accessKey"`
// 七牛云 SecretKey。
SecretKey string `json:"secretKey"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *qiniusdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKey, config.SecretKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 生成新证书名(需符合七牛云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
uploadSslCertResp, err := u.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPem, privkeyPem)
u.logger.Debug("sdk request 'cdn.UploadSslCert'", slog.Any("response", uploadSslCertResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
}
certId = uploadSslCertResp.CertID
return &uploader.UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func createSdkClient(accessKey, secretKey string) (*qiniusdk.Client, error) {
if secretKey == "" {
return nil, errors.New("invalid qiniu access key")
}
if secretKey == "" {
return nil, errors.New("invalid qiniu secret key")
}
credential := auth.New(accessKey, secretKey)
client := qiniusdk.NewClient(credential)
return client, nil
}

View File

@@ -1,169 +0,0 @@
package rainyunsslcenter
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
)
type UploaderConfig struct {
// 雨云 API 密钥。
ApiKey string `json:"ApiKey"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *rainyunsdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
return nil, err
} else if res != nil {
u.logger.Info("ssl certificate already exists")
return res, nil
}
// SSL 证书上传
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
sslCenterCreateReq := &rainyunsdk.SslCenterCreateRequest{
Cert: certPem,
Key: privkeyPem,
}
sslCenterCreateResp, err := u.sdkClient.SslCenterCreate(sslCenterCreateReq)
u.logger.Debug("sdk request 'sslcenter.Create'", slog.Any("request", sslCenterCreateReq), slog.Any("response", sslCenterCreateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Create'")
}
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
return nil, err
} else if res == nil {
return nil, errors.New("rainyun sslcenter: no certificate found")
} else {
return res, nil
}
}
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 遍历 SSL 证书列表,避免重复上传
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048
sslCenterListPage := int32(1)
sslCenterListPerPage := int32(100)
for {
sslCenterListReq := &rainyunsdk.SslCenterListRequest{
Filters: &rainyunsdk.SslCenterListFilters{
Domain: &certX509.Subject.CommonName,
},
Page: &sslCenterListPage,
PerPage: &sslCenterListPerPage,
}
sslCenterListResp, err := u.sdkClient.SslCenterList(sslCenterListReq)
u.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.List'")
}
if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil {
for _, sslItem := range sslCenterListResp.Data.Records {
// 先对比证书的多域名
if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") {
continue
}
// 再对比证书的有效期
if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() {
continue
}
// 最后对比证书内容
sslCenterGetResp, err := u.sdkClient.SslCenterGet(sslItem.ID)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Get'")
}
var isSameCert bool
if sslCenterGetResp.Data != nil {
if sslCenterGetResp.Data.Cert == certPem {
isSameCert = true
} else {
oldCertX509, err := certutil.ParseCertificateFromPEM(sslCenterGetResp.Data.Cert)
if err != nil {
continue
}
isSameCert = certutil.EqualCertificate(certX509, oldCertX509)
}
}
// 如果已存在相同证书,直接返回
if isSameCert {
return &uploader.UploadResult{
CertId: fmt.Sprintf("%d", sslItem.ID),
}, nil
}
}
}
if sslCenterListResp.Data == nil || len(sslCenterListResp.Data.Records) < int(sslCenterListPerPage) {
break
} else {
sslCenterListPage++
}
}
return nil, nil
}
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
if apiKey == "" {
return nil, errors.New("invalid rainyun api key")
}
client := rainyunsdk.NewClient(apiKey)
return client, nil
}

View File

@@ -1,83 +0,0 @@
package upyunssl
import (
"context"
"errors"
"log/slog"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
upyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/upyun-sdk/console"
)
type UploaderConfig struct {
// 又拍云账号用户名。
Username string `json:"username"`
// 又拍云账号密码。
Password string `json:"password"`
}
type UploaderProvider struct {
config *UploaderConfig
logger *slog.Logger
sdkClient *upyunsdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.Username, config.Password)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
if logger == nil {
u.logger = slog.Default()
} else {
u.logger = logger
}
return u
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 上传证书
uploadHttpsCertificateReq := &upyunsdk.UploadHttpsCertificateRequest{
Certificate: certPem,
PrivateKey: privkeyPem,
}
uploadHttpsCertificateResp, err := u.sdkClient.UploadHttpsCertificate(uploadHttpsCertificateReq)
u.logger.Debug("sdk request 'console.UploadHttpsCertificate'", slog.Any("response", uploadHttpsCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.UploadHttpsCertificate'")
}
return &uploader.UploadResult{
CertId: uploadHttpsCertificateResp.Data.Result.CertificateId,
}, nil
}
func createSdkClient(username, password string) (*upyunsdk.Client, error) {
if username == "" {
return nil, errors.New("invalid upyun username")
}
if password == "" {
return nil, errors.New("invalid upyun password")
}
client := upyunsdk.NewClient(username, password)
return client, nil
}

View File

@@ -1,32 +0,0 @@
package uploader
import (
"context"
"log/slog"
)
// 表示定义证书上传器的抽象类型接口。
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
type Uploader interface {
WithLogger(logger *slog.Logger) Uploader
// 上传证书。
//
// 入参:
// - ctx上下文。
// - certPem证书 PEM 内容。
// - privkeyPem私钥 PEM 内容。
//
// 出参:
// - res上传结果。
// - err: 错误。
Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error)
}
// 表示证书上传结果的数据结构,包含上传后的证书 ID、名称和其他数据。
type UploadResult struct {
CertId string `json:"certId"`
CertName string `json:"certName,omitzero"`
ExtendedData map[string]any `json:"extendedData,omitempty"`
}

View File

@@ -1,42 +0,0 @@
package onepanelsdk
import (
"fmt"
"net/http"
)
func (c *Client) UpdateSystemSSL(req *UpdateSystemSSLRequest) (*UpdateSystemSSLResponse, error) {
resp := &UpdateSystemSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/settings/ssl/update", req, resp)
return resp, err
}
func (c *Client) SearchWebsiteSSL(req *SearchWebsiteSSLRequest) (*SearchWebsiteSSLResponse, error) {
resp := &SearchWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/search", req, resp)
return resp, err
}
func (c *Client) GetWebsiteSSL(req *GetWebsiteSSLRequest) (*GetWebsiteSSLResponse, error) {
resp := &GetWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/ssl/%d", req.SSLID), req, resp)
return resp, err
}
func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) {
resp := &UploadWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp)
return resp, err
}
func (c *Client) GetHttpsConf(req *GetHttpsConfRequest) (*GetHttpsConfResponse, error) {
resp := &GetHttpsConfResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp)
return resp, err
}
func (c *Client) UpdateHttpsConf(req *UpdateHttpsConfRequest) (*UpdateHttpsConfResponse, error) {
resp := &UpdateHttpsConfResponse{}
err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp)
return resp, err
}

View File

@@ -1,104 +0,0 @@
package onepanelsdk
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiHost string
apiKey string
client *resty.Client
}
func NewClient(apiHost, apiKey string) *Client {
client := resty.New()
return &Client{
apiHost: strings.TrimRight(apiHost, "/"),
apiKey: apiKey,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) WithTLSConfig(config *tls.Config) *Client {
c.client.SetTLSClientConfig(config)
return c
}
func (c *Client) generateToken(timestamp string) string {
tokenMd5 := md5.Sum([]byte("1panel" + c.apiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
return tokenMd5Hex
}
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
req := c.client.R()
req.Method = method
req.URL = c.apiHost + "/api/v1" + path
if strings.EqualFold(method, http.MethodGet) {
qs := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
qs[k] = fmt.Sprintf("%v", v)
}
}
}
req = req.SetQueryParams(qs)
} else {
req = req.
SetHeader("Content-Type", "application/json").
SetBody(params)
}
timestamp := fmt.Sprintf("%d", time.Now().Unix())
token := c.generateToken(timestamp)
req.SetHeader("1Panel-Timestamp", timestamp)
req.SetHeader("1Panel-Token", token)
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("1panel api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("1panel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error {
resp, err := c.sendRequest(method, path, params)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &result)
}
return err
}
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return fmt.Errorf("1panel api error: failed to parse response: %w", err)
} else if errcode := result.GetCode(); errcode/100 != 2 {
return fmt.Errorf("1panel api error: %d - %s", errcode, result.GetMessage())
}
return nil
}

View File

@@ -1,131 +0,0 @@
package onepanelsdk
type BaseResponse interface {
GetCode() int32
GetMessage() string
}
type baseResponse struct {
Code *int32 `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
func (r *baseResponse) GetCode() int32 {
if r.Code != nil {
return *r.Code
}
return 0
}
func (r *baseResponse) GetMessage() string {
if r.Message != nil {
return *r.Message
}
return ""
}
type UpdateSystemSSLRequest struct {
Cert string `json:"cert"`
Key string `json:"key"`
SSLType string `json:"sslType"`
SSL string `json:"ssl"`
SSLID int64 `json:"sslID"`
AutoRestart string `json:"autoRestart"`
}
type UpdateSystemSSLResponse struct {
baseResponse
}
type SearchWebsiteSSLRequest struct {
Page int32 `json:"page"`
PageSize int32 `json:"pageSize"`
}
type SearchWebsiteSSLResponse struct {
baseResponse
Data *struct {
Items []*struct {
ID int64 `json:"id"`
PEM string `json:"pem"`
PrivateKey string `json:"privateKey"`
Domains string `json:"domains"`
Description string `json:"description"`
Status string `json:"status"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
} `json:"items"`
Total int32 `json:"total"`
} `json:"data,omitempty"`
}
type GetWebsiteSSLRequest struct {
SSLID int64 `json:"-"`
}
type GetWebsiteSSLResponse struct {
baseResponse
Data *struct {
ID int64 `json:"id"`
Provider string `json:"provider"`
Description string `json:"description"`
PrimaryDomain string `json:"primaryDomain"`
Domains string `json:"domains"`
Type string `json:"type"`
Organization string `json:"organization"`
Status string `json:"status"`
StartDate string `json:"startDate"`
ExpireDate string `json:"expireDate"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
} `json:"data,omitempty"`
}
type UploadWebsiteSSLRequest struct {
Type string `json:"type"`
SSLID int64 `json:"sslID"`
Certificate string `json:"certificate"`
CertificatePath string `json:"certificatePath"`
PrivateKey string `json:"privateKey"`
PrivateKeyPath string `json:"privateKeyPath"`
Description string `json:"description"`
}
type UploadWebsiteSSLResponse struct {
baseResponse
}
type GetHttpsConfRequest struct {
WebsiteID int64 `json:"-"`
}
type GetHttpsConfResponse struct {
baseResponse
Data *struct {
Enable bool `json:"enable"`
HttpConfig string `json:"httpConfig"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
} `json:"data,omitempty"`
}
type UpdateHttpsConfRequest struct {
WebsiteID int64 `json:"websiteId"`
Enable bool `json:"enable"`
Type string `json:"type"`
WebsiteSSLID int64 `json:"websiteSSLId"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
}
type UpdateHttpsConfResponse struct {
baseResponse
}

View File

@@ -1,23 +0,0 @@
package baishansdk
import (
"net/http"
)
func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
resp := &CreateCertificateResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/v2/domain/certificate", req, resp)
return resp, err
}
func (c *Client) GetDomainConfig(req *GetDomainConfigRequest) (*GetDomainConfigResponse, error) {
resp := &GetDomainConfigResponse{}
err := c.sendRequestWithResult(http.MethodGet, "/v2/domain/config", req, resp)
return resp, err
}
func (c *Client) SetDomainConfig(req *SetDomainConfigRequest) (*SetDomainConfigResponse, error) {
resp := &SetDomainConfigResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/v2/domain/config", req, resp)
return resp, err
}

View File

@@ -1,100 +0,0 @@
package baishansdk
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"reflect"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiToken string
client *resty.Client
}
func NewClient(apiToken string) *Client {
client := resty.New()
return &Client{
apiToken: apiToken,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
req := c.client.R()
req.Method = method
req.URL = "https://cdn.api.baishan.com" + path
if strings.EqualFold(method, http.MethodGet) {
qs := url.Values{}
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < rv.Len(); i++ {
qs.Add(fmt.Sprintf("%s[]", k), fmt.Sprintf("%v", rv.Index(i).Interface()))
}
case reflect.Map:
for _, rk := range rv.MapKeys() {
qs.Add(fmt.Sprintf("%s[%s]", k, rk.Interface()), fmt.Sprintf("%v", rv.MapIndex(rk).Interface()))
}
default:
qs.Set(k, fmt.Sprintf("%v", v))
}
}
}
}
req = req.
SetQueryParam("token", c.apiToken).
SetQueryParamsFromValues(qs)
} else {
req = req.
SetHeader("Content-Type", "application/json").
SetQueryParam("token", c.apiToken).
SetBody(params)
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("baishan api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("baishan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error {
resp, err := c.sendRequest(method, path, params)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &result)
}
return err
}
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return fmt.Errorf("baishan api error: failed to parse response: %w", err)
} else if errcode := result.GetCode(); errcode != 0 {
return fmt.Errorf("baishan api error: %d - %s", errcode, result.GetMessage())
}
return nil
}

View File

@@ -1,82 +0,0 @@
package baishansdk
import "encoding/json"
type BaseResponse interface {
GetCode() int32
GetMessage() string
}
type baseResponse struct {
Code *int32 `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
func (r *baseResponse) GetCode() int32 {
if r.Code != nil {
return *r.Code
}
return 0
}
func (r *baseResponse) GetMessage() string {
if r.Message != nil {
return *r.Message
}
return ""
}
type CreateCertificateRequest struct {
CertificateId *string `json:"cert_id,omitempty"`
Certificate string `json:"certificate"`
Key string `json:"key"`
Name string `json:"name"`
}
type CreateCertificateResponse struct {
baseResponse
Data *DomainCertificate `json:"data,omitempty"`
}
type GetDomainConfigRequest struct {
Domains string `json:"domains"`
Config []string `json:"config"`
}
type GetDomainConfigResponse struct {
baseResponse
Data []*struct {
Domain string `json:"domain"`
Config *DomainConfig `json:"config"`
} `json:"data,omitempty"`
}
type SetDomainConfigRequest struct {
Domains string `json:"domains"`
Config *DomainConfig `json:"config"`
}
type SetDomainConfigResponse struct {
baseResponse
Data *struct {
Config *DomainConfig `json:"config"`
} `json:"data,omitempty"`
}
type DomainCertificate struct {
CertId json.Number `json:"cert_id"`
Name string `json:"name"`
CertStartTime string `json:"cert_start_time"`
CertExpireTime string `json:"cert_expire_time"`
}
type DomainConfig struct {
Https *DomainConfigHttps `json:"https"`
}
type DomainConfigHttps struct {
CertId json.Number `json:"cert_id"`
ForceHttps *string `json:"force_https,omitempty"`
EnableHttp2 *string `json:"http2,omitempty"`
EnableOcsp *string `json:"ocsp,omitempty"`
}

View File

@@ -1,31 +0,0 @@
package btpanelsdk
func (c *Client) ConfigSavePanelSSL(req *ConfigSavePanelSSLRequest) (*ConfigSavePanelSSLResponse, error) {
resp := &ConfigSavePanelSSLResponse{}
err := c.sendRequestWithResult("/config?action=SavePanelSSL", req, resp)
return resp, err
}
func (c *Client) SiteSetSSL(req *SiteSetSSLRequest) (*SiteSetSSLResponse, error) {
resp := &SiteSetSSLResponse{}
err := c.sendRequestWithResult("/site?action=SetSSL", req, resp)
return resp, err
}
func (c *Client) SystemServiceAdmin(req *SystemServiceAdminRequest) (*SystemServiceAdminResponse, error) {
resp := &SystemServiceAdminResponse{}
err := c.sendRequestWithResult("/system?action=ServiceAdmin", req, resp)
return resp, err
}
func (c *Client) SSLCertSaveCert(req *SSLCertSaveCertRequest) (*SSLCertSaveCertResponse, error) {
resp := &SSLCertSaveCertResponse{}
err := c.sendRequestWithResult("/ssl/cert/save_cert", req, resp)
return resp, err
}
func (c *Client) SSLSetBatchCertToSite(req *SSLSetBatchCertToSiteRequest) (*SSLSetBatchCertToSiteResponse, error) {
resp := &SSLSetBatchCertToSiteResponse{}
err := c.sendRequestWithResult("/ssl?action=SetBatchCertToSite", req, resp)
return resp, err
}

View File

@@ -1,112 +0,0 @@
package btpanelsdk
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiHost string
apiKey string
client *resty.Client
}
func NewClient(apiHost, apiKey string) *Client {
client := resty.New()
return &Client{
apiHost: strings.TrimRight(apiHost, "/"),
apiKey: apiKey,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) WithTLSConfig(config *tls.Config) *Client {
c.client.SetTLSClientConfig(config)
return c
}
func (c *Client) generateSignature(timestamp string) string {
keyMd5 := md5.Sum([]byte(c.apiKey))
keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:]))
signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex))
signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:]))
return signMd5Hex
}
func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) {
timestamp := time.Now().Unix()
data := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
switch reflect.Indirect(reflect.ValueOf(v)).Kind() {
case reflect.String:
data[k] = v.(string)
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
data[k] = fmt.Sprintf("%v", v)
default:
if t, ok := v.(time.Time); ok {
data[k] = t.Format(time.RFC3339)
} else {
jbytes, _ := json.Marshal(v)
data[k] = string(jbytes)
}
}
}
}
}
data["request_time"] = fmt.Sprintf("%d", timestamp)
data["request_token"] = c.generateSignature(fmt.Sprintf("%d", timestamp))
url := c.apiHost + path
req := c.client.R().
SetHeader("Content-Type", "application/x-www-form-urlencoded").
SetFormData(data)
resp, err := req.Post(url)
if err != nil {
return resp, fmt.Errorf("baota api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) sendRequestWithResult(path string, params interface{}, result BaseResponse) error {
resp, err := c.sendRequest(path, params)
if err != nil {
return err
}
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return fmt.Errorf("baota api error: failed to parse response: %w", err)
} else if errstatus := result.GetStatus(); errstatus != nil && !*errstatus {
if result.GetMessage() == nil {
return fmt.Errorf("baota api error: unknown error")
} else {
return fmt.Errorf("baota api error: %s", *result.GetMessage())
}
}
return nil
}

View File

@@ -1,75 +0,0 @@
package btpanelsdk
type BaseResponse interface {
GetStatus() *bool
GetMessage() *string
}
type baseResponse struct {
Status *bool `json:"status,omitempty"`
Message *string `json:"msg,omitempty"`
}
func (r *baseResponse) GetStatus() *bool {
return r.Status
}
func (r *baseResponse) GetMessage() *string {
return r.Message
}
type ConfigSavePanelSSLRequest struct {
PrivateKey string `json:"privateKey"`
Certificate string `json:"certPem"`
}
type ConfigSavePanelSSLResponse struct {
baseResponse
}
type SiteSetSSLRequest struct {
Type string `json:"type"`
SiteName string `json:"siteName"`
PrivateKey string `json:"key"`
Certificate string `json:"csr"`
}
type SiteSetSSLResponse struct {
baseResponse
}
type SystemServiceAdminRequest struct {
Name string `json:"name"`
Type string `json:"type"`
}
type SystemServiceAdminResponse struct {
baseResponse
}
type SSLCertSaveCertRequest struct {
PrivateKey string `json:"key"`
Certificate string `json:"csr"`
}
type SSLCertSaveCertResponse struct {
baseResponse
SSLHash string `json:"ssl_hash"`
}
type SSLSetBatchCertToSiteRequest struct {
BatchInfo []*SSLSetBatchCertToSiteRequestBatchInfo `json:"BatchInfo"`
}
type SSLSetBatchCertToSiteRequestBatchInfo struct {
SSLHash string `json:"ssl_hash"`
SiteName string `json:"siteName"`
CertName string `json:"certName"`
}
type SSLSetBatchCertToSiteResponse struct {
baseResponse
TotalCount int32 `json:"total"`
SuccessCount int32 `json:"success"`
FailedCount int32 `json:"faild"`
}

View File

@@ -1,11 +0,0 @@
package bunnysdk
import (
"fmt"
"net/http"
)
func (c *Client) AddCustomCertificate(req *AddCustomCertificateRequest) ([]byte, error) {
resp, err := c.sendRequest(http.MethodPost, fmt.Sprintf("/pullzone/%s/addCertificate", req.PullZoneId), req)
return resp.Body(), err
}

View File

@@ -1,66 +0,0 @@
package bunnysdk
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiToken string
client *resty.Client
}
func NewClient(apiToken string) *Client {
client := resty.New()
return &Client{
apiToken: apiToken,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
req := c.client.R()
req.Method = method
req.URL = "https://api.bunny.net" + path
req = req.SetHeader("AccessKey", c.apiToken)
if strings.EqualFold(method, http.MethodGet) {
qs := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
qs[k] = fmt.Sprintf("%v", v)
}
}
}
req = req.SetQueryParams(qs)
} else {
req = req.
SetHeader("Content-Type", "application/json").
SetBody(params)
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("bunny api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("bunny api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}

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