Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff923dd1b | ||
|
|
f4f13f91f2 | ||
|
|
034aa980e6 | ||
|
|
6ac7a51ce0 | ||
|
|
cf0c0e3e2c | ||
|
|
23e5cb5669 | ||
|
|
e4ba4c9b37 | ||
|
|
9ed64bdc9a | ||
|
|
e9b6fb55ff | ||
|
|
80caf881ae | ||
|
|
c36db3545f | ||
|
|
a367585ab4 | ||
|
|
2994cb5c65 | ||
|
|
1bedb31a3c | ||
|
|
8fecebc254 | ||
|
|
44497a0969 | ||
|
|
5362371bda | ||
|
|
8b04e96a7d | ||
|
|
5d93334426 | ||
|
|
150b666d4b | ||
|
|
94579d65c4 | ||
|
|
551b06b4e8 | ||
|
|
76fc47a274 | ||
|
|
35e1bfcd7f | ||
|
|
24df7913fe | ||
|
|
83674e4b35 | ||
|
|
22d3aeb7b5 | ||
|
|
cf005711c0 | ||
|
|
0a00d0c52f | ||
|
|
9aa17a0395 | ||
|
|
65ecdf7dc2 | ||
|
|
0dfa5994cc | ||
|
|
5d2844fdb6 | ||
|
|
44332b9d07 | ||
|
|
20a23e148c | ||
|
|
0bcb6206f4 | ||
|
|
943b9827ee | ||
|
|
741f3ec212 | ||
|
|
8549a17675 | ||
|
|
718cfccbea | ||
|
|
2458fa26d8 | ||
|
|
ac24684d2b | ||
|
|
106dbd9538 | ||
|
|
f9efb2b800 | ||
|
|
897d124d5b | ||
|
|
34daf9ccac | ||
|
|
269a97e81e | ||
|
|
2fd57621d8 | ||
|
|
76de837214 | ||
|
|
1e41020728 | ||
|
|
8a78e49bf0 | ||
|
|
e6726e4c02 | ||
|
|
76330a4a1a | ||
|
|
7e5f0097e4 | ||
|
|
18e1c02d1c | ||
|
|
28992f178e | ||
|
|
c41f34c352 | ||
|
|
6b5580a30c | ||
|
|
1dee14e32d | ||
|
|
1e3c4881d0 | ||
|
|
657964cda4 | ||
|
|
893aac916c | ||
|
|
68da6cf3ae | ||
|
|
0d96ea9eef | ||
|
|
0ceb44a7cd | ||
|
|
4fec0036cb | ||
|
|
f82eee4636 | ||
|
|
260cfb96ec | ||
|
|
f71a519674 | ||
|
|
369c146eca | ||
|
|
83264a6946 | ||
|
|
3c3d4e9109 | ||
|
|
ce55365292 | ||
|
|
be495839b6 | ||
|
|
a27a9f55a7 | ||
|
|
10e14caf35 | ||
|
|
59af246479 | ||
|
|
1f52eaca01 | ||
|
|
d833f4b5ff | ||
|
|
bfee39049d | ||
|
|
b4599df6c6 | ||
|
|
261c6f6956 | ||
|
|
b97d77c848 | ||
|
|
c1cefe0e7f | ||
|
|
55b77fdf5c | ||
|
|
16967c4ab1 | ||
|
|
61a4fd8657 | ||
|
|
67ca7e3097 | ||
|
|
26fa8e75bd | ||
|
|
aeaa45b713 | ||
|
|
edeac86f06 | ||
|
|
4e0c23165f | ||
|
|
feb851a3fc | ||
|
|
3103d60508 | ||
|
|
53be6b5f5b | ||
|
|
9d3e0d1090 | ||
|
|
f8aef129cf | ||
|
|
c419b2c8b4 | ||
|
|
e1a3a3e7c7 | ||
|
|
b47a1a13cb | ||
|
|
3397f424bc | ||
|
|
48672d1a44 | ||
|
|
38dc8a63d9 | ||
|
|
009e8fb976 | ||
|
|
6d7a91f49b | ||
|
|
9d4d14db06 | ||
|
|
c9f347f77a | ||
|
|
0396d8222e | ||
|
|
305f3de50f | ||
|
|
ffacfe0f42 | ||
|
|
be9e66c7d3 | ||
|
|
1238508bdb | ||
|
|
1ab5c4035a | ||
|
|
67fa9d91bf | ||
|
|
dc5f9abf20 | ||
|
|
7240a42fbc | ||
|
|
6fbb6d4992 | ||
|
|
86838f305b | ||
|
|
1b1b5939c5 | ||
|
|
ffdd61b5ee | ||
|
|
adad5d86ba | ||
|
|
e7870e2b05 | ||
|
|
548cbbfdd4 | ||
|
|
da4715e6dc | ||
|
|
506ab4f18e | ||
|
|
d87026d5be | ||
|
|
1690963aaf | ||
|
|
20d2c5699c | ||
|
|
e660e9cad1 | ||
|
|
26d7b0ba03 | ||
|
|
ee097b3135 | ||
|
|
f5052e9a58 | ||
|
|
3b3376899c | ||
|
|
a24a3595fa | ||
|
|
6a14d801f1 | ||
|
|
332c5c5127 | ||
|
|
f9568f1a4a | ||
|
|
b458720dca | ||
|
|
935a320100 | ||
|
|
361d0de17c | ||
|
|
024b3c936e | ||
|
|
dc720a5d99 | ||
|
|
af3e20709d | ||
|
|
ea9e9165b6 | ||
|
|
ee531dd186 | ||
|
|
51abe8de56 | ||
|
|
e2254faf15 | ||
|
|
cea6be37dc | ||
|
|
46dccb176e | ||
|
|
5411b9cb92 | ||
|
|
9f6ea410af | ||
|
|
528a3d9da8 | ||
|
|
564eb48ebe | ||
|
|
92a6b179d4 | ||
|
|
83393a4ee1 | ||
|
|
6875151717 | ||
|
|
2a8c6cf033 | ||
|
|
7544286b0f | ||
|
|
7c685646da | ||
|
|
d82a9c9253 | ||
|
|
59584a2961 | ||
|
|
195aa54cdc | ||
|
|
4b324e6a22 | ||
|
|
0e575a0ce7 | ||
|
|
7ab8517a93 | ||
|
|
1dca6ecf8d | ||
|
|
8bec234fe8 | ||
|
|
bff18a7be7 | ||
|
|
bac00491fe | ||
|
|
f8da3ded0d | ||
|
|
b01849eb0c | ||
|
|
c9eb487953 | ||
|
|
dc383644d6 | ||
|
|
a8f718afa0 | ||
|
|
cd76d170b2 | ||
|
|
7b129c11e9 | ||
|
|
f7972d5b68 | ||
|
|
b1a0d84033 | ||
|
|
969fba8a57 | ||
|
|
63865b5fbd | ||
|
|
46c32f15e3 | ||
|
|
5f62c887c0 | ||
|
|
c85beaa52b | ||
|
|
885cdfaec9 | ||
|
|
011130432c | ||
|
|
062d66222a | ||
|
|
e53749e16e | ||
|
|
dbfb84ec6d | ||
|
|
265842feeb | ||
|
|
0c35928eee | ||
|
|
ea4bcb4aaf | ||
|
|
716f5f1426 | ||
|
|
18a7bf0d66 | ||
|
|
908d33f186 | ||
|
|
68b9171390 | ||
|
|
45005a5073 | ||
|
|
9c41b0e357 | ||
|
|
b031f00764 | ||
|
|
a4fc8dfc56 | ||
|
|
f168bd903d | ||
|
|
fc55e37454 | ||
|
|
f6a3f4edfa | ||
|
|
560d21c854 | ||
|
|
3a213dc9c3 | ||
|
|
f0e7fe695d | ||
|
|
8d41a9aae7 | ||
|
|
896b5d3a13 | ||
|
|
88e64717cd |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,11 +10,13 @@
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
__debug_bin*
|
__debug_bin*
|
||||||
|
|
||||||
vendor
|
vendor
|
||||||
pb_data
|
pb_data
|
||||||
build
|
build
|
||||||
main
|
main
|
||||||
ui/dist
|
/ui/dist/*
|
||||||
|
!/ui/dist/.gitkeep
|
||||||
./dist
|
./dist
|
||||||
./certimate
|
./certimate
|
||||||
/docker/data
|
/docker/data
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -35,3 +35,6 @@ help:
|
|||||||
@echo " make help - 显示此帮助信息"
|
@echo " make help - 显示此帮助信息"
|
||||||
|
|
||||||
.PHONY: all build clean help
|
.PHONY: all build clean help
|
||||||
|
|
||||||
|
local.run:
|
||||||
|
go mod vendor&& npm --prefix=./ui install && npm --prefix=./ui run build && go run main.go serve --http 127.0.0.1:8090
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -55,8 +55,7 @@ mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubuserconten
|
|||||||
```bash
|
```bash
|
||||||
git clone EMAIL:usual2970/certimate.git
|
git clone EMAIL:usual2970/certimate.git
|
||||||
cd certimate
|
cd certimate
|
||||||
go mod vendor
|
make local.run
|
||||||
go run main.go serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 二、使用
|
## 二、使用
|
||||||
@@ -72,34 +71,34 @@ go run main.go serve
|
|||||||
|
|
||||||
## 三、支持的服务商列表
|
## 三、支持的服务商列表
|
||||||
|
|
||||||
| 服务商 | 支持申请证书 | 支持部署证书 | 备注 |
|
| 服务商 | 支持申请证书 | 支持部署证书 | 备注 |
|
||||||
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
|
| :--------: | :----------: | :----------: | ----------------------------------------------------------------- |
|
||||||
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN |
|
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、SLB |
|
||||||
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN |
|
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、ECDN、CLB、TEO |
|
||||||
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN |
|
| 百度智能云 | | √ | 可部署到百度智能云 CDN |
|
||||||
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
||||||
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
||||||
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
| 多吉云 | | √ | 可部署到多吉云 CDN |
|
||||||
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
||||||
| Namesilo | √ | | 可签发在 Namesilo 注册的域名 |
|
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
||||||
| PowerDNS | √ | | 可签发通过PowerDNS管理的域名 |
|
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
||||||
| HTTP request | √ | | 可签发通过HTTP Request修改dns的域名 |
|
| Namesilo | √ | | 可签发在 Namesilo 注册的域名 |
|
||||||
| 本地部署 | | √ | 可部署到本地服务器 |
|
| PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 |
|
||||||
| SSH | | √ | 可部署到 SSH 服务器 |
|
| HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 |
|
||||||
| Webhook | | √ | 可部署时回调到 Webhook |
|
| 本地部署 | | √ | 可部署到本地服务器 |
|
||||||
| Kubernetes | | √ | 可部署到 Kubernetes Secret |
|
| SSH | | √ | 可部署到 SSH 服务器 |
|
||||||
|
| Webhook | | √ | 可部署时回调到 Webhook |
|
||||||
|
| Kubernetes | | √ | 可部署到 Kubernetes Secret |
|
||||||
|
|
||||||
## 四、系统截图
|
## 四、系统截图
|
||||||
|
|
||||||

|
<div align="center">
|
||||||
|
<img src="https://i.imgur.com/SYjjbql.jpeg" title="Login page" width="95%"/>
|
||||||

|
<img src="https://i.imgur.com/WMVbBId.jpeg" title="Dashboard page" width="47%"/>
|
||||||
|
<img src="https://i.imgur.com/8wit3ZA.jpeg" title="Domains page" width="47%"/>
|
||||||

|
<img src="https://i.imgur.com/EWtOoJ0.jpeg" title="Accesses page" width="47%"/>
|
||||||
|
<img src="https://i.imgur.com/aaPtSW7.jpeg" title="History page" width="47%"/>
|
||||||

|
</div>
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 五、概念
|
## 五、概念
|
||||||
|
|
||||||
@@ -171,13 +170,22 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
|
|||||||
|
|
||||||
支持更多服务商、UI 的优化改进、Bug 修复、文档完善等,欢迎大家提交 PR。
|
支持更多服务商、UI 的优化改进、Bug 修复、文档完善等,欢迎大家提交 PR。
|
||||||
|
|
||||||
## 八、加入社区
|
## 八、免责声明
|
||||||
|
|
||||||
|
本软件依据 MIT 许可证(MIT License)发布,免费提供,旨在“按现状”供用户使用。作者及贡献者不对使用本软件所产生的任何直接或间接后果承担责任,包括但不限于性能下降、数据丢失、服务中断、或任何其他类型的损害。
|
||||||
|
|
||||||
|
无任何保证:本软件不提供任何明示或暗示的保证,包括但不限于对特定用途的适用性、无侵权性、商用性及可靠性的保证。
|
||||||
|
|
||||||
|
用户责任:使用本软件即表示您理解并同意承担由此产生的一切风险及责任。
|
||||||
|
|
||||||
|
## 九、加入社区
|
||||||
|
|
||||||
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
|
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
|
||||||
- 微信群聊(超 200 人需邀请入群,可先加作者好友)
|
- 微信群聊(超 200 人需邀请入群,可先加作者好友)
|
||||||
|
|
||||||
<img src="https://i.imgur.com/8xwsLTA.png" width="400"/>
|
<img src="https://i.imgur.com/8xwsLTA.png" width="400"/>
|
||||||
|
|
||||||
## 九、Star 趋势图
|
## 十、Star 趋势图
|
||||||
|
|
||||||
[](https://starchart.cc/usual2970/certimate)
|
[](https://starchart.cc/usual2970/certimate)
|
||||||
|
|
||||||
|
|||||||
64
README_EN.md
64
README_EN.md
@@ -54,13 +54,12 @@ mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubuserconten
|
|||||||
```bash
|
```bash
|
||||||
git clone EMAIL:usual2970/certimate.git
|
git clone EMAIL:usual2970/certimate.git
|
||||||
cd certimate
|
cd certimate
|
||||||
go mod vendor
|
make local.run
|
||||||
go run main.go serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
After completing the installation steps above, you can access the Certimate management page by visiting http://127.0.0.1:8090 in your browser.
|
After completing the installation steps above, you can access the Certimate management page by visiting <http://127.0.0.1:8090> in your browser.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
username:admin@certimate.fun
|
username:admin@certimate.fun
|
||||||
@@ -71,35 +70,34 @@ password:1234567890
|
|||||||
|
|
||||||
## List of Supported Providers
|
## List of Supported Providers
|
||||||
|
|
||||||
| Provider | Registration | Deployment | Remarks |
|
| Provider | Registration | Deployment | Remarks |
|
||||||
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
|
| :-----------: | :----------: | :--------: | ----------------------------------------------------------------------------------------------------------- |
|
||||||
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN |
|
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB |
|
||||||
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN |
|
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, ECDN, CLB, TEO |
|
||||||
| Huawei Cloud | √ | √ | Supports domains registered on Huawei; supports deployment to Huawei Cloud CDN |
|
| Baidu Cloud | | √ | Supports deployment to Baidu Cloud CDN |
|
||||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
||||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
| Doge Cloud | | √ | Supports deployment to Doge Cloud CDN |
|
||||||
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||||
| Namesilo | √ | | Supports domains registered on Namesilo |
|
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||||
| PowerDNS | √ | | Supports domains managed by PowerDNS |
|
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
||||||
| HTTP request | √ | | Supports domains dns managed by HTTP Request |
|
| Namesilo | √ | | Supports domains registered on Namesilo |
|
||||||
|
| PowerDNS | √ | | Supports domains managed on PowerDNS |
|
||||||
| Local Deploy | | √ | Supports deployment to local servers |
|
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
|
||||||
| SSH | | √ | Supports deployment to SSH servers |
|
| Local Deploy | | √ | Supports deployment to local servers |
|
||||||
| Webhook | | √ | Supports callback to Webhook |
|
| SSH | | √ | Supports deployment to SSH servers |
|
||||||
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
| Webhook | | √ | Supports callback to Webhook |
|
||||||
|
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|
<div align="center">
|
||||||
|
<img src="https://i.imgur.com/SYjjbql.jpeg" title="Login page" width="95%"/>
|
||||||

|
<img src="https://i.imgur.com/WMVbBId.jpeg" title="Dashboard page" width="47%"/>
|
||||||
|
<img src="https://i.imgur.com/8wit3ZA.jpeg" title="Domains page" width="47%"/>
|
||||||

|
<img src="https://i.imgur.com/EWtOoJ0.jpeg" title="Accesses page" width="47%"/>
|
||||||
|
<img src="https://i.imgur.com/aaPtSW7.jpeg" title="History page" width="47%"/>
|
||||||

|
</div>
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
@@ -171,6 +169,14 @@ You can support the development of Certimate in the following ways:
|
|||||||
|
|
||||||
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to submit pull requests (PRs).
|
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to submit pull requests (PRs).
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This software is provided under the MIT License 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.
|
||||||
|
|
||||||
|
User Responsibility: By using this software, you agree to take full responsibility for any outcomes resulting from its use.
|
||||||
|
|
||||||
## Join the Community
|
## Join the Community
|
||||||
|
|
||||||
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
|
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
|
||||||
|
|||||||
52
go.mod
52
go.mod
@@ -5,26 +5,36 @@ go 1.22.0
|
|||||||
toolchain go1.23.2
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alibabacloud-go/alb-20200616/v2 v2.2.1
|
||||||
|
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1
|
||||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
|
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
|
||||||
|
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
|
||||||
|
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
||||||
github.com/alibabacloud-go/tea v1.2.2
|
github.com/alibabacloud-go/tea v1.2.2
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197
|
||||||
github.com/go-acme/lego/v4 v4.19.2
|
github.com/go-acme/lego/v4 v4.19.2
|
||||||
github.com/gojek/heimdall/v7 v7.0.3
|
github.com/gojek/heimdall/v7 v7.0.3
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
|
||||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||||
github.com/nikoksr/notify v1.0.0
|
github.com/nikoksr/notify v1.0.0
|
||||||
|
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.6
|
||||||
github.com/pocketbase/dbx v1.10.1
|
github.com/pocketbase/dbx v1.10.1
|
||||||
github.com/pocketbase/pocketbase v0.22.18
|
github.com/pocketbase/pocketbase v0.22.18
|
||||||
github.com/qiniu/go-sdk/v7 v7.22.0
|
github.com/qiniu/go-sdk/v7 v7.22.0
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
||||||
golang.org/x/crypto v0.27.0
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030
|
||||||
|
golang.org/x/crypto v0.28.0
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
|
k8s.io/api v0.31.1
|
||||||
k8s.io/apimachinery v0.31.1
|
k8s.io/apimachinery v0.31.1
|
||||||
k8s.io/client-go v0.31.1
|
k8s.io/client-go v0.31.1
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.5.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -32,6 +42,7 @@ require (
|
|||||||
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
||||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
||||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
@@ -57,7 +68,6 @@ require (
|
|||||||
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
k8s.io/api v0.31.1 // indirect
|
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||||
@@ -69,15 +79,15 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
|
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
|
||||||
github.com/alibabacloud-go/debug v1.0.0 // indirect
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
||||||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
|
||||||
github.com/aliyun/credentials-go v1.3.1 // indirect
|
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
|
||||||
@@ -103,10 +113,10 @@ require (
|
|||||||
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
|
github.com/domodwyer/mailyak/v3 v3.6.2
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||||
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
|
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||||
@@ -133,7 +143,7 @@ require (
|
|||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/cobra v1.8.1 // indirect
|
github.com/spf13/cobra v1.8.1 // indirect
|
||||||
@@ -148,19 +158,19 @@ require (
|
|||||||
gocloud.dev v0.37.0 // indirect
|
gocloud.dev v0.37.0 // indirect
|
||||||
golang.org/x/image v0.18.0 // indirect
|
golang.org/x/image v0.18.0 // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/oauth2 v0.23.0 // indirect
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/term v0.24.0 // indirect
|
golang.org/x/term v0.25.0 // indirect
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
golang.org/x/tools v0.25.0 // indirect
|
golang.org/x/tools v0.25.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||||
google.golang.org/api v0.197.0 // indirect
|
google.golang.org/api v0.202.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
google.golang.org/grpc v1.66.1 // indirect
|
google.golang.org/grpc v1.67.1 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
|
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
|
||||||
|
|||||||
118
go.sum
118
go.sum
@@ -1,13 +1,13 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||||
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||||
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
|
cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8=
|
||||||
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
|
cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||||
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
|
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
|
||||||
cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs=
|
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||||
cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||||
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
||||||
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
||||||
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
|
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
|
||||||
@@ -29,33 +29,60 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
|
|||||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
|
github.com/alibabacloud-go/alb-20200616/v2 v2.2.1 h1:b8ixnrkFhWrmJQd+iEE1UWPD5vdyC3d9l7G0uvkfi2s=
|
||||||
|
github.com/alibabacloud-go/alb-20200616/v2 v2.2.1/go.mod h1:cPdZwovbqpv+5nM/HnMwZpG5q0/gBuX31hu2H1VoyrM=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
|
||||||
|
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1 h1:kAxd9IkdMaIX9aoBRA34q9WXKnkKTucil/zUlG4/3vo=
|
||||||
|
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1/go.mod h1:gElMYWcjdjKgqq9/2YxE6BIUMs10ZNGM4PRiRlDXgBs=
|
||||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 h1:yTKngw4rBR3hdpoo/uCyBffYXfPfjNjlaDL8nTYUIds=
|
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 h1:yTKngw4rBR3hdpoo/uCyBffYXfPfjNjlaDL8nTYUIds=
|
||||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0/go.mod h1:HxQrwVKBx3/6bIwmdDcpqBpSQt2tpi/j4LfEhl+QFPk=
|
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0/go.mod h1:HxQrwVKBx3/6bIwmdDcpqBpSQt2tpi/j4LfEhl+QFPk=
|
||||||
|
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=
|
||||||
|
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||||
|
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||||
|
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/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.7/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9 h1:fxMCrZatZfXq5nLcgkmWBXmU3FLC1OR+m/SqVtMqflk=
|
|
||||||
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.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
|
||||||
|
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.4.2 h1:WKMtPfhEmf8jX4FvdG7MFBJeCknPQ+FEHQppDcaCoU0=
|
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2 h1:WKMtPfhEmf8jX4FvdG7MFBJeCknPQ+FEHQppDcaCoU0=
|
||||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2/go.mod h1:dGuR8qQqofJKl99rVaWvObnP3bMkru3cdOtqJJ95048=
|
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2/go.mod h1:dGuR8qQqofJKl99rVaWvObnP3bMkru3cdOtqJJ95048=
|
||||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||||
github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA=
|
|
||||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||||
|
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko=
|
||||||
|
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3/go.mod h1:4a/RcBYeAhYowHzX+LMgnouz7NradnSKPKl14KS3B1U=
|
||||||
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
|
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
|
||||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
|
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
|
||||||
|
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9 h1:nrf9gQth7fONUj7V8i78Yb98eb9NdKl0VdeSjmeYugI=
|
||||||
|
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9/go.mod h1:PEMEsQoxhkMvykMFP5ZXg6SWI9vmAiZ6lK3Pu4mTKB0=
|
||||||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
github.com/alibabacloud-go/tea v1.1.12/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
github.com/alibabacloud-go/tea v1.1.12/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
|
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.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
|
||||||
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
|
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
|
||||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||||
@@ -70,6 +97,7 @@ github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQ
|
|||||||
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
|
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
|
||||||
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||||
@@ -82,8 +110,10 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.63.15/go.mod h1:SOSDHfe1kX91v3W5QiBsWS
|
|||||||
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 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/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.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||||
github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
|
|
||||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
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 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
|
||||||
|
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
@@ -129,6 +159,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
||||||
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
||||||
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197 h1:TQqa4J+FTagrywhaTQ707ffE1eG3ix1s06eSZ/K+Wk0=
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
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/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||||
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
||||||
@@ -174,8 +206,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
||||||
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
|
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.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||||
@@ -368,6 +400,8 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
|||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||||
|
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ=
|
||||||
|
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -426,14 +460,20 @@ github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQ
|
|||||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017 h1:OymmfmyFkvHirY3WHsoRT3cdTEsqygLbMn8jM41erK4=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017 h1:OymmfmyFkvHirY3WHsoRT3cdTEsqygLbMn8jM41erK4=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017/go.mod h1:gnLxGXlLmF+jDqWR1/RVoF/UUwxQxomQhkc0oN7KeuI=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017/go.mod h1:gnLxGXlLmF+jDqWR1/RVoF/UUwxQxomQhkc0oN7KeuI=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031 h1:/eVMCl+jadCex6HxNN6/hFbC0iWl+e8s4PSIcI8aqS4=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031/go.mod h1:8Km0fRIaDS7PssuyxDFvRRFBUFmECqG+ICpViCs/Vak=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017 h1:SXrldOXwgomYuATVAuz5ofpTjB+99qVELgdy5R5kMgI=
|
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031 h1:3ouglYKE5cwhx2vwICGeW7pAlwyCLnpQd7O0l3hCSTg=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 h1:QwE0dRkAAbdf+eACnkNULgDn9ZKUJpPWRyXdqJolP5E=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 h1:QwE0dRkAAbdf+eACnkNULgDn9ZKUJpPWRyXdqJolP5E=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002/go.mod h1:WdC0FYbqYhJwQ3kbqri6hVP5HAEp+rzX9FToItTAzUg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002/go.mod h1:WdC0FYbqYhJwQ3kbqri6hVP5HAEp+rzX9FToItTAzUg=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992 h1:A6O89OlCJQUpNxGqC/E5By04UNKBryIt5olQIGOx8mg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992 h1:A6O89OlCJQUpNxGqC/E5By04UNKBryIt5olQIGOx8mg=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992/go.mod h1:BcvC7ZPdSlhRggVq4J1ToJlgv8bmODIAuSo0naFZOLo=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992/go.mod h1:BcvC7ZPdSlhRggVq4J1ToJlgv8bmODIAuSo0naFZOLo=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030 h1:tlHbfQlAfL12J/5XF4indKl0cAA3vEn6TDiGZVsr050=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030/go.mod h1:8dW6JByZKNDAPnjlXxBk9yDc+QGbldpa0tBRfi1kG+U=
|
||||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
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 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
@@ -487,15 +527,18 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45
|
|||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
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-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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
@@ -537,8 +580,9 @@ golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
|||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
@@ -576,9 +620,10 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
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.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -588,9 +633,10 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
|||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
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.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -604,11 +650,11 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
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.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/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.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -636,28 +682,28 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ
|
|||||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||||
google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ=
|
google.golang.org/api v0.202.0 h1:y1iuVHMqokQbimW79ZqPZWo4CiyFu6HcCYHwSNyzlfo=
|
||||||
google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw=
|
google.golang.org/api v0.202.0/go.mod h1:3Jjeq7M/SFblTNCp7ES2xhq+WvGL0KeXI0joHQBfwTQ=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
|
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE=
|
||||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
|
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||||
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -667,8 +713,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -738,3 +784,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s
|
|||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
"github.com/usual2970/certimate/internal/repository"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
@@ -39,16 +41,19 @@ const defaultSSLProvider = "letsencrypt"
|
|||||||
const (
|
const (
|
||||||
sslProviderLetsencrypt = "letsencrypt"
|
sslProviderLetsencrypt = "letsencrypt"
|
||||||
sslProviderZeroSSL = "zerossl"
|
sslProviderZeroSSL = "zerossl"
|
||||||
|
sslProviderGts = "gts"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
|
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
|
||||||
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
|
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
gtsUrl = "https://dv.acme-v02.api.pki.goog/directory"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sslProviderUrls = map[string]string{
|
var sslProviderUrls = map[string]string{
|
||||||
sslProviderLetsencrypt: letsencryptUrl,
|
sslProviderLetsencrypt: letsencryptUrl,
|
||||||
sslProviderZeroSSL: zerosslUrl,
|
sslProviderZeroSSL: zerosslUrl,
|
||||||
|
sslProviderGts: gtsUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultEmail = "536464346@qq.com"
|
const defaultEmail = "536464346@qq.com"
|
||||||
@@ -75,9 +80,37 @@ type ApplyOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ApplyUser struct {
|
type ApplyUser struct {
|
||||||
|
Ca string
|
||||||
Email string
|
Email string
|
||||||
Registration *registration.Resource
|
Registration *registration.Resource
|
||||||
key crypto.PrivateKey
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApplyUser(ca, email string) (*ApplyUser, error) {
|
||||||
|
repo := getAcmeAccountRepository()
|
||||||
|
rs := &ApplyUser{
|
||||||
|
Ca: ca,
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
resp, err := repo.GetByCAAndEmail(ca, email)
|
||||||
|
if err != nil {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyStr, err := x509.ConvertECPrivateKeyToPEM(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rs.key = keyStr
|
||||||
|
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.Registration = resp.Resource
|
||||||
|
rs.key = resp.Key
|
||||||
|
|
||||||
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ApplyUser) GetEmail() string {
|
func (u *ApplyUser) GetEmail() string {
|
||||||
@@ -89,6 +122,15 @@ func (u ApplyUser) GetRegistration() *registration.Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ApplyUser) GetPrivateKey() crypto.PrivateKey {
|
func (u *ApplyUser) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
rs, _ := x509.ParseECPrivateKeyFromPEM(u.key)
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ApplyUser) hasRegistration() bool {
|
||||||
|
return u.Registration != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ApplyUser) getPrivateKeyString() string {
|
||||||
return u.key
|
return u.key
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +199,13 @@ type SSLProviderConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SSLProviderConfigContent struct {
|
type SSLProviderConfigContent struct {
|
||||||
Zerossl struct {
|
Zerossl SSLProviderEab `json:"zerossl"`
|
||||||
EabHmacKey string `json:"eabHmacKey"`
|
Gts SSLProviderEab `json:"gts"`
|
||||||
EabKid string `json:"eabKid"`
|
}
|
||||||
}
|
|
||||||
|
type SSLProviderEab struct {
|
||||||
|
EabHmacKey string `json:"eabHmacKey"`
|
||||||
|
EabKid string `json:"eabKid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, error) {
|
func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, error) {
|
||||||
@@ -176,21 +221,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some unified lego environment variables are configured here.
|
// Some unified lego environment variables are configured here.
|
||||||
// link: https://github.com/go-acme/lego/issues/1867
|
// link: https://github.com/go-acme/lego/issues/1867
|
||||||
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
|
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
|
||||||
|
|
||||||
myUser := ApplyUser{
|
myUser, err := newApplyUser(sslProvider.Provider, option.Email)
|
||||||
Email: option.Email,
|
if err != nil {
|
||||||
key: privateKey,
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := lego.NewConfig(&myUser)
|
config := lego.NewConfig(myUser)
|
||||||
|
|
||||||
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
|
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
|
||||||
config.CADirURL = sslProviderUrls[sslProvider.Provider]
|
config.CADirURL = sslProviderUrls[sslProvider.Provider]
|
||||||
@@ -211,11 +251,13 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
|||||||
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
|
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
|
||||||
|
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
reg, err := getReg(client, sslProvider)
|
if !myUser.hasRegistration() {
|
||||||
if err != nil {
|
reg, err := getReg(client, sslProvider, myUser)
|
||||||
return nil, fmt.Errorf("failed to register: %w", err)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to register: %w", err)
|
||||||
|
}
|
||||||
|
myUser.Registration = reg
|
||||||
}
|
}
|
||||||
myUser.Registration = reg
|
|
||||||
|
|
||||||
domains := strings.Split(option.Domain, ";")
|
domains := strings.Split(option.Domain, ";")
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
@@ -237,7 +279,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.Resource, error) {
|
type AcmeAccountRepository interface {
|
||||||
|
GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error)
|
||||||
|
Save(ca, email, key string, resource *registration.Resource) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAcmeAccountRepository() AcmeAccountRepository {
|
||||||
|
return repository.NewAcmeAccountRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReg(client *lego.Client, sslProvider *SSLProviderConfig, user *ApplyUser) (*registration.Resource, error) {
|
||||||
var reg *registration.Resource
|
var reg *registration.Resource
|
||||||
var err error
|
var err error
|
||||||
switch sslProvider.Provider {
|
switch sslProvider.Provider {
|
||||||
@@ -247,6 +298,12 @@ func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.
|
|||||||
Kid: sslProvider.Config.Zerossl.EabKid,
|
Kid: sslProvider.Config.Zerossl.EabKid,
|
||||||
HmacEncoded: sslProvider.Config.Zerossl.EabHmacKey,
|
HmacEncoded: sslProvider.Config.Zerossl.EabHmacKey,
|
||||||
})
|
})
|
||||||
|
case sslProviderGts:
|
||||||
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
Kid: sslProvider.Config.Gts.EabKid,
|
||||||
|
HmacEncoded: sslProvider.Config.Gts.EabHmacKey,
|
||||||
|
})
|
||||||
|
|
||||||
case sslProviderLetsencrypt:
|
case sslProviderLetsencrypt:
|
||||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
@@ -259,6 +316,18 @@ func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repo := getAcmeAccountRepository()
|
||||||
|
|
||||||
|
resp, err := repo.GetByCAAndEmail(sslProvider.Provider, user.GetEmail())
|
||||||
|
if err == nil {
|
||||||
|
user.key = resp.Key
|
||||||
|
return resp.Resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.Save(sslProvider.Provider, user.GetEmail(), user.getPrivateKeyString(), reg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save registration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return reg, nil
|
return reg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ func (t *huaweicloud) Apply() (*Certificate, error) {
|
|||||||
access := &domain.HuaweiCloudAccess{}
|
access := &domain.HuaweiCloudAccess{}
|
||||||
json.Unmarshal([]byte(t.option.Access), access)
|
json.Unmarshal([]byte(t.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
region := access.Region
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("HUAWEICLOUD_REGION", region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
||||||
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
||||||
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
||||||
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
||||||
|
|||||||
281
internal/deployer/aliyun_alb.go
Normal file
281
internal/deployer/aliyun_alb.go
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunALBDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunAlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&AliyunALBDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
||||||
|
if aliCasRegion != "" {
|
||||||
|
// 阿里云 CAS 服务接入点是独立于 ALB 服务的
|
||||||
|
// 国内版接入点:华东一杭州
|
||||||
|
// 国际版接入点:亚太东南一新加坡
|
||||||
|
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
||||||
|
aliCasRegion = "ap-southeast-1"
|
||||||
|
} else {
|
||||||
|
aliCasRegion = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: aliCasRegion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunALBDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) Deploy(ctx context.Context) error {
|
||||||
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
|
case "loadbalancer":
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // ALB 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "cn-hangzhou-finance":
|
||||||
|
endpoint = "alb.cn-hangzhou.aliyuncs.com"
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
|
client, err := aliyunAlb.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
if aliLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliListenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute
|
||||||
|
getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
}
|
||||||
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp))
|
||||||
|
|
||||||
|
// 查询 HTTPS 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("HTTPS"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", aliListenerIds))
|
||||||
|
|
||||||
|
// 查询 QUIC 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners
|
||||||
|
listListenersPage = 1
|
||||||
|
listListenersToken = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("QUIC"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", aliListenerIds))
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, aliListenerId := range aliListenerIds {
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
aliListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
if aliListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
||||||
|
// 查询监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute
|
||||||
|
getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(aliListenerId),
|
||||||
|
}
|
||||||
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ALB 监听配置", getListenerAttributeResp))
|
||||||
|
|
||||||
|
// 修改监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute
|
||||||
|
updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(aliListenerId),
|
||||||
|
Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{
|
||||||
|
CertificateId: tea.String(aliCertId),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新 ALB 监听配置", updateListenerAttributeResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,39 +4,41 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
cdn20180510 "github.com/alibabacloud-go/cdn-20180510/v5/client"
|
aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunCDNDeployer struct {
|
type AliyunCDNDeployer struct {
|
||||||
client *cdn20180510.Client
|
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunCdn.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunCDNDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
|
func NewAliyunCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
d := &AliyunCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
client, err := (&AliyunCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AliyunCDNDeployer{
|
return &AliyunCDNDeployer{
|
||||||
client: client,
|
option: option,
|
||||||
option: option,
|
infos: make([]string, 0),
|
||||||
infos: make([]string, 0),
|
sdkClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,41 +46,43 @@ func (d *AliyunCDNDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetInfo() []string {
|
func (d *AliyunCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
// 设置 CDN 域名域名证书
|
||||||
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
|
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
|
||||||
DomainName: tea.String(getDeployString(d.option.DeployConfig, "domain")),
|
setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{
|
||||||
CertName: tea.String(certName),
|
DomainName: tea.String(d.option.DeployConfig.GetConfigAsString("domain")),
|
||||||
|
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
CertType: tea.String("upload"),
|
CertType: tea.String("upload"),
|
||||||
SSLProtocol: tea.String("on"),
|
SSLProtocol: tea.String("on"),
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
SSLPub: tea.String(d.option.Certificate.Certificate),
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
||||||
CertRegion: tea.String("cn-hangzhou"),
|
|
||||||
}
|
}
|
||||||
|
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
|
||||||
runtime := &util.RuntimeOptions{}
|
|
||||||
|
|
||||||
resp, err := d.client.SetCdnDomainSSLCertificateWithOptions(setCdnDomainSSLCertificateRequest, runtime)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("cdn设置证书", resp))
|
d.infos = append(d.infos, toStr("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) createClient(accessKeyId, accessKeySecret string) (_result *cdn20180510.Client, _err error) {
|
func (d *AliyunCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) {
|
||||||
config := &openapi.Config{
|
aConfig := &aliyunOpen.Config{
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("cdn.aliyuncs.com"),
|
||||||
}
|
}
|
||||||
config.Endpoint = tea.String("cdn.aliyuncs.com")
|
|
||||||
_result = &cdn20180510.Client{}
|
client, err := aliyunCdn.NewClient(aConfig)
|
||||||
_result, _err = cdn20180510.NewClient(config)
|
if err != nil {
|
||||||
return _result, _err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|||||||
285
internal/deployer/aliyun_clb.go
Normal file
285
internal/deployer/aliyun_clb.go
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunCLBDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunSlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunCLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&AliyunCLBDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderAliyunSlb.New(&uploaderAliyunSlb.AliyunSLBUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunCLBDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) Deploy(ctx context.Context) error {
|
||||||
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
|
case "loadbalancer":
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "cn-hangzhou":
|
||||||
|
case "cn-hangzhou-finance":
|
||||||
|
case "cn-shanghai-finance-1":
|
||||||
|
case "cn-shenzhen-finance-1":
|
||||||
|
endpoint = "slb.aliyuncs.com"
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
|
client, err := aliyunSlb.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
aliListenerPorts := make([]int32, 0)
|
||||||
|
if aliLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute
|
||||||
|
describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{
|
||||||
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
}
|
||||||
|
describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp))
|
||||||
|
|
||||||
|
// 查询 HTTPS 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{
|
||||||
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerId: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("https"),
|
||||||
|
}
|
||||||
|
describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeLoadBalancerListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range describeLoadBalancerListenersResp.Body.Listeners {
|
||||||
|
aliListenerPorts = append(aliListenerPorts, *listener.ListenerPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeLoadBalancerListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = describeLoadBalancerListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", aliListenerPorts))
|
||||||
|
|
||||||
|
// 上传证书到 SLB
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, aliListenerPort := range aliListenerPorts {
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
if aliLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliListenerPort := d.option.DeployConfig.GetConfigAsInt32("listenerPort")
|
||||||
|
if aliListenerPort == 0 {
|
||||||
|
return errors.New("`listenerPort` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SLB
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLoadbalancerId string, aliListenerPort int32, aliCertId string) error {
|
||||||
|
// 查询监听配置
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute
|
||||||
|
describeLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.DescribeLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
|
}
|
||||||
|
describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp))
|
||||||
|
|
||||||
|
// 查询扩展域名
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions
|
||||||
|
describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{
|
||||||
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
|
}
|
||||||
|
describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 CLB 扩展域名", describeDomainExtensionsResp))
|
||||||
|
|
||||||
|
// 遍历修改扩展域名
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute
|
||||||
|
//
|
||||||
|
// 这里仅修改跟被替换证书一致的扩展域名
|
||||||
|
if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil {
|
||||||
|
for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension {
|
||||||
|
if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
||||||
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
|
DomainExtensionId: tea.String(*domainExtension.DomainExtensionId),
|
||||||
|
ServerCertificateId: tea.String(aliCertId),
|
||||||
|
}
|
||||||
|
_, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改监听配置
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute
|
||||||
|
//
|
||||||
|
// 注意修改监听配置要放在修改扩展域名之后
|
||||||
|
setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
|
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(aliListenerPort),
|
||||||
|
ServerCertificateId: tea.String(aliCertId),
|
||||||
|
}
|
||||||
|
setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
95
internal/deployer/aliyun_dcdn.go
Normal file
95
internal/deployer/aliyun_dcdn.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunDcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunDCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunDcdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunDCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&AliyunDCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunDCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 支持泛解析域名,在 Aliyun DCDN 中泛解析域名表示为 .example.com
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domain = strings.TrimPrefix(domain, "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate
|
||||||
|
setDcdnDomainSSLCertificateReq := &aliyunDcdn.SetDcdnDomainSSLCertificateRequest{
|
||||||
|
DomainName: tea.String(domain),
|
||||||
|
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
|
CertType: tea.String("upload"),
|
||||||
|
SSLProtocol: tea.String("on"),
|
||||||
|
SSLPub: tea.String(d.option.Certificate.Certificate),
|
||||||
|
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
||||||
|
}
|
||||||
|
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) {
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("dcdn.aliyuncs.com"),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunDcdn.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: Bin
|
|
||||||
* @Date: 2024-09-17
|
|
||||||
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
|
|
||||||
*/
|
|
||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
|
||||||
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
|
||||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
||||||
"github.com/alibabacloud-go/tea/tea"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunESADeployer struct {
|
|
||||||
client *dcdn20180115.Client
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunESADeployer(option *DeployerOption) (*AliyunESADeployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
|
||||||
|
|
||||||
d := &AliyunESADeployer{
|
|
||||||
option: option,
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunESADeployer{
|
|
||||||
client: client,
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetInfo() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
|
|
||||||
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
|
||||||
|
|
||||||
// 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domain = strings.TrimPrefix(domain, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
|
||||||
DomainName: tea.String(domain),
|
|
||||||
CertName: tea.String(certName),
|
|
||||||
CertType: tea.String("upload"),
|
|
||||||
SSLProtocol: tea.String("on"),
|
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
|
||||||
CertRegion: tea.String("cn-hangzhou"),
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime := &util.RuntimeOptions{}
|
|
||||||
|
|
||||||
resp, err := d.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("dcdn设置证书", resp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunESADeployer) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
|
|
||||||
config := &openapi.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
config.Endpoint = tea.String("dcdn.aliyuncs.com")
|
|
||||||
_result = &dcdn20180115.Client{}
|
|
||||||
_result, _err = dcdn20180115.NewClient(config)
|
|
||||||
return _result, _err
|
|
||||||
}
|
|
||||||
245
internal/deployer/aliyun_nlb.go
Normal file
245
internal/deployer/aliyun_nlb.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunNlb "github.com/alibabacloud-go/nlb-20220430/v2/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunNLBDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *aliyunNlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&AliyunNLBDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
||||||
|
if aliCasRegion != "" {
|
||||||
|
// 阿里云 CAS 服务接入点是独立于 NLB 服务的
|
||||||
|
// 国内版接入点:华东一杭州
|
||||||
|
// 国际版接入点:亚太东南一新加坡
|
||||||
|
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
||||||
|
aliCasRegion = "ap-southeast-1"
|
||||||
|
} else {
|
||||||
|
aliCasRegion = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: aliCasRegion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunNLBDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) Deploy(ctx context.Context) error {
|
||||||
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
|
case "loadbalancer":
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
|
client, err := aliyunNlb.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
if aliLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliListenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute
|
||||||
|
getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(aliLoadbalancerId),
|
||||||
|
}
|
||||||
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp))
|
||||||
|
|
||||||
|
// 查询 TCPSSL 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunNlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("TCPSSL"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", aliListenerIds))
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, aliListenerId := range aliListenerIds {
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
aliListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
if aliListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error {
|
||||||
|
// 查询监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute
|
||||||
|
getListenerAttributeReq := &aliyunNlb.GetListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(aliListenerId),
|
||||||
|
}
|
||||||
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 NLB 监听配置", getListenerAttributeResp))
|
||||||
|
|
||||||
|
// 修改监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute
|
||||||
|
updateListenerAttributeReq := &aliyunNlb.UpdateListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(aliListenerId),
|
||||||
|
CertificateIds: []*string{tea.String(aliCertId)},
|
||||||
|
}
|
||||||
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,48 +3,62 @@ package deployer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliyunOSSDeployer struct {
|
type AliyunOSSDeployer struct {
|
||||||
client *oss.Client
|
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *oss.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
d := &AliyunOSSDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
|
client, err := (&AliyunOSSDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.AccessKeySecret,
|
||||||
|
option.DeployConfig.GetConfigAsString("endpoint"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
}
|
}
|
||||||
d.client = client
|
|
||||||
|
|
||||||
return d, nil
|
return &AliyunOSSDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetID() string {
|
func (d *AliyunOSSDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetInfo() []string {
|
func (d *AliyunOSSDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
||||||
err := d.client.PutBucketCnameWithCertificate(getDeployString(d.option.DeployConfig, "bucket"), oss.PutBucketCname{
|
aliBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
||||||
Cname: getDeployString(d.option.DeployConfig, "domain"),
|
if aliBucket == "" {
|
||||||
|
return errors.New("`bucket` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为存储空间绑定自定义域名
|
||||||
|
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
|
||||||
|
err := d.sdkClient.PutBucketCnameWithCertificate(aliBucket, oss.PutBucketCname{
|
||||||
|
Cname: d.option.DeployConfig.GetConfigAsString("domain"),
|
||||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||||
Certificate: d.option.Certificate.Certificate,
|
Certificate: d.option.Certificate.Certificate,
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
PrivateKey: d.option.Certificate.PrivateKey,
|
||||||
@@ -52,19 +66,21 @@ func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("deploy aliyun oss error: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
|
func (d *AliyunOSSDeployer) createSdkClient(accessKeyId, accessKeySecret, endpoint string) (*oss.Client, error) {
|
||||||
client, err := oss.New(
|
if endpoint == "" {
|
||||||
getDeployString(d.option.DeployConfig, "endpoint"),
|
endpoint = "oss.aliyuncs.com"
|
||||||
accessKeyId,
|
|
||||||
accessKeySecret,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create aliyun client error: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|||||||
80
internal/deployer/baiducloud_cdn.go
Normal file
80
internal/deployer/baiducloud_cdn.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
bceCdn "github.com/baidubce/bce-sdk-go/services/cdn"
|
||||||
|
bceCdnApi "github.com/baidubce/bce-sdk-go/services/cdn/api"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaiduCloudCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *bceCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaiduCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.BaiduCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&BaiduCloudCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.SecretAccessKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BaiduCloudCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduCloudCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduCloudCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 修改域名证书
|
||||||
|
// REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8
|
||||||
|
putCertResp, err := d.sdkClient.PutCert(
|
||||||
|
d.option.DeployConfig.GetConfigAsString("domain"),
|
||||||
|
&bceCdnApi.UserCertificate{
|
||||||
|
CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
||||||
|
ServerData: d.option.Certificate.Certificate,
|
||||||
|
PrivateData: d.option.Certificate.PrivateKey,
|
||||||
|
},
|
||||||
|
"ON",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改域名证书", putCertResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey string) (*bceCdn.Client, error) {
|
||||||
|
client, err := bceCdn.NewClient(accessKeyId, secretAccessKey, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -1,26 +1,41 @@
|
|||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/applicant"
|
"github.com/usual2970/certimate/internal/applicant"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
targetAliyunOSS = "aliyun-oss"
|
targetAliyunOSS = "aliyun-oss"
|
||||||
targetAliyunCDN = "aliyun-cdn"
|
targetAliyunCDN = "aliyun-cdn"
|
||||||
targetAliyunESA = "aliyun-dcdn"
|
targetAliyunDCDN = "aliyun-dcdn"
|
||||||
|
targetAliyunCLB = "aliyun-clb"
|
||||||
|
targetAliyunALB = "aliyun-alb"
|
||||||
|
targetAliyunNLB = "aliyun-nlb"
|
||||||
targetTencentCDN = "tencent-cdn"
|
targetTencentCDN = "tencent-cdn"
|
||||||
|
targetTencentECDN = "tencent-ecdn"
|
||||||
|
targetTencentCLB = "tencent-clb"
|
||||||
|
targetTencentCOS = "tencent-cos"
|
||||||
|
targetTencentTEO = "tencent-teo"
|
||||||
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
||||||
|
targetHuaweiCloudELB = "huaweicloud-elb"
|
||||||
|
targetBaiduCloudCDN = "baiducloud-cdn"
|
||||||
targetQiniuCdn = "qiniu-cdn"
|
targetQiniuCdn = "qiniu-cdn"
|
||||||
|
targetDogeCloudCdn = "dogecloud-cdn"
|
||||||
targetLocal = "local"
|
targetLocal = "local"
|
||||||
targetSSH = "ssh"
|
targetSSH = "ssh"
|
||||||
targetWebhook = "webhook"
|
targetWebhook = "webhook"
|
||||||
@@ -30,7 +45,6 @@ const (
|
|||||||
type DeployerOption struct {
|
type DeployerOption struct {
|
||||||
DomainId string `json:"domainId"`
|
DomainId string `json:"domainId"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Product string `json:"product"`
|
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
AccessRecord *models.Record `json:"-"`
|
AccessRecord *models.Record `json:"-"`
|
||||||
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
||||||
@@ -40,7 +54,7 @@ type DeployerOption struct {
|
|||||||
|
|
||||||
type Deployer interface {
|
type Deployer interface {
|
||||||
Deploy(ctx context.Context) error
|
Deploy(ctx context.Context) error
|
||||||
GetInfo() []string
|
GetInfos() []string
|
||||||
GetID() string
|
GetID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +96,6 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
option := &DeployerOption{
|
option := &DeployerOption{
|
||||||
DomainId: record.Id,
|
DomainId: record.Id,
|
||||||
Domain: record.GetString("domain"),
|
Domain: record.GetString("domain"),
|
||||||
Product: getProduct(deployConfig.Type),
|
|
||||||
Access: access.GetString("config"),
|
Access: access.GetString("config"),
|
||||||
AccessRecord: access,
|
AccessRecord: access,
|
||||||
DeployConfig: deployConfig,
|
DeployConfig: deployConfig,
|
||||||
@@ -101,14 +114,34 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
return NewAliyunOSSDeployer(option)
|
return NewAliyunOSSDeployer(option)
|
||||||
case targetAliyunCDN:
|
case targetAliyunCDN:
|
||||||
return NewAliyunCDNDeployer(option)
|
return NewAliyunCDNDeployer(option)
|
||||||
case targetAliyunESA:
|
case targetAliyunDCDN:
|
||||||
return NewAliyunESADeployer(option)
|
return NewAliyunDCDNDeployer(option)
|
||||||
|
case targetAliyunCLB:
|
||||||
|
return NewAliyunCLBDeployer(option)
|
||||||
|
case targetAliyunALB:
|
||||||
|
return NewAliyunALBDeployer(option)
|
||||||
|
case targetAliyunNLB:
|
||||||
|
return NewAliyunNLBDeployer(option)
|
||||||
case targetTencentCDN:
|
case targetTencentCDN:
|
||||||
return NewTencentCDNDeployer(option)
|
return NewTencentCDNDeployer(option)
|
||||||
|
case targetTencentECDN:
|
||||||
|
return NewTencentECDNDeployer(option)
|
||||||
|
case targetTencentCLB:
|
||||||
|
return NewTencentCLBDeployer(option)
|
||||||
|
case targetTencentCOS:
|
||||||
|
return NewTencentCOSDeployer(option)
|
||||||
|
case targetTencentTEO:
|
||||||
|
return NewTencentTEODeployer(option)
|
||||||
case targetHuaweiCloudCDN:
|
case targetHuaweiCloudCDN:
|
||||||
return NewHuaweiCloudCDNDeployer(option)
|
return NewHuaweiCloudCDNDeployer(option)
|
||||||
|
case targetHuaweiCloudELB:
|
||||||
|
return NewHuaweiCloudELBDeployer(option)
|
||||||
|
case targetBaiduCloudCDN:
|
||||||
|
return NewBaiduCloudCDNDeployer(option)
|
||||||
case targetQiniuCdn:
|
case targetQiniuCdn:
|
||||||
return NewQiniuCDNDeployer(option)
|
return NewQiniuCDNDeployer(option)
|
||||||
|
case targetDogeCloudCdn:
|
||||||
|
return NewDogeCloudCDNDeployer(option)
|
||||||
case targetLocal:
|
case targetLocal:
|
||||||
return NewLocalDeployer(option)
|
return NewLocalDeployer(option)
|
||||||
case targetSSH:
|
case targetSSH:
|
||||||
@@ -118,15 +151,7 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
case targetK8sSecret:
|
case targetK8sSecret:
|
||||||
return NewK8sSecretDeployer(option)
|
return NewK8sSecretDeployer(option)
|
||||||
}
|
}
|
||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("unsupported deploy target")
|
||||||
}
|
|
||||||
|
|
||||||
func getProduct(t string) string {
|
|
||||||
rs := strings.Split(t, "-")
|
|
||||||
if len(rs) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return rs[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toStr(tag string, data any) string {
|
func toStr(tag string, data any) string {
|
||||||
@@ -137,37 +162,56 @@ func toStr(tag string, data any) string {
|
|||||||
return tag + ":" + string(byts)
|
return tag + ":" + string(byts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployString(conf domain.DeployConfig, key string) string {
|
func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) {
|
||||||
if _, ok := conf.Config[key]; !ok {
|
cert, err := x509.ParseCertificateFromPEM(certificate)
|
||||||
return ""
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
val, ok := conf.Config[key].(string)
|
privkey, err := x509.ParsePKCS1PrivateKeyFromPEM(privateKey)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return ""
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return val
|
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfxData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployVariables(conf domain.DeployConfig) map[string]string {
|
func convertPEMToJKS(certificate string, privateKey string, alias string, keypass string, storepass string) ([]byte, error) {
|
||||||
rs := make(map[string]string)
|
certBlock, _ := pem.Decode([]byte(certificate))
|
||||||
data, ok := conf.Config["variables"]
|
if certBlock == nil {
|
||||||
if !ok {
|
return nil, errors.New("failed to decode certificate PEM")
|
||||||
return rs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bts, _ := json.Marshal(data)
|
privkeyBlock, _ := pem.Decode([]byte(privateKey))
|
||||||
|
if privkeyBlock == nil {
|
||||||
kvData := make([]domain.KV, 0)
|
return nil, errors.New("failed to decode private key PEM")
|
||||||
|
|
||||||
if err := json.Unmarshal(bts, &kvData); err != nil {
|
|
||||||
return rs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, kv := range kvData {
|
ks := keystore.New()
|
||||||
rs[kv.Key] = kv.Value
|
entry := keystore.PrivateKeyEntry{
|
||||||
|
CreationTime: time.Now(),
|
||||||
|
PrivateKey: privkeyBlock.Bytes,
|
||||||
|
CertificateChain: []keystore.Certificate{
|
||||||
|
{
|
||||||
|
Type: "X509",
|
||||||
|
Content: certBlock.Bytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return rs
|
if err := ks.SetPrivateKeyEntry(alias, entry, []byte(keypass)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := ks.Store(&buf, []byte(storepass)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
88
internal/deployer/dogecloud_cdn.go
Normal file
88
internal/deployer/dogecloud_cdn.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
|
||||||
|
doge "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DogeCloudCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *doge.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDogeCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.DogeCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&DogeCloudCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKey,
|
||||||
|
access.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderDoge.New(&uploaderDoge.DogeCloudUploaderConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DogeCloudCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DogeCloudCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DogeCloudCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 上传证书到 CDN
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 绑定证书
|
||||||
|
// REF: https://docs.dogecloud.com/cdn/api-cert-bind
|
||||||
|
bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
|
||||||
|
bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.option.DeployConfig.GetConfigAsString("domain"))
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已绑定证书", bindCdnCertResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DogeCloudCDNDeployer) createSdkClient(accessKey, secretKey string) (*doge.Client, error) {
|
||||||
|
client := doge.NewClient(accessKey, secretKey)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -6,23 +6,55 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
cdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
cdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
cdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderHcScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
hcCdnEx "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HuaweiCloudCDNDeployer struct {
|
type HuaweiCloudCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *hcCdnEx.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.HuaweiCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.SecretAccessKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderHcScm.New(&uploaderHcScm.HuaweiCloudSCMUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
|
Region: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &HuaweiCloudCDNDeployer{
|
return &HuaweiCloudCDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,50 +62,49 @@ func (d *HuaweiCloudCDNDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
|
func (d *HuaweiCloudCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.HuaweiCloudAccess{}
|
// 上传证书到 SCM
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := d.createClient(access)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("HuaweiCloudCdnClient 创建成功", nil))
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
// 查询加速域名配置
|
// 查询加速域名配置
|
||||||
showDomainFullConfigReq := &cdnModel.ShowDomainFullConfigRequest{
|
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
||||||
DomainName: getDeployString(d.option.DeployConfig, "domain"),
|
showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
|
||||||
|
DomainName: d.option.DeployConfig.GetConfigAsString("domain"),
|
||||||
}
|
}
|
||||||
showDomainFullConfigResp, err := client.ShowDomainFullConfig(showDomainFullConfigReq)
|
showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
||||||
|
|
||||||
// 更新加速域名配置
|
// 更新加速域名配置
|
||||||
certName := fmt.Sprintf("%s-%s", d.option.DomainId, rand.RandStr(12))
|
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
||||||
updateDomainMultiCertificatesReq := &cdnModel.UpdateDomainMultiCertificatesRequest{
|
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
|
||||||
Body: &cdnModel.UpdateDomainMultiCertificatesRequestBody{
|
updateDomainMultiCertificatesReqBodyContent := &hcCdnEx.UpdateDomainMultiCertificatesExRequestBodyContent{}
|
||||||
Https: mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, &cdnModel.UpdateDomainMultiCertificatesRequestBodyContent{
|
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
DomainName: getDeployString(d.option.DeployConfig, "domain"),
|
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
|
||||||
HttpsSwitch: 1,
|
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
|
||||||
CertName: &certName,
|
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(upres.CertId)
|
||||||
Certificate: &d.option.Certificate.Certificate,
|
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(upres.CertName)
|
||||||
PrivateKey: &d.option.Certificate.PrivateKey,
|
updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs)
|
||||||
}),
|
updateDomainMultiCertificatesReq := &hcCdnEx.UpdateDomainMultiCertificatesExRequest{
|
||||||
|
Body: &hcCdnEx.UpdateDomainMultiCertificatesExRequestBody{
|
||||||
|
Https: updateDomainMultiCertificatesReqBodyContent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
updateDomainMultiCertificatesResp, err := client.UpdateDomainMultiCertificates(updateDomainMultiCertificatesReq)
|
updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
||||||
@@ -81,70 +112,32 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) createClient(access *domain.HuaweiCloudAccess) (*cdn.CdnClient, error) {
|
func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdnEx.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-1" // CDN 服务默认区域:华北一北京
|
||||||
|
}
|
||||||
|
|
||||||
auth, err := global.NewCredentialsBuilder().
|
auth, err := global.NewCredentialsBuilder().
|
||||||
WithAk(access.AccessKeyId).
|
WithAk(accessKeyId).
|
||||||
WithSk(access.SecretAccessKey).
|
WithSk(secretAccessKey).
|
||||||
SafeBuild()
|
SafeBuild()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
region, err := cdnRegion.SafeValueOf(access.Region)
|
hcRegion, err := hcCdnRegion.SafeValueOf(region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hcClient, err := cdn.CdnClientBuilder().
|
hcClient, err := hcCdn.CdnClientBuilder().
|
||||||
WithRegion(region).
|
WithRegion(hcRegion).
|
||||||
WithCredential(auth).
|
WithCredential(auth).
|
||||||
SafeBuild()
|
SafeBuild()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := cdn.NewCdnClient(hcClient)
|
client := hcCdnEx.NewClient(hcClient)
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *cdnModel.UpdateDomainMultiCertificatesRequestBodyContent) *cdnModel.UpdateDomainMultiCertificatesRequestBodyContent {
|
|
||||||
if src == nil {
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去
|
|
||||||
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化
|
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
|
||||||
|
|
||||||
if *src.OriginProtocol == "follow" {
|
|
||||||
accessOriginWay := int32(1)
|
|
||||||
dest.AccessOriginWay = &accessOriginWay
|
|
||||||
} else if *src.OriginProtocol == "http" {
|
|
||||||
accessOriginWay := int32(2)
|
|
||||||
dest.AccessOriginWay = &accessOriginWay
|
|
||||||
} else if *src.OriginProtocol == "https" {
|
|
||||||
accessOriginWay := int32(3)
|
|
||||||
dest.AccessOriginWay = &accessOriginWay
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.ForceRedirect != nil {
|
|
||||||
dest.ForceRedirectConfig = &cdnModel.ForceRedirect{}
|
|
||||||
|
|
||||||
if src.ForceRedirect.Status == "on" {
|
|
||||||
dest.ForceRedirectConfig.Switch = 1
|
|
||||||
dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
|
|
||||||
} else {
|
|
||||||
dest.ForceRedirectConfig.Switch = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.Https != nil {
|
|
||||||
if *src.Https.Http2Status == "on" {
|
|
||||||
http2 := int32(1)
|
|
||||||
dest.Http2 = &http2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|||||||
386
internal/deployer/huaweicloud_elb.go
Normal file
386
internal/deployer/huaweicloud_elb.go
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
|
hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
|
||||||
|
hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
|
||||||
|
hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
|
||||||
|
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
||||||
|
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
||||||
|
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderHcElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudELBDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *hcElb.ElbClient
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.HuaweiCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&HuaweiCloudELBDeployer{}).createSdkClient(
|
||||||
|
access.AccessKeyId,
|
||||||
|
access.SecretAccessKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderHcElb.New(&uploaderHcElb.HuaweiCloudELBUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
|
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HuaweiCloudELBDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context) error {
|
||||||
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
|
case "certificate":
|
||||||
|
// 部署到指定证书
|
||||||
|
if err := d.deployToCertificate(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "loadbalancer":
|
||||||
|
// 部署到指定负载均衡器
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
// 部署到指定监听器
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId, err := (&HuaweiCloudELBDeployer{}).getSdkProjectId(
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey,
|
||||||
|
region,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := basic.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
WithProjectId(projectId).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcElbRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcElb.ElbClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hcElb.NewElbClient(hcClient)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *HuaweiCloudELBDeployer) getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // IAM 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := global.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcIamRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcIam.IamClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hcIam.NewIamClient(hcClient)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &hcIamModel.KeystoneListProjectsRequest{
|
||||||
|
Name: ®ion,
|
||||||
|
}
|
||||||
|
response, err := client.KeystoneListProjects(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
||||||
|
return "", errors.New("no project found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*response.Projects)[0].Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context) error {
|
||||||
|
hcCertId := d.option.DeployConfig.GetConfigAsString("certificateId")
|
||||||
|
if hcCertId == "" {
|
||||||
|
return errors.New("`certificateId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新证书
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/UpdateCertificate.html
|
||||||
|
updateCertificateReq := &hcElbModel.UpdateCertificateRequest{
|
||||||
|
CertificateId: hcCertId,
|
||||||
|
Body: &hcElbModel.UpdateCertificateRequestBody{
|
||||||
|
Certificate: &hcElbModel.UpdateCertificateOption{
|
||||||
|
Certificate: cast.StringPtr(d.option.Certificate.Certificate),
|
||||||
|
PrivateKey: cast.StringPtr(d.option.Certificate.PrivateKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新 ELB 证书", updateCertificateResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
hcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
if hcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
hcListenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡器详情
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ShowLoadBalancer.html
|
||||||
|
showLoadBalancerReq := &hcElbModel.ShowLoadBalancerRequest{
|
||||||
|
LoadbalancerId: hcLoadbalancerId,
|
||||||
|
}
|
||||||
|
showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器", showLoadBalancerResp))
|
||||||
|
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ListListeners.html
|
||||||
|
listListenersLimit := int32(2000)
|
||||||
|
var listListenersMarker *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &hcElbModel.ListListenersRequest{
|
||||||
|
Limit: cast.Int32Ptr(listListenersLimit),
|
||||||
|
Marker: listListenersMarker,
|
||||||
|
Protocol: &[]string{"HTTPS", "TERMINATED_HTTPS"},
|
||||||
|
LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id},
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Listeners != nil {
|
||||||
|
for _, listener := range *listListenersResp.Listeners {
|
||||||
|
hcListenerIds = append(hcListenerIds, listener.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersMarker = listListenersResp.PageInfo.NextMarker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器下的监听器", hcListenerIds))
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听器证书
|
||||||
|
var errs []error
|
||||||
|
for _, hcListenerId := range hcListenerIds {
|
||||||
|
if err := d.modifyListenerCertificate(ctx, hcListenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
hcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
if hcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听器证书
|
||||||
|
if err := d.modifyListenerCertificate(ctx, hcListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, hcListenerId string, hcCertId string) error {
|
||||||
|
// 查询监听器详情
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
|
||||||
|
showListenerReq := &hcElbModel.ShowListenerRequest{
|
||||||
|
ListenerId: hcListenerId,
|
||||||
|
}
|
||||||
|
showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到 ELB 监听器", showListenerResp))
|
||||||
|
|
||||||
|
// 更新监听器
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/UpdateListener.html
|
||||||
|
updateListenerReq := &hcElbModel.UpdateListenerRequest{
|
||||||
|
ListenerId: hcListenerId,
|
||||||
|
Body: &hcElbModel.UpdateListenerRequestBody{
|
||||||
|
Listener: &hcElbModel.UpdateListenerOption{
|
||||||
|
DefaultTlsContainerRef: cast.StringPtr(hcCertId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if showListenerResp.Listener.SniContainerRefs != nil {
|
||||||
|
if len(showListenerResp.Listener.SniContainerRefs) > 0 {
|
||||||
|
// 如果开启 SNI,需替换同 SAN 的证书
|
||||||
|
sniCertIds := make([]string, 0)
|
||||||
|
sniCertIds = append(sniCertIds, hcCertId)
|
||||||
|
|
||||||
|
listOldCertificateReq := &hcElbModel.ListCertificatesRequest{
|
||||||
|
Id: &showListenerResp.Listener.SniContainerRefs,
|
||||||
|
}
|
||||||
|
listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'")
|
||||||
|
}
|
||||||
|
|
||||||
|
showNewCertificateReq := &hcElbModel.ShowCertificateRequest{
|
||||||
|
CertificateId: hcCertId,
|
||||||
|
}
|
||||||
|
showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, certificate := range *listOldCertificateResp.Certificates {
|
||||||
|
oldCertificate := certificate
|
||||||
|
newCertificate := showNewCertificateResp.Certificate
|
||||||
|
|
||||||
|
if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil {
|
||||||
|
oldCertificateSans := oldCertificate.SubjectAlternativeNames
|
||||||
|
newCertificateSans := newCertificate.SubjectAlternativeNames
|
||||||
|
sort.Strings(*oldCertificateSans)
|
||||||
|
sort.Strings(*newCertificateSans)
|
||||||
|
if strings.Join(*oldCertificateSans, ";") == strings.Join(*newCertificateSans, ";") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if oldCertificate.Domain == newCertificate.Domain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sniCertIds = append(sniCertIds, certificate.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateListenerReq.Body.Listener.SniContainerRefs = &sniCertIds
|
||||||
|
}
|
||||||
|
|
||||||
|
if showListenerResp.Listener.SniMatchAlgo != "" {
|
||||||
|
updateListenerReq.Body.Listener.SniMatchAlgo = cast.StringPtr(showListenerResp.Listener.SniMatchAlgo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新 ELB 监听器", updateListenerResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,24 +3,43 @@ package deployer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
xerrors "github.com/pkg/errors"
|
||||||
|
k8sCore "k8s.io/api/core/v1"
|
||||||
|
k8sMeta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
type K8sSecretDeployer struct {
|
type K8sSecretDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
|
|
||||||
|
k8sClient *kubernetes.Clientset
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.KubernetesAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&K8sSecretDeployer{}).createK8sClient(access)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create k8s client")
|
||||||
|
}
|
||||||
|
|
||||||
return &K8sSecretDeployer{
|
return &K8sSecretDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
infos: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
k8sClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,73 +47,82 @@ func (d *K8sSecretDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetInfo() []string {
|
func (d *K8sSecretDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.KubernetesAccess{}
|
namespace := d.option.DeployConfig.GetConfigAsString("namespace")
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
secretName := d.option.DeployConfig.GetConfigAsString("secretName")
|
||||||
return err
|
secretDataKeyForCrt := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForCrt", "tls.crt")
|
||||||
}
|
secretDataKeyForKey := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForKey", "tls.key")
|
||||||
|
|
||||||
client, err := d.createClient(access)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("kubeClient 创建成功", nil))
|
|
||||||
|
|
||||||
namespace := getDeployString(d.option.DeployConfig, "namespace")
|
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
namespace = "default"
|
namespace = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
secretName := getDeployString(d.option.DeployConfig, "secretName")
|
|
||||||
if secretName == "" {
|
if secretName == "" {
|
||||||
return fmt.Errorf("k8s secret name is empty")
|
return errors.New("`secretName` is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
secretDataKeyForCrt := getDeployString(d.option.DeployConfig, "secretDataKeyForCrt")
|
certX509, err := x509.ParseCertificateFromPEM(d.option.Certificate.Certificate)
|
||||||
if secretDataKeyForCrt == "" {
|
if err != nil {
|
||||||
namespace = "tls.crt"
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretDataKeyForKey := getDeployString(d.option.DeployConfig, "secretDataKeyForKey")
|
secretPayload := k8sCore.Secret{
|
||||||
if secretDataKeyForKey == "" {
|
TypeMeta: k8sMeta.TypeMeta{
|
||||||
namespace = "tls.key"
|
Kind: "Secret",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: k8sMeta.ObjectMeta{
|
||||||
|
Name: secretName,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"certimate/domains": d.option.Domain,
|
||||||
|
"certimate/alt-names": strings.Join(certX509.DNSNames, ","),
|
||||||
|
"certimate/common-name": certX509.Subject.CommonName,
|
||||||
|
"certimate/issuer-organization": strings.Join(certX509.Issuer.Organization, ","),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: k8sCore.SecretType("kubernetes.io/tls"),
|
||||||
}
|
}
|
||||||
|
secretPayload.Data = make(map[string][]byte)
|
||||||
|
secretPayload.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
|
||||||
|
secretPayload.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
|
||||||
|
|
||||||
// 获取 Secret 实例
|
// 获取 Secret 实例
|
||||||
secret, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMetaV1.GetOptions{})
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMeta.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get k8s secret: %w", err)
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Create(context.TODO(), &secretPayload, k8sMeta.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create k8s secret")
|
||||||
|
} else {
|
||||||
|
d.infos = append(d.infos, toStr("Certificate has been created in K8s Secret", nil))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 Secret Data
|
// 更新 Secret 实例
|
||||||
secret.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
|
_, err = d.k8sClient.CoreV1().Secrets(namespace).Update(context.TODO(), &secretPayload, k8sMeta.UpdateOptions{})
|
||||||
secret.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
|
|
||||||
_, err = client.CoreV1().Secrets(namespace).Update(context.TODO(), secret, k8sMetaV1.UpdateOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update k8s secret: %w", err)
|
return xerrors.Wrap(err, "failed to update k8s secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("证书已更新到 K8s Secret", nil))
|
d.infos = append(d.infos, toStr("Certificate has been updated to K8s Secret", nil))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
func (d *K8sSecretDeployer) createK8sClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
||||||
kubeConfig, err := clientcmd.Load([]byte(access.KubeConfig))
|
var config *rest.Config
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, err
|
if access.KubeConfig == "" {
|
||||||
|
config, err = rest.InClusterConfig()
|
||||||
|
} else {
|
||||||
|
kubeConfig, err := clientcmd.NewClientConfigFromBytes([]byte(access.KubeConfig))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config, err = kubeConfig.ClientConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
|
||||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: ""},
|
|
||||||
&clientcmd.ConfigOverrides{CurrentContext: kubeConfig.CurrentContext},
|
|
||||||
)
|
|
||||||
config, err := clientConfig.ClientConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalDeployer struct {
|
type LocalDeployer struct {
|
||||||
@@ -17,6 +18,18 @@ type LocalDeployer struct {
|
|||||||
infos []string
|
infos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
certFormatPEM = "pem"
|
||||||
|
certFormatPFX = "pfx"
|
||||||
|
certFormatJKS = "jks"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellEnvSh = "sh"
|
||||||
|
shellEnvCmd = "cmd"
|
||||||
|
shellEnvPowershell = "powershell"
|
||||||
|
)
|
||||||
|
|
||||||
func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
|
func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
return &LocalDeployer{
|
return &LocalDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
@@ -28,84 +41,122 @@ func (d *LocalDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) GetInfo() []string {
|
func (d *LocalDeployer) GetInfos() []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.LocalAccess{}
|
// 执行前置命令
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
|
|
||||||
|
|
||||||
if preCommand != "" {
|
if preCommand != "" {
|
||||||
if err := execCmd(preCommand); err != nil {
|
stdout, stderr, err := d.execCommand(preCommand)
|
||||||
return fmt.Errorf("执行前置命令失败: %w", err)
|
if err != nil {
|
||||||
|
return xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("执行前置命令成功", stdout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制证书文件
|
// 写入证书和私钥文件
|
||||||
if err := copyFile(getDeployString(d.option.DeployConfig, "certPath"), d.option.Certificate.Certificate); err != nil {
|
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
||||||
return fmt.Errorf("复制证书失败: %w", err)
|
case certFormatPEM:
|
||||||
}
|
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 复制私钥文件
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
if err := copyFile(getDeployString(d.option.DeployConfig, "keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
|
||||||
return fmt.Errorf("复制私钥失败: %w", err)
|
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
||||||
|
|
||||||
|
case certFormatPFX:
|
||||||
|
pfxData, err := convertPEMToPFX(
|
||||||
|
d.option.Certificate.Certificate,
|
||||||
|
d.option.Certificate.PrivateKey,
|
||||||
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
||||||
|
case certFormatJKS:
|
||||||
|
jksData, err := convertPEMToJKS(
|
||||||
|
d.option.Certificate.Certificate,
|
||||||
|
d.option.Certificate.PrivateKey,
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksKeypass"),
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported format")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行命令
|
// 执行命令
|
||||||
if err := execCmd(getDeployString(d.option.DeployConfig, "command")); err != nil {
|
command := d.option.DeployConfig.GetConfigAsString("command")
|
||||||
return fmt.Errorf("执行命令失败: %w", err)
|
if command != "" {
|
||||||
|
stdout, stderr, err := d.execCommand(command)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("执行命令成功", stdout))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func execCmd(command string) error {
|
func (d *LocalDeployer) execCommand(command string) (string, string, error) {
|
||||||
// 执行命令
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
switch d.option.DeployConfig.GetConfigAsString("shell") {
|
||||||
cmd = exec.Command("cmd", "/C", command)
|
case shellEnvSh:
|
||||||
} else {
|
|
||||||
cmd = exec.Command("sh", "-c", command)
|
cmd = exec.Command("sh", "-c", command)
|
||||||
|
|
||||||
|
case shellEnvCmd:
|
||||||
|
cmd = exec.Command("cmd", "/C", command)
|
||||||
|
|
||||||
|
case shellEnvPowershell:
|
||||||
|
cmd = exec.Command("powershell", "-Command", command)
|
||||||
|
|
||||||
|
case "":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd = exec.Command("cmd", "/C", command)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("sh", "-c", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", "", errors.New("unsupported shell")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
var stdoutBuf bytes.Buffer
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stdout = &stdoutBuf
|
||||||
|
var stderrBuf bytes.Buffer
|
||||||
|
cmd.Stderr = &stderrBuf
|
||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("执行命令失败: %w", err)
|
return "", "", xerrors.Wrap(err, "failed to execute shell script")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(path string, content string) error {
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
|
|
||||||
// 如果目录不存在,创建目录
|
|
||||||
err := os.MkdirAll(dir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("创建目录失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建或打开文件
|
|
||||||
file, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("创建文件失败: %w", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// 写入内容到文件
|
|
||||||
_, err = file.Write([]byte(content))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("写入文件失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,55 @@
|
|||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"strings"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
|
||||||
|
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const qiniuGateway = "http://api.qiniu.com"
|
|
||||||
|
|
||||||
type QiniuCDNDeployer struct {
|
type QiniuCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
info []string
|
infos []string
|
||||||
credentials *auth.Credentials
|
|
||||||
|
sdkClient *qiniuEx.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) {
|
func NewQiniuCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.QiniuAccess{}
|
access := &domain.QiniuAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&QiniuCDNDeployer{}).createSdkClient(
|
||||||
|
access.AccessKey,
|
||||||
|
access.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderQiniu.New(&uploaderQiniu.QiniuSSLCertUploaderConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &QiniuCDNDeployer{
|
return &QiniuCDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
info: make([]string, 0),
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
credentials: auth.New(access.AccessKey, access.SecretKey),
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,173 +57,57 @@ func (d *QiniuCDNDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetInfo() []string {
|
func (d *QiniuCDNDeployer) GetInfos() []string {
|
||||||
return d.info
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// 上传证书
|
||||||
certId, err := d.uploadCert()
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("uploadCert failed: %w", err)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 在七牛 CDN 中泛域名表示为 .example.com,需去除前缀星号
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domain = strings.TrimPrefix(domain, "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取域名信息
|
// 获取域名信息
|
||||||
domainInfo, err := d.getDomainInfo()
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
|
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getDomainInfo failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断域名是否启用 https
|
d.infos = append(d.infos, toStr("已获取域名信息", getDomainInfoResp))
|
||||||
if domainInfo.Https != nil && domainInfo.Https.CertID != "" {
|
|
||||||
// 启用了 https
|
// 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS
|
||||||
// 修改域名证书
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
err = d.modifyDomainCert(certId)
|
if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" {
|
||||||
|
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("modifyDomainCert failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改域名证书", modifyDomainHttpsConfResp))
|
||||||
} else {
|
} else {
|
||||||
// 没启用 https
|
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true)
|
||||||
// 启用 https
|
|
||||||
err = d.enableHttps(certId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("enableHttps failed: %w", err)
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已将域名升级为 HTTPS", enableDomainHttpsResp))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) enableHttps(certId string) error {
|
func (u *QiniuCDNDeployer) createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) {
|
||||||
path := fmt.Sprintf("/domain/%s/sslize", getDeployString(d.option.DeployConfig, "domain"))
|
credential := auth.New(accessKey, secretKey)
|
||||||
|
client := qiniuEx.NewClient(credential)
|
||||||
body := &qiniuModifyDomainCertReq{
|
return client, nil
|
||||||
CertID: certId,
|
|
||||||
ForceHttps: true,
|
|
||||||
Http2Enable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable https failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable https failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuDomainInfo struct {
|
|
||||||
Https *qiniuModifyDomainCertReq `json:"https"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) getDomainInfo() (*qiniuDomainInfo, error) {
|
|
||||||
path := fmt.Sprintf("/domain/%s", getDeployString(d.option.DeployConfig, "domain"))
|
|
||||||
|
|
||||||
res, err := d.req(qiniuGateway+path, http.MethodGet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &qiniuDomainInfo{}
|
|
||||||
err = json.Unmarshal(res, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("json.Unmarshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuUploadCertReq struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CommonName string `json:"common_name"`
|
|
||||||
Pri string `json:"pri"`
|
|
||||||
Ca string `json:"ca"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuUploadCertResp struct {
|
|
||||||
CertID string `json:"certID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) uploadCert() (string, error) {
|
|
||||||
path := "/sslcert"
|
|
||||||
|
|
||||||
body := &qiniuUploadCertReq{
|
|
||||||
Name: getDeployString(d.option.DeployConfig, "domain"),
|
|
||||||
CommonName: getDeployString(d.option.DeployConfig, "domain"),
|
|
||||||
Pri: d.option.Certificate.PrivateKey,
|
|
||||||
Ca: d.option.Certificate.Certificate,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("json.Marshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := d.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
resp := &qiniuUploadCertResp{}
|
|
||||||
err = json.Unmarshal(res, resp)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("json.Unmarshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.CertID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type qiniuModifyDomainCertReq struct {
|
|
||||||
CertID string `json:"certId"`
|
|
||||||
ForceHttps bool `json:"forceHttps"`
|
|
||||||
Http2Enable bool `json:"http2Enable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) modifyDomainCert(certId string) error {
|
|
||||||
path := fmt.Sprintf("/domain/%s/httpsconf", getDeployString(d.option.DeployConfig, "domain"))
|
|
||||||
|
|
||||||
body := &qiniuModifyDomainCertReq{
|
|
||||||
CertID: certId,
|
|
||||||
ForceHttps: true,
|
|
||||||
Http2Enable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("json.Marshal failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("req failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) req(url, method string, body io.Reader) ([]byte, error) {
|
|
||||||
req := xhttp.BuildReq(url, method, body, map[string]string{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := d.credentials.AddToken(auth.TokenQBox, req); err != nil {
|
|
||||||
return nil, fmt.Errorf("credentials.AddToken failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody, err := xhttp.ToRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ToRequest failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer respBody.Close()
|
|
||||||
|
|
||||||
res, err := io.ReadAll(respBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("io.ReadAll failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/applicant"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_qiuniu_uploadCert(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
option *DeployerOption
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
fields: fields{
|
|
||||||
option: &DeployerOption{
|
|
||||||
DomainId: "1",
|
|
||||||
Domain: "example.com",
|
|
||||||
Product: "test",
|
|
||||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
|
||||||
Certificate: applicant.Certificate{
|
|
||||||
Certificate: "",
|
|
||||||
PrivateKey: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q, _ := NewQiniuCDNDeployer(tt.fields.option)
|
|
||||||
got, err := q.uploadCert()
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("qiuniu.uploadCert() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_qiuniu_modifyDomainCert(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
option *DeployerOption
|
|
||||||
info []string
|
|
||||||
credentials *auth.Credentials
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
certId string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
fields: fields{
|
|
||||||
option: &DeployerOption{
|
|
||||||
DomainId: "1",
|
|
||||||
Domain: "jt1.ikit.fun",
|
|
||||||
Product: "test",
|
|
||||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q, _ := NewQiniuCDNDeployer(tt.fields.option)
|
|
||||||
if err := q.modifyDomainCert(tt.args.certId); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
xpath "path"
|
"path/filepath"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
sshPkg "golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
@@ -30,7 +32,7 @@ func (d *SSHDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) GetInfo() []string {
|
func (d *SSHDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,52 +43,126 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 连接
|
// 连接
|
||||||
client, err := d.createClient(access)
|
client, err := d.createSshClient(access)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("ssh连接成功", nil))
|
d.infos = append(d.infos, toStr("SSH 连接成功", nil))
|
||||||
|
|
||||||
// 执行前置命令
|
// 执行前置命令
|
||||||
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
|
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
||||||
if preCommand != "" {
|
if preCommand != "" {
|
||||||
stdout, stderr, err := d.sshExecCommand(client, preCommand)
|
stdout, stderr, err := d.sshExecCommand(client, preCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书
|
// 上传证书和私钥文件
|
||||||
if err := d.upload(client, d.option.Certificate.Certificate, getDeployString(d.option.DeployConfig, "certPath")); err != nil {
|
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
case certFormatPEM:
|
||||||
|
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
||||||
|
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
||||||
|
|
||||||
|
case certFormatPFX:
|
||||||
|
pfxData, err := convertPEMToPFX(
|
||||||
|
d.option.Certificate.Certificate,
|
||||||
|
d.option.Certificate.PrivateKey,
|
||||||
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
||||||
|
case certFormatJKS:
|
||||||
|
jksData, err := convertPEMToJKS(
|
||||||
|
d.option.Certificate.Certificate,
|
||||||
|
d.option.Certificate.PrivateKey,
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksKeypass"),
|
||||||
|
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported format")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("ssh上传证书成功", nil))
|
|
||||||
|
|
||||||
// 上传私钥
|
|
||||||
if err := d.upload(client, d.option.Certificate.PrivateKey, getDeployString(d.option.DeployConfig, "keyPath")); err != nil {
|
|
||||||
return fmt.Errorf("failed to upload private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("ssh上传私钥成功", nil))
|
|
||||||
|
|
||||||
// 执行命令
|
// 执行命令
|
||||||
stdout, stderr, err := d.sshExecCommand(client, getDeployString(d.option.DeployConfig, "command"))
|
command := d.option.DeployConfig.GetConfigAsString("command")
|
||||||
if err != nil {
|
if command != "" {
|
||||||
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
stdout, stderr, err := d.sshExecCommand(client, command)
|
||||||
}
|
if err != nil {
|
||||||
|
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("ssh执行命令成功", stdout))
|
d.infos = append(d.infos, toStr("SSH 执行命令成功", stdout))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
|
func (d *SSHDeployer) createSshClient(access *domain.SSHAccess) (*ssh.Client, error) {
|
||||||
session, err := client.NewSession()
|
var authMethod ssh.AuthMethod
|
||||||
|
|
||||||
|
if access.Key != "" {
|
||||||
|
var signer ssh.Signer
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if access.KeyPassphrase != "" {
|
||||||
|
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
|
||||||
|
} else {
|
||||||
|
signer, err = ssh.ParsePrivateKey([]byte(access.Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authMethod = ssh.PublicKeys(signer)
|
||||||
|
} else {
|
||||||
|
authMethod = ssh.Password(access.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &ssh.ClientConfig{
|
||||||
|
User: access.Username,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
authMethod,
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SSHDeployer) sshExecCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
||||||
|
session, err := sshCli.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("failed to create ssh session: %w", err)
|
return "", "", xerrors.Wrap(err, "failed to create ssh session")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
@@ -95,60 +171,38 @@ func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (str
|
|||||||
var stderrBuf bytes.Buffer
|
var stderrBuf bytes.Buffer
|
||||||
session.Stderr = &stderrBuf
|
session.Stderr = &stderrBuf
|
||||||
err = session.Run(command)
|
err = session.Run(command)
|
||||||
return stdoutBuf.String(), stderrBuf.String(), err
|
if err != nil {
|
||||||
|
return "", "", xerrors.Wrap(err, "failed to execute ssh script")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error {
|
func (d *SSHDeployer) writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
||||||
sftpCli, err := sftp.NewClient(client)
|
return d.writeSftpFile(sshCli, path, []byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SSHDeployer) writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
|
||||||
|
sftpCli, err := sftp.NewClient(sshCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create sftp client: %w", err)
|
return xerrors.Wrap(err, "failed to create sftp client")
|
||||||
}
|
}
|
||||||
defer sftpCli.Close()
|
defer sftpCli.Close()
|
||||||
|
|
||||||
if err := sftpCli.MkdirAll(xpath.Dir(path)); err != nil {
|
if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
|
||||||
return fmt.Errorf("failed to create remote directory: %w", err)
|
return xerrors.Wrap(err, "failed to create remote directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open remote file: %w", err)
|
return xerrors.Wrap(err, "failed to open remote file")
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
_, err = file.Write([]byte(content))
|
_, err = file.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write to remote file: %w", err)
|
return xerrors.Wrap(err, "failed to write to remote file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) createClient(access *domain.SSHAccess) (*sshPkg.Client, error) {
|
|
||||||
var authMethod sshPkg.AuthMethod
|
|
||||||
|
|
||||||
if access.Key != "" {
|
|
||||||
var signer sshPkg.Signer
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if access.KeyPassphrase != "" {
|
|
||||||
signer, err = sshPkg.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
|
|
||||||
} else {
|
|
||||||
signer, err = sshPkg.ParsePrivateKey([]byte(access.Key))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
authMethod = sshPkg.PublicKeys(signer)
|
|
||||||
} else {
|
|
||||||
authMethod = sshPkg.Password(access.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sshPkg.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &sshPkg.ClientConfig{
|
|
||||||
User: access.Username,
|
|
||||||
Auth: []sshPkg.AuthMethod{
|
|
||||||
authMethod,
|
|
||||||
},
|
|
||||||
HostKeyCallback: sshPkg.InsecureIgnoreHostKey(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,41 +2,62 @@ package deployer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/rand"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TencentCDNDeployer struct {
|
type TencentCDNDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
credential *common.Credential
|
infos []string
|
||||||
infos []string
|
|
||||||
|
sdkClients *tencentCDNDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentCDNDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.TencentAccess{}
|
access := &domain.TencentAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := common.NewCredential(
|
clients, err := (&TencentCDNDeployer{}).createSdkClients(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
return &TencentCDNDeployer{
|
return &TencentCDNDeployer{
|
||||||
option: option,
|
option: option,
|
||||||
credential: credential,
|
infos: make([]string, 0),
|
||||||
infos: make([]string, 0),
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,101 +65,129 @@ func (d *TencentCDNDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetInfo() []string {
|
func (d *TencentCDNDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
// 上传证书
|
// 上传证书到 SSL
|
||||||
certId, err := d.uploadCert()
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
d.infos = append(d.infos, toStr("上传证书", certId))
|
|
||||||
|
|
||||||
if err := d.deploy(certId); err != nil {
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
return fmt.Errorf("failed to deploy: %w", err)
|
|
||||||
|
// 获取待部署的 CDN 实例
|
||||||
|
// 如果是泛域名,根据证书匹配 CDN 实例
|
||||||
|
tcInstanceIds := make([]string, 0)
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcInstanceIds = domains
|
||||||
|
} else {
|
||||||
|
tcInstanceIds = append(tcInstanceIds, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳过已部署的 CDN 实例
|
||||||
|
if len(tcInstanceIds) > 0 {
|
||||||
|
deployedDomains, err := d.getDeployedDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := make([]string, 0)
|
||||||
|
for _, aliInstanceId := range tcInstanceIds {
|
||||||
|
if !slices.Contains(deployedDomains, aliInstanceId) {
|
||||||
|
temp = append(temp, aliInstanceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcInstanceIds = temp
|
||||||
|
}
|
||||||
|
if len(tcInstanceIds) == 0 {
|
||||||
|
d.infos = append(d.infos, "已部署过或没有要部署的 CDN 实例")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书部署到 CDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(tcInstanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) uploadCert() (string, error) {
|
func (d *TencentCDNDeployer) createSdkClients(secretId, secretKey string) (*tencentCDNDeployerSdkClients, error) {
|
||||||
cpf := profile.NewClientProfile()
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
|
||||||
|
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
|
||||||
request := ssl.NewUploadCertificateRequest()
|
|
||||||
|
|
||||||
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
|
|
||||||
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
|
|
||||||
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
|
|
||||||
request.Repeatable = common.BoolPtr(false)
|
|
||||||
|
|
||||||
response, err := client.UploadCertificate(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to upload certificate: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return *response.Response.CertificateId, nil
|
cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentCDNDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) deploy(certId string) error {
|
func (d *TencentCDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
cpf := profile.NewClientProfile()
|
// 获取证书中的可用域名
|
||||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
client, _ := ssl.NewClient(d.credential, "", cpf)
|
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("cdn")
|
||||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
request := ssl.NewDeployCertificateInstanceRequest()
|
|
||||||
|
|
||||||
request.CertificateId = common.StringPtr(certId)
|
|
||||||
request.ResourceType = common.StringPtr("cdn")
|
|
||||||
request.Status = common.Int64Ptr(1)
|
|
||||||
|
|
||||||
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
|
|
||||||
domain := getDeployString(d.option.DeployConfig, "domain")
|
|
||||||
if strings.Contains(domain, "*") {
|
|
||||||
list, errGetList := d.getDomainList()
|
|
||||||
if errGetList != nil {
|
|
||||||
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
|
|
||||||
}
|
|
||||||
if list == nil || len(list) == 0 {
|
|
||||||
return fmt.Errorf("failed to get certificate domain list: empty list.")
|
|
||||||
}
|
|
||||||
request.InstanceIdList = common.StringPtrs(list)
|
|
||||||
} else { // 否则直接使用传入的域名
|
|
||||||
request.InstanceIdList = common.StringPtrs([]string{domain})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
|
||||||
resp, err := client.DeployCertificateInstance(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to deploy certificate: %w", err)
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
}
|
|
||||||
d.infos = append(d.infos, toStr("部署证书", resp.Response))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) getDomainList() ([]string, error) {
|
|
||||||
cpf := profile.NewClientProfile()
|
|
||||||
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
|
|
||||||
client, _ := cdn.NewClient(d.credential, "", cpf)
|
|
||||||
|
|
||||||
request := cdn.NewDescribeCertDomainsRequest()
|
|
||||||
|
|
||||||
cert := base64.StdEncoding.EncodeToString([]byte(d.option.Certificate.Certificate))
|
|
||||||
request.Cert = &cert
|
|
||||||
|
|
||||||
response, err := client.DescribeCertDomains(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get domain list: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := make([]string, 0)
|
domains := make([]string, 0)
|
||||||
for _, domain := range response.Response.Domains {
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
domains = append(domains, *domain)
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
|
domains = append(domains, *domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCDNDeployer) getDeployedDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
|
// 根据证书查询关联 CDN 域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/62674
|
||||||
|
describeDeployedResourcesReq := tcSsl.NewDescribeDeployedResourcesRequest()
|
||||||
|
describeDeployedResourcesReq.CertificateIds = common.StringPtrs([]string{tcCertId})
|
||||||
|
describeDeployedResourcesReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
describeDeployedResourcesResp, err := d.sdkClients.ssl.DescribeDeployedResources(describeDeployedResourcesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeDeployedResources'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeDeployedResourcesResp.Response.DeployedResources != nil {
|
||||||
|
for _, deployedResource := range describeDeployedResourcesResp.Response.DeployedResources {
|
||||||
|
for _, resource := range deployedResource.Resources {
|
||||||
|
domains = append(domains, *resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return domains, nil
|
return domains, nil
|
||||||
|
|||||||
328
internal/deployer/tencent_clb.go
Normal file
328
internal/deployer/tencent_clb.go
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcClb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCLBDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentCLBDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentCLBDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
clb *tcClb.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTencentCLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := (&TencentCLBDeployer{}).createSdkClients(
|
||||||
|
access.SecretId,
|
||||||
|
access.SecretKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCLBDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
||||||
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
|
case "ssl-deploy":
|
||||||
|
// 通过 SSL 服务部署到云资源实例
|
||||||
|
err := d.deployToInstanceUseSsl(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "loadbalancer":
|
||||||
|
// 部署到指定负载均衡器
|
||||||
|
if err := d.deployToLoadbalancer(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "listener":
|
||||||
|
// 部署到指定监听器
|
||||||
|
if err := d.deployToListener(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "ruledomain":
|
||||||
|
// 部署到指定七层监听转发规则域名
|
||||||
|
if err := d.deployToRuleDomain(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported resource type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clbClient, err := tcClb.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentCLBDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
clb: clbClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToInstanceUseSsl(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 证书部署到 CLB 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("clb")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
if tcDomain == "" {
|
||||||
|
// 未开启 SNI,只需指定到监听器
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", tcLoadbalancerId, tcListenerId)})
|
||||||
|
} else {
|
||||||
|
// 开启 SNI,需指定到域名(支持泛域名)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", tcLoadbalancerId, tcListenerId, tcDomain)})
|
||||||
|
}
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerIds := make([]string, 0)
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询负载均衡器详细信息
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/46916
|
||||||
|
describeLoadBalancersDetailReq := tcClb.NewDescribeLoadBalancersDetailRequest()
|
||||||
|
describeLoadBalancersDetailResp, err := d.sdkClients.clb.DescribeLoadBalancersDetail(describeLoadBalancersDetailReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeLoadBalancersDetail'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到负载均衡详细信息", describeLoadBalancersDetailResp))
|
||||||
|
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
} else {
|
||||||
|
if describeListenersResp.Response.Listeners != nil {
|
||||||
|
for _, listener := range describeListenersResp.Response.Listeners {
|
||||||
|
if listener.Protocol == nil || (*listener.Protocol != "HTTPS" && *listener.Protocol != "TCP_SSL" && *listener.Protocol != "QUIC") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tcListenerIds = append(tcListenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到负载均衡器下的监听器", tcListenerIds))
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 批量更新监听器证书
|
||||||
|
var errs []error
|
||||||
|
for _, tcListenerId := range tcListenerIds {
|
||||||
|
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToListener(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 更新监听器证书
|
||||||
|
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) deployToRuleDomain(ctx context.Context) error {
|
||||||
|
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
||||||
|
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
||||||
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if tcLoadbalancerId == "" {
|
||||||
|
return errors.New("`loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if tcListenerId == "" {
|
||||||
|
return errors.New("`listenerId` is required")
|
||||||
|
}
|
||||||
|
if tcDomain == "" {
|
||||||
|
return errors.New("`domain` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 修改负载均衡七层监听器转发规则的域名级别属性
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/38092
|
||||||
|
modifyDomainAttributesReq := tcClb.NewModifyDomainAttributesRequest()
|
||||||
|
modifyDomainAttributesReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
modifyDomainAttributesReq.ListenerId = common.StringPtr(tcListenerId)
|
||||||
|
modifyDomainAttributesReq.Domain = common.StringPtr(tcDomain)
|
||||||
|
modifyDomainAttributesReq.Certificate = &tcClb.CertificateInput{
|
||||||
|
SSLMode: common.StringPtr("UNIDIRECTIONAL"),
|
||||||
|
CertId: common.StringPtr(upres.CertId),
|
||||||
|
}
|
||||||
|
modifyDomainAttributesResp, err := d.sdkClients.clb.ModifyDomainAttributes(modifyDomainAttributesReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCLBDeployer) modifyListenerCertificate(ctx context.Context, tcLoadbalancerId, tcListenerId, tcCertId string) error {
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
describeListenersReq.ListenerIds = common.StringPtrs([]string{tcListenerId})
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
}
|
||||||
|
if len(describeListenersResp.Response.Listeners) == 0 {
|
||||||
|
d.infos = append(d.infos, toStr("未找到监听器", nil))
|
||||||
|
return errors.New("listener not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到监听器属性", describeListenersResp.Response))
|
||||||
|
|
||||||
|
// 修改监听器属性
|
||||||
|
// REF: https://cloud.tencent.com/document/product/214/30681
|
||||||
|
modifyListenerReq := tcClb.NewModifyListenerRequest()
|
||||||
|
modifyListenerReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
||||||
|
modifyListenerReq.ListenerId = common.StringPtr(tcListenerId)
|
||||||
|
modifyListenerReq.Certificate = &tcClb.CertificateInput{CertId: common.StringPtr(tcCertId)}
|
||||||
|
if describeListenersResp.Response.Listeners[0].Certificate != nil && describeListenersResp.Response.Listeners[0].Certificate.SSLMode != nil {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = describeListenersResp.Response.Listeners[0].Certificate.SSLMode
|
||||||
|
modifyListenerReq.Certificate.CertCaId = describeListenersResp.Response.Listeners[0].Certificate.CertCaId
|
||||||
|
} else {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = common.StringPtr("UNIDIRECTIONAL")
|
||||||
|
}
|
||||||
|
modifyListenerResp, err := d.sdkClients.clb.ModifyListener(modifyListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已修改监听器属性", modifyListenerResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
107
internal/deployer/tencent_cos.go
Normal file
107
internal/deployer/tencent_cos.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
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/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCOSDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClient *tcSsl.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
||||||
|
access.SecretId,
|
||||||
|
access.SecretKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCOSDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCOSDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCOSDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
||||||
|
tcRegion := d.option.DeployConfig.GetConfigAsString("region")
|
||||||
|
tcBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
||||||
|
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if tcBucket == "" {
|
||||||
|
return errors.New("`bucket` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 证书部署到 COS 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cos")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", tcRegion, tcBucket, tcDomain)})
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCOSDeployer) 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
|
||||||
|
}
|
||||||
154
internal/deployer/tencent_ecdn.go
Normal file
154
internal/deployer/tencent_ecdn.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"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/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentECDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentECDNDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentECDNDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTencentECDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := (&TencentECDNDeployer{}).createSdkClients(
|
||||||
|
access.SecretId,
|
||||||
|
access.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentECDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentECDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentECDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentECDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 获取待部署的 ECDN 实例
|
||||||
|
// 如果是泛域名,根据证书匹配 ECDN 实例
|
||||||
|
aliInstanceIds := make([]string, 0)
|
||||||
|
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aliInstanceIds = domains
|
||||||
|
} else {
|
||||||
|
aliInstanceIds = append(aliInstanceIds, domain)
|
||||||
|
}
|
||||||
|
if len(aliInstanceIds) == 0 {
|
||||||
|
d.infos = append(d.infos, "没有要部署的 ECDN 实例")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书部署到 ECDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(aliInstanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentECDNDeployer) createSdkClients(secretId, secretKey string) (*tencentECDNDeployerSdkClients, 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 &tencentECDNDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentECDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
||||||
|
// 获取证书中的可用域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
|
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("ecdn")
|
||||||
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
|
domains = append(domains, *domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
119
internal/deployer/tencent_teo.go
Normal file
119
internal/deployer/tencent_teo.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
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"
|
||||||
|
tcTeo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentTEODeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
|
||||||
|
sdkClients *tencentTEODeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tencentTEODeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
teo *tcTeo.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTencentTEODeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := (&TencentTEODeployer{}).createSdkClients(
|
||||||
|
access.SecretId,
|
||||||
|
access.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentTEODeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentTEODeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentTEODeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentTEODeployer) Deploy(ctx context.Context) error {
|
||||||
|
tcZoneId := d.option.DeployConfig.GetConfigAsString("zoneId")
|
||||||
|
if tcZoneId == "" {
|
||||||
|
return xerrors.New("`zoneId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://cloud.tencent.com/document/product/1552/80764
|
||||||
|
modifyHostsCertificateReq := tcTeo.NewModifyHostsCertificateRequest()
|
||||||
|
modifyHostsCertificateReq.ZoneId = common.StringPtr(tcZoneId)
|
||||||
|
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||||
|
modifyHostsCertificateReq.Hosts = common.StringPtrs(strings.Split(strings.ReplaceAll(d.option.Domain, "\r\n", "\n"), "\n"))
|
||||||
|
modifyHostsCertificateReq.ServerCertInfo = []*tcTeo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||||
|
modifyHostsCertificateResp, err := d.sdkClients.teo.ModifyHostsCertificate(modifyHostsCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已配置域名证书", modifyHostsCertificateResp.Response))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentTEODeployer) createSdkClients(secretId, secretKey string) (*tencentTEODeployerSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
teoClient, err := tcTeo.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentTEODeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
teo: teoClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
)
|
)
|
||||||
@@ -27,7 +29,7 @@ func (d *WebhookDeployer) GetID() string {
|
|||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetInfo() []string {
|
func (d *WebhookDeployer) GetInfos() []string {
|
||||||
return d.infos
|
return d.infos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,26 +43,24 @@ type webhookData struct {
|
|||||||
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &domain.WebhookAccess{}
|
access := &domain.WebhookAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return fmt.Errorf("failed to parse hook access config: %w", err)
|
return xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &webhookData{
|
data := &webhookData{
|
||||||
Domain: d.option.Domain,
|
Domain: d.option.Domain,
|
||||||
Certificate: d.option.Certificate.Certificate,
|
Certificate: d.option.Certificate.Certificate,
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
PrivateKey: d.option.Certificate.PrivateKey,
|
||||||
Variables: getDeployVariables(d.option.DeployConfig),
|
Variables: d.option.DeployConfig.GetConfigAsVariables(),
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(data)
|
body, _ := json.Marshal(data)
|
||||||
|
|
||||||
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send hook request: %w", err)
|
return xerrors.Wrap(err, "failed to send webhook request")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("webhook response", string(resp)))
|
d.infos = append(d.infos, toStr("Webhook Response", string(resp)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ type TencentAccess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HuaweiCloudAccess struct {
|
type HuaweiCloudAccess struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaiduCloudAccess struct {
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
}
|
}
|
||||||
@@ -32,6 +37,11 @@ type QiniuAccess struct {
|
|||||||
SecretKey string `json:"secretKey"`
|
SecretKey string `json:"secretKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DogeCloudAccess struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
type NameSiloAccess struct {
|
type NameSiloAccess struct {
|
||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
}
|
}
|
||||||
|
|||||||
17
internal/domain/acme_accounts.go
Normal file
17
internal/domain/acme_accounts.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AcmeAccount struct {
|
||||||
|
Id string
|
||||||
|
Ca string
|
||||||
|
Email string
|
||||||
|
Resource *registration.Resource
|
||||||
|
Key string
|
||||||
|
Created time.Time
|
||||||
|
Updated time.Time
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
|
)
|
||||||
|
|
||||||
type ApplyConfig struct {
|
type ApplyConfig struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
@@ -16,6 +23,121 @@ type DeployConfig struct {
|
|||||||
Config map[string]any `json:"config"`
|
Config map[string]any `json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 以字符串形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
|
||||||
|
func (dc *DeployConfig) GetConfigAsString(key string) string {
|
||||||
|
return maps.GetValueAsString(dc.Config, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以字符串形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。
|
||||||
|
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
|
||||||
|
return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 32 位整数形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回 0。
|
||||||
|
func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
||||||
|
return maps.GetValueAsInt32(dc.Config, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 32 位整数形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。
|
||||||
|
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
||||||
|
return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以布尔形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
|
||||||
|
func (dc *DeployConfig) GetConfigAsBool(key string) bool {
|
||||||
|
return maps.GetValueAsBool(dc.Config, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以布尔形式获取配置项。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - key: 配置项的键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回默认值。
|
||||||
|
func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool {
|
||||||
|
return maps.GetValueOrDefaultAsBool(dc.Config, key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以变量字典形式获取配置项。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 变量字典。
|
||||||
|
func (dc *DeployConfig) GetConfigAsVariables() map[string]string {
|
||||||
|
rs := make(map[string]string)
|
||||||
|
|
||||||
|
if dc.Config != nil {
|
||||||
|
value, ok := dc.Config["variables"]
|
||||||
|
if !ok {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs := make([]KV, 0)
|
||||||
|
bts, _ := json.Marshal(value)
|
||||||
|
if err := json.Unmarshal(bts, &kvs); err != nil {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range kvs {
|
||||||
|
rs[kv.Key] = kv.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain returns the domain from the deploy config
|
||||||
|
// if the domain is a wildcard domain, and wildcard is true, return the wildcard domain
|
||||||
|
func (dc *DeployConfig) GetDomain(wildcard ...bool) string {
|
||||||
|
val := dc.GetConfigAsString("domain")
|
||||||
|
if val == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(val, "*") {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wildcard) > 0 && wildcard[0] {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(val, "*")
|
||||||
|
}
|
||||||
|
|
||||||
type KV struct {
|
type KV struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotifyChannelDingtalk = "dingtalk"
|
NotifyChannelEmail = "email"
|
||||||
NotifyChannelWebhook = "webhook"
|
NotifyChannelWebhook = "webhook"
|
||||||
NotifyChannelTelegram = "telegram"
|
NotifyChannelDingtalk = "dingtalk"
|
||||||
NotifyChannelLark = "lark"
|
NotifyChannelLark = "lark"
|
||||||
|
NotifyChannelTelegram = "telegram"
|
||||||
|
NotifyChannelServerChan = "serverchan"
|
||||||
|
NotifyChannelBark = "bark"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotifyTestPushReq struct {
|
type NotifyTestPushReq struct {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
|||||||
|
|
||||||
v, ok := (*conf)[channel]
|
v, ok := (*conf)[channel]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("channel %s not found", channel)
|
return nil, fmt.Errorf("channel \"%s\" not found", channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|||||||
@@ -105,11 +105,11 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
if err = deployer.Deploy(ctx); err != nil {
|
if err = deployer.Deploy(ctx); err != nil {
|
||||||
|
|
||||||
app.GetApp().Logger().Error("部署失败", "err", err)
|
app.GetApp().Logger().Error("部署失败", "err", err)
|
||||||
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
|
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfos()})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
|
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
|
||||||
Info: deployer.GetInfo(),
|
Info: deployer.GetInfos(),
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,13 @@ import (
|
|||||||
"github.com/usual2970/certimate/internal/utils/xtime"
|
"github.com/usual2970/certimate/internal/utils/xtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type msg struct {
|
|
||||||
subject string
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultExpireSubject = "您有{COUNT}张证书即将过期"
|
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
||||||
defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
|
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PushExpireMsg() {
|
func PushExpireMsg() {
|
||||||
// 查询即将过期的证书
|
// 查询即将过期的证书
|
||||||
|
|
||||||
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
||||||
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
|
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -34,12 +28,12 @@ func PushExpireMsg() {
|
|||||||
|
|
||||||
// 组装消息
|
// 组装消息
|
||||||
msg := buildMsg(records)
|
msg := buildMsg(records)
|
||||||
|
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Send(msg.subject, msg.message); err != nil {
|
// 发送通知
|
||||||
|
if err := SendToAllChannels(msg.Subject, msg.Message); err != nil {
|
||||||
app.GetApp().Logger().Error("send expire msg", "error", err)
|
app.GetApp().Logger().Error("send expire msg", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,22 +47,27 @@ type notifyTemplate struct {
|
|||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMsg(records []*models.Record) *msg {
|
type notifyMessage struct {
|
||||||
|
Subject string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMsg(records []*models.Record) *notifyMessage {
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询模板信息
|
// 查询模板信息
|
||||||
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
||||||
title := defaultExpireSubject
|
subject := defaultExpireSubject
|
||||||
content := defaultExpireMsg
|
message := defaultExpireMessage
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var templates *notifyTemplates
|
var templates *notifyTemplates
|
||||||
templateRecord.UnmarshalJSONField("content", templates)
|
templateRecord.UnmarshalJSONField("content", templates)
|
||||||
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
||||||
title = templates.NotifyTemplates[0].Title
|
subject = templates.NotifyTemplates[0].Title
|
||||||
content = templates.NotifyTemplates[0].Content
|
message = templates.NotifyTemplates[0].Content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,17 +80,17 @@ func buildMsg(records []*models.Record) *msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
countStr := strconv.Itoa(count)
|
countStr := strconv.Itoa(count)
|
||||||
domainStr := strings.Join(domains, ",")
|
domainStr := strings.Join(domains, ";")
|
||||||
|
|
||||||
title = strings.ReplaceAll(title, "{COUNT}", countStr)
|
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
||||||
title = strings.ReplaceAll(title, "{DOMAINS}", domainStr)
|
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
||||||
|
|
||||||
content = strings.ReplaceAll(content, "{COUNT}", countStr)
|
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
||||||
content = strings.ReplaceAll(content, "{DOMAINS}", domainStr)
|
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
||||||
|
|
||||||
// 返回消息
|
// 返回消息
|
||||||
return &msg{
|
return ¬ifyMessage{
|
||||||
subject: title,
|
Subject: subject,
|
||||||
message: content,
|
Message: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
internal/notify/factory.go
Normal file
66
internal/notify/factory.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
notifierBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
|
||||||
|
notifierDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
|
||||||
|
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||||
|
notifierLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
||||||
|
notifierServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
||||||
|
notifierTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||||
|
notifierWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) {
|
||||||
|
switch channel {
|
||||||
|
case domain.NotifyChannelEmail:
|
||||||
|
return notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
||||||
|
SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"),
|
||||||
|
SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"),
|
||||||
|
SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true),
|
||||||
|
Username: maps.GetValueOrDefaultAsString(channelConfig, "username", maps.GetValueAsString(channelConfig, "senderAddress")),
|
||||||
|
Password: maps.GetValueAsString(channelConfig, "password"),
|
||||||
|
SenderAddress: maps.GetValueAsString(channelConfig, "senderAddress"),
|
||||||
|
ReceiverAddress: maps.GetValueAsString(channelConfig, "receiverAddress"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelWebhook:
|
||||||
|
return notifierWebhook.New(¬ifierWebhook.WebhookNotifierConfig{
|
||||||
|
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelDingtalk:
|
||||||
|
return notifierDingTalk.New(¬ifierDingTalk.DingTalkNotifierConfig{
|
||||||
|
AccessToken: maps.GetValueAsString(channelConfig, "accessToken"),
|
||||||
|
Secret: maps.GetValueAsString(channelConfig, "secret"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelLark:
|
||||||
|
return notifierLark.New(¬ifierLark.LarkNotifierConfig{
|
||||||
|
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelTelegram:
|
||||||
|
return notifierTelegram.New(¬ifierTelegram.TelegramNotifierConfig{
|
||||||
|
ApiToken: maps.GetValueAsString(channelConfig, "apiToken"),
|
||||||
|
ChatId: maps.GetValueAsInt64(channelConfig, "chatId"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelServerChan:
|
||||||
|
return notifierServerChan.New(¬ifierServerChan.ServerChanNotifierConfig{
|
||||||
|
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||||
|
})
|
||||||
|
|
||||||
|
case domain.NotifyChannelBark:
|
||||||
|
return notifierBark.New(¬ifierBark.BarkNotifierConfig{
|
||||||
|
DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"),
|
||||||
|
ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unsupported notifier channel")
|
||||||
|
}
|
||||||
@@ -3,21 +3,16 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
|
||||||
notifyPackage "github.com/nikoksr/notify"
|
|
||||||
"github.com/nikoksr/notify/service/dingding"
|
|
||||||
"github.com/nikoksr/notify/service/http"
|
|
||||||
"github.com/nikoksr/notify/service/lark"
|
|
||||||
"github.com/nikoksr/notify/service/telegram"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Send(title, content string) error {
|
func SendToAllChannels(subject, message string) error {
|
||||||
// 获取所有的推送渠道
|
notifiers, err := getEnabledNotifiers()
|
||||||
notifiers, err := getNotifiers()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -25,136 +20,56 @@ func Send(title, content string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n := notifyPackage.New()
|
var eg errgroup.Group
|
||||||
// 添加推送渠道
|
for _, n := range notifiers {
|
||||||
n.UseServices(notifiers...)
|
if n == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 发送消息
|
eg.Go(func() error {
|
||||||
return n.Send(context.Background(), title, content)
|
_, err := n.Notify(context.Background(), subject, message)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendTestParam struct {
|
func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error {
|
||||||
Title string `json:"title"`
|
notifier, err := createNotifier(channel, channelConfig)
|
||||||
Content string `json:"content"`
|
|
||||||
Channel string `json:"channel"`
|
|
||||||
Conf map[string]any `json:"conf"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendTest(param *sendTestParam) error {
|
|
||||||
notifier, err := getNotifier(param.Channel, param.Conf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n := notifyPackage.New()
|
_, err = notifier.Notify(context.Background(), subject, message)
|
||||||
|
return err
|
||||||
// 添加推送渠道
|
|
||||||
n.UseServices(notifier)
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
return n.Send(context.Background(), param.Title, param.Content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNotifiers() ([]notifyPackage.Notifier, error) {
|
func getEnabledNotifiers() ([]notifier.Notifier, error) {
|
||||||
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
settings, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("find notifyChannels error: %w", err)
|
return nil, fmt.Errorf("find notifyChannels error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
notifiers := make([]notifyPackage.Notifier, 0)
|
|
||||||
|
|
||||||
rs := make(map[string]map[string]any)
|
rs := make(map[string]map[string]any)
|
||||||
|
if err := settings.UnmarshalJSONField("content", &rs); err != nil {
|
||||||
if err := resp.UnmarshalJSONField("content", &rs); err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
|
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifiers := make([]notifier.Notifier, 0)
|
||||||
for k, v := range rs {
|
for k, v := range rs {
|
||||||
|
if !maps.GetValueAsBool(v, "enabled") {
|
||||||
if !getBool(v, "enabled") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
notifier, err := getNotifier(k, v)
|
notifier, err := createNotifier(k, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
notifiers = append(notifiers, notifier)
|
notifiers = append(notifiers, notifier)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifiers, nil
|
return notifiers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) {
|
|
||||||
switch channel {
|
|
||||||
case domain.NotifyChannelTelegram:
|
|
||||||
temp := getTelegramNotifier(conf)
|
|
||||||
if temp == nil {
|
|
||||||
return nil, fmt.Errorf("telegram notifier config error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp, nil
|
|
||||||
case domain.NotifyChannelDingtalk:
|
|
||||||
return getDingTalkNotifier(conf), nil
|
|
||||||
case domain.NotifyChannelLark:
|
|
||||||
return getLarkNotifier(conf), nil
|
|
||||||
case domain.NotifyChannelWebhook:
|
|
||||||
return getWebhookNotifier(conf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("notifier not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
|
|
||||||
rs := http.New()
|
|
||||||
|
|
||||||
rs.AddReceiversURLs(getString(conf, "url"))
|
|
||||||
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier {
|
|
||||||
rs, err := telegram.New(getString(conf, "apiToken"))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
chatId := getString(conf, "chatId")
|
|
||||||
|
|
||||||
id, err := strconv.ParseInt(chatId, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rs.AddReceivers(id)
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier {
|
|
||||||
return dingding.New(&dingding.Config{
|
|
||||||
Token: getString(conf, "accessToken"),
|
|
||||||
Secret: getString(conf, "secret"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLarkNotifier(conf map[string]any) notifyPackage.Notifier {
|
|
||||||
return lark.NewWebhookService(getString(conf, "webhookUrl"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(conf map[string]any, key string) string {
|
|
||||||
if _, ok := conf[key]; !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf[key].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBool(conf map[string]any, key string) bool {
|
|
||||||
if _, ok := conf[key]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf[key].(bool)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,18 +29,13 @@ func NewNotifyService(settingRepo SettingRepository) *NotifyService {
|
|||||||
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
|
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
|
||||||
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get notify channels setting failed: %w", err)
|
return fmt.Errorf("failed to get notify channels settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conf, err := setting.GetChannelContent(req.Channel)
|
channelConfig, err := setting.GetChannelContent(req.Channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
|
return fmt.Errorf("failed to get notify channel \"%s\" config: %w", req.Channel, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendTest(&sendTestParam{
|
return SendToChannel(notifyTestTitle, notifyTestBody, req.Channel, channelConfig)
|
||||||
Title: notifyTestTitle,
|
|
||||||
Content: notifyTestBody,
|
|
||||||
Channel: req.Channel,
|
|
||||||
Conf: conf,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
internal/pkg/core/notifier/notifier.go
Normal file
23
internal/pkg/core/notifier/notifier.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package notifier
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// 表示定义消息通知器的抽象类型接口。
|
||||||
|
type Notifier interface {
|
||||||
|
// 发送通知。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - ctx:上下文。
|
||||||
|
// - subject:通知主题。
|
||||||
|
// - message:通知内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - res:发送结果。
|
||||||
|
// - err: 错误。
|
||||||
|
Notify(ctx context.Context, subject string, message string) (res *NotifyResult, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示通知发送结果的数据结构。
|
||||||
|
type NotifyResult struct {
|
||||||
|
NotificationData map[string]any `json:"notificationData,omitempty"`
|
||||||
|
}
|
||||||
48
internal/pkg/core/notifier/providers/bark/bark.go
Normal file
48
internal/pkg/core/notifier/providers/bark/bark.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package bark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nikoksr/notify"
|
||||||
|
"github.com/nikoksr/notify/service/bark"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BarkNotifierConfig struct {
|
||||||
|
ServerUrl string `json:"serverUrl"`
|
||||||
|
DeviceKey string `json:"deviceKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BarkNotifier struct {
|
||||||
|
config *BarkNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*BarkNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *BarkNotifierConfig) (*BarkNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BarkNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BarkNotifier) 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 ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
45
internal/pkg/core/notifier/providers/dingtalk/dingtalk.go
Normal file
45
internal/pkg/core/notifier/providers/dingtalk/dingtalk.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nikoksr/notify/service/dingding"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DingTalkNotifierConfig struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DingTalkNotifier struct {
|
||||||
|
config *DingTalkNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*DingTalkNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *DingTalkNotifierConfig) (*DingTalkNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DingTalkNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *DingTalkNotifier) 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 ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
95
internal/pkg/core/notifier/providers/email/email.go
Normal file
95
internal/pkg/core/notifier/providers/email/email.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/smtp"
|
||||||
|
|
||||||
|
"github.com/domodwyer/mailyak/v3"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EmailNotifierConfig struct {
|
||||||
|
SmtpHost string `json:"smtpHost"`
|
||||||
|
SmtpPort int32 `json:"smtpPort"`
|
||||||
|
SmtpTLS bool `json:"smtpTLS"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
SenderAddress string `json:"senderAddress"`
|
||||||
|
ReceiverAddress string `json:"receiverAddress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailNotifier struct {
|
||||||
|
config *EmailNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*EmailNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *EmailNotifierConfig) (*EmailNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &EmailNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EmailNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||||
|
var smtpAuth smtp.Auth
|
||||||
|
if n.config.Username != "" || n.config.Password != "" {
|
||||||
|
smtpAuth = smtp.PlainAuth("", n.config.Username, n.config.Password, n.config.SmtpHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
var smtpAddr string
|
||||||
|
if n.config.SmtpPort == 0 {
|
||||||
|
if n.config.SmtpTLS {
|
||||||
|
smtpAddr = fmt.Sprintf("%s:465", n.config.SmtpHost)
|
||||||
|
} else {
|
||||||
|
smtpAddr = fmt.Sprintf("%s:25", n.config.SmtpHost)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smtpAddr = fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
var yak *mailyak.MailYak
|
||||||
|
if n.config.SmtpTLS {
|
||||||
|
yak, err = mailyak.NewWithTLS(smtpAddr, smtpAuth, newTlsConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yak = mailyak.New(smtpAddr, smtpAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
yak.From(n.config.SenderAddress)
|
||||||
|
yak.To(n.config.ReceiverAddress)
|
||||||
|
yak.Subject(subject)
|
||||||
|
yak.Plain().Set(message)
|
||||||
|
|
||||||
|
err = yak.Send()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTlsConfig() *tls.Config {
|
||||||
|
var suiteIds []uint16
|
||||||
|
for _, suite := range tls.CipherSuites() {
|
||||||
|
suiteIds = append(suiteIds, suite.ID)
|
||||||
|
}
|
||||||
|
for _, suite := range tls.InsecureCipherSuites() {
|
||||||
|
suiteIds = append(suiteIds, suite.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为兼容国内部分低版本 TLS 的 SMTP 服务商
|
||||||
|
return &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
CipherSuites: suiteIds,
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/pkg/core/notifier/providers/email/email_test.go
Normal file
51
internal/pkg/core/notifier/providers/email/email_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package email_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \
|
||||||
|
CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" \
|
||||||
|
go test -v -run TestNotify email_test.go
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
smtpPort, err := strconv.ParseInt(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPPORT"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("invalid envvar: %+v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
smtpTLS, err := strconv.ParseBool(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPTLS"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("invalid envvar: %+v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
||||||
|
SmtpHost: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPHOST"),
|
||||||
|
SmtpPort: int32(smtpPort),
|
||||||
|
SmtpTLS: smtpTLS,
|
||||||
|
Username: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_USERNAME"),
|
||||||
|
Password: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_PASSWORD"),
|
||||||
|
SenderAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS"),
|
||||||
|
ReceiverAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("invalid envvar: %+v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("notify result: %v", res)
|
||||||
|
}
|
||||||
41
internal/pkg/core/notifier/providers/lark/lark.go
Normal file
41
internal/pkg/core/notifier/providers/lark/lark.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package lark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nikoksr/notify/service/lark"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LarkNotifierConfig struct {
|
||||||
|
WebhookUrl string `json:"webhookUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LarkNotifier struct {
|
||||||
|
config *LarkNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*LarkNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *LarkNotifierConfig) (*LarkNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LarkNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *LarkNotifier) 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 ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package serverchan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
notifyHttp "github.com/nikoksr/notify/service/http"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerChanNotifierConfig struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerChanNotifier struct {
|
||||||
|
config *ServerChanNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*ServerChanNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *ServerChanNotifierConfig) (*ServerChanNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ServerChanNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ServerChanNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||||
|
srv := notifyHttp.New()
|
||||||
|
|
||||||
|
srv.AddReceivers(¬ifyHttp.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 ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
47
internal/pkg/core/notifier/providers/telegram/telegram.go
Normal file
47
internal/pkg/core/notifier/providers/telegram/telegram.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nikoksr/notify/service/telegram"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TelegramNotifierConfig struct {
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
ChatId int64 `json:"chatId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramNotifier struct {
|
||||||
|
config *TelegramNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*TelegramNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *TelegramNotifierConfig) (*TelegramNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TelegramNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *TelegramNotifier) 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 ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
43
internal/pkg/core/notifier/providers/webhook/webhook.go
Normal file
43
internal/pkg/core/notifier/providers/webhook/webhook.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nikoksr/notify/service/http"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebhookNotifierConfig struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookNotifier struct {
|
||||||
|
config *WebhookNotifierConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*WebhookNotifier)(nil)
|
||||||
|
|
||||||
|
func New(config *WebhookNotifierConfig) (*WebhookNotifier, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebhookNotifier{
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *WebhookNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||||
|
srv := http.New()
|
||||||
|
|
||||||
|
srv.AddReceiversURLs(n.config.Url)
|
||||||
|
|
||||||
|
err = srv.Send(ctx, subject, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ¬ifier.NotifyResult{}, nil
|
||||||
|
}
|
||||||
167
internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go
Normal file
167
internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package aliyuncas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunCas "github.com/alibabacloud-go/cas-20200407/v3/client"
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunCASUploaderConfig struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunCASUploader struct {
|
||||||
|
config *AliyunCASUploaderConfig
|
||||||
|
sdkClient *aliyunCas.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*AliyunCASUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKeyId,
|
||||||
|
config.AccessKeySecret,
|
||||||
|
config.Region,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunCASUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询证书列表,避免重复上传
|
||||||
|
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listusercertificateorder
|
||||||
|
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail
|
||||||
|
listUserCertificateOrderPage := int64(1)
|
||||||
|
listUserCertificateOrderLimit := int64(50)
|
||||||
|
for {
|
||||||
|
listUserCertificateOrderReq := &aliyunCas.ListUserCertificateOrderRequest{
|
||||||
|
CurrentPage: tea.Int64(listUserCertificateOrderPage),
|
||||||
|
ShowSize: tea.Int64(listUserCertificateOrderLimit),
|
||||||
|
OrderType: tea.String("CERT"),
|
||||||
|
}
|
||||||
|
listUserCertificateOrderResp, err := u.sdkClient.ListUserCertificateOrder(listUserCertificateOrderReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.ListUserCertificateOrder'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listUserCertificateOrderResp.Body.CertificateOrderList != nil {
|
||||||
|
for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList {
|
||||||
|
if strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) {
|
||||||
|
getUserCertificateDetailReq := &aliyunCas.GetUserCertificateDetailRequest{
|
||||||
|
CertId: certDetail.CertificateId,
|
||||||
|
}
|
||||||
|
getUserCertificateDetailResp, err := u.sdkClient.GetUserCertificateDetail(getUserCertificateDetailReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.GetUserCertificateDetail'")
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSameCert bool
|
||||||
|
if *getUserCertificateDetailResp.Body.Cert == certPem {
|
||||||
|
isSameCert = true
|
||||||
|
} else {
|
||||||
|
oldCertX509, err := x509.ParseCertificateFromPEM(*getUserCertificateDetailResp.Body.Cert)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isSameCert = x509.EqualCertificate(certX509, oldCertX509)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)),
|
||||||
|
CertName: *certDetail.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listUserCertificateOrderPage += 1
|
||||||
|
if listUserCertificateOrderPage > 99 { // 避免死循环
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合阿里云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-uploadusercertificate
|
||||||
|
uploadUserCertificateReq := &aliyunCas.UploadUserCertificateRequest{
|
||||||
|
Name: tea.String(certName),
|
||||||
|
Cert: tea.String(certPem),
|
||||||
|
Key: tea.String(privkeyPem),
|
||||||
|
}
|
||||||
|
uploadUserCertificateResp, err := u.sdkClient.UploadUserCertificate(uploadUserCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.UploadUserCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = fmt.Sprintf("%d", tea.Int64Value(uploadUserCertificateResp.Body.CertId))
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "cn-hangzhou":
|
||||||
|
endpoint = "cas.aliyuncs.com"
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
|
client, err := aliyunCas.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
147
internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go
Normal file
147
internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package aliyunslb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunSLBUploaderConfig struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunSLBUploader struct {
|
||||||
|
config *AliyunSLBUploaderConfig
|
||||||
|
sdkClient *aliyunSlb.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*AliyunSLBUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKeyId,
|
||||||
|
config.AccessKeySecret,
|
||||||
|
config.Region,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunSLBUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询证书列表,避免重复上传
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeservercertificates
|
||||||
|
describeServerCertificatesReq := &aliyunSlb.DescribeServerCertificatesRequest{
|
||||||
|
RegionId: tea.String(u.config.Region),
|
||||||
|
}
|
||||||
|
describeServerCertificatesResp, err := u.sdkClient.DescribeServerCertificates(describeServerCertificatesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeServerCertificates'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil {
|
||||||
|
fingerprint := sha256.Sum256(certX509.Raw)
|
||||||
|
fingerprintHex := hex.EncodeToString(fingerprint[:])
|
||||||
|
for _, certDetail := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate {
|
||||||
|
isSameCert := *certDetail.IsAliCloudCertificate == 0 &&
|
||||||
|
strings.EqualFold(fingerprintHex, strings.ReplaceAll(*certDetail.Fingerprint, ":", "")) &&
|
||||||
|
strings.EqualFold(certX509.Subject.CommonName, *certDetail.CommonName)
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: *certDetail.ServerCertificateId,
|
||||||
|
CertName: *certDetail.ServerCertificateName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合阿里云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 去除证书和私钥内容中的空白行,以符合阿里云 API 要求
|
||||||
|
// REF: https://github.com/usual2970/certimate/issues/326
|
||||||
|
re := regexp.MustCompile(`(?m)^\s*$\n?`)
|
||||||
|
certPem = strings.TrimSpace(re.ReplaceAllString(certPem, ""))
|
||||||
|
privkeyPem = strings.TrimSpace(re.ReplaceAllString(privkeyPem, ""))
|
||||||
|
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
|
||||||
|
uploadServerCertificateReq := &aliyunSlb.UploadServerCertificateRequest{
|
||||||
|
RegionId: tea.String(u.config.Region),
|
||||||
|
ServerCertificateName: tea.String(certName),
|
||||||
|
ServerCertificate: tea.String(certPem),
|
||||||
|
PrivateKey: tea.String(privkeyPem),
|
||||||
|
}
|
||||||
|
uploadServerCertificateResp, err := u.sdkClient.UploadServerCertificate(uploadServerCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'slb.UploadServerCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = *uploadServerCertificateResp.Body.ServerCertificateId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
aConfig := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "cn-hangzhou":
|
||||||
|
case "cn-hangzhou-finance":
|
||||||
|
case "cn-shanghai-finance-1":
|
||||||
|
case "cn-shenzhen-finance-1":
|
||||||
|
endpoint = "slb.aliyuncs.com"
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
aConfig.Endpoint = tea.String(endpoint)
|
||||||
|
|
||||||
|
client, err := aliyunSlb.NewClient(aConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
68
internal/pkg/core/uploader/providers/dogecloud/dogecloud.go
Normal file
68
internal/pkg/core/uploader/providers/dogecloud/dogecloud.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package dogecloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
doge "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DogeCloudUploaderConfig struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DogeCloudUploader struct {
|
||||||
|
config *DogeCloudUploaderConfig
|
||||||
|
sdkClient *doge.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*DogeCloudUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *DogeCloudUploaderConfig) (*DogeCloudUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKey,
|
||||||
|
config.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DogeCloudUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DogeCloudUploader) 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)
|
||||||
|
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) (*doge.Client, error) {
|
||||||
|
client := doge.NewClient(accessKey, secretKey)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package huaweicloudelb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
|
hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
|
||||||
|
hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
|
||||||
|
hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
|
||||||
|
hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
|
||||||
|
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
||||||
|
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudELBUploaderConfig struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HuaweiCloudELBUploader struct {
|
||||||
|
config *HuaweiCloudELBUploaderConfig
|
||||||
|
sdkClient *hcElb.ElbClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*HuaweiCloudELBUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("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: %w")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HuaweiCloudELBUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
newCert, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历查询已有证书,避免重复上传
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ListCertificates.html
|
||||||
|
listCertificatesPage := 1
|
||||||
|
listCertificatesLimit := int32(2000)
|
||||||
|
var listCertificatesMarker *string = nil
|
||||||
|
for {
|
||||||
|
listCertificatesReq := &hcElbModel.ListCertificatesRequest{
|
||||||
|
Limit: cast.Int32Ptr(listCertificatesLimit),
|
||||||
|
Marker: listCertificatesMarker,
|
||||||
|
Type: &[]string{"server"},
|
||||||
|
}
|
||||||
|
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertificatesResp.Certificates != nil {
|
||||||
|
for _, certDetail := range *listCertificatesResp.Certificates {
|
||||||
|
var isSameCert bool
|
||||||
|
if certDetail.Certificate == certPem {
|
||||||
|
isSameCert = true
|
||||||
|
} else {
|
||||||
|
cert, err := x509.ParseCertificateFromPEM(certDetail.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isSameCert = x509.EqualCertificate(cert, newCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certDetail.Id,
|
||||||
|
CertName: certDetail.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker
|
||||||
|
listCertificatesPage++
|
||||||
|
if listCertificatesPage >= 9 { // 避免死循环
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目 ID
|
||||||
|
// REF: https://support.huaweicloud.com/api-iam/iam_06_0001.html
|
||||||
|
projectId, err := getSdkProjectId(u.config.AccessKeyId, u.config.SecretAccessKey, u.config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get SDK project id")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合华为云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 创建新证书
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/CreateCertificate.html
|
||||||
|
createCertificateReq := &hcElbModel.CreateCertificateRequest{
|
||||||
|
Body: &hcElbModel.CreateCertificateRequestBody{
|
||||||
|
Certificate: &hcElbModel.CreateCertificateOption{
|
||||||
|
ProjectId: cast.StringPtr(projectId),
|
||||||
|
Name: cast.StringPtr(certName),
|
||||||
|
Certificate: cast.StringPtr(certPem),
|
||||||
|
PrivateKey: cast.StringPtr(privkeyPem),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'elb.CreateCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = createCertificateResp.Certificate.Id
|
||||||
|
certName = createCertificateResp.Certificate.Name
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := basic.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcElbRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcElb.ElbClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hcElb.NewElbClient(hcClient)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // IAM 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := global.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcIamRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcIam.IamClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hcIam.NewIamClient(hcClient)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &hcIamModel.KeystoneListProjectsRequest{
|
||||||
|
Name: ®ion,
|
||||||
|
}
|
||||||
|
response, err := client.KeystoneListProjects(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if response.Projects == nil || len(*response.Projects) == 0 {
|
||||||
|
return "", errors.New("no project found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*response.Projects)[0].Id, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package huaweicloudscm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
||||||
|
hcScm "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3"
|
||||||
|
hcScmModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/model"
|
||||||
|
hcScmRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudSCMUploaderConfig struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HuaweiCloudSCMUploader struct {
|
||||||
|
config *HuaweiCloudSCMUploaderConfig
|
||||||
|
sdkClient *hcScm.ScmClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*HuaweiCloudSCMUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HuaweiCloudSCMUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历查询已有证书,避免重复上传
|
||||||
|
// REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html
|
||||||
|
// REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html
|
||||||
|
listCertificatesPage := 1
|
||||||
|
listCertificatesLimit := int32(50)
|
||||||
|
listCertificatesOffset := int32(0)
|
||||||
|
for {
|
||||||
|
listCertificatesReq := &hcScmModel.ListCertificatesRequest{
|
||||||
|
Limit: cast.Int32Ptr(listCertificatesLimit),
|
||||||
|
Offset: cast.Int32Ptr(listCertificatesOffset),
|
||||||
|
SortDir: cast.StringPtr("DESC"),
|
||||||
|
SortKey: cast.StringPtr("certExpiredTime"),
|
||||||
|
}
|
||||||
|
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ListCertificates'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertificatesResp.Certificates != nil {
|
||||||
|
for _, certDetail := range *listCertificatesResp.Certificates {
|
||||||
|
exportCertificateReq := &hcScmModel.ExportCertificateRequest{
|
||||||
|
CertificateId: certDetail.Id,
|
||||||
|
}
|
||||||
|
exportCertificateResp, err := u.sdkClient.ExportCertificate(exportCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ExportCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSameCert bool
|
||||||
|
if *exportCertificateResp.Certificate == certPem {
|
||||||
|
isSameCert = true
|
||||||
|
} else {
|
||||||
|
cert, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isSameCert = x509.EqualCertificate(certX509, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certDetail.Id,
|
||||||
|
CertName: certDetail.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listCertificatesOffset += listCertificatesLimit
|
||||||
|
listCertificatesPage += 1
|
||||||
|
if listCertificatesPage > 99 { // 避免死循环
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合华为云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://support.huaweicloud.com/api-ccm/ImportCertificate.html
|
||||||
|
importCertificateReq := &hcScmModel.ImportCertificateRequest{
|
||||||
|
Body: &hcScmModel.ImportCertificateRequestBody{
|
||||||
|
Name: certName,
|
||||||
|
Certificate: certPem,
|
||||||
|
PrivateKey: privkeyPem,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
importCertificateResp, err := u.sdkClient.ImportCertificate(importCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'scm.ImportCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = *importCertificateResp.CertificateId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcScm.ScmClient, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // SCM 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := basic.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcScmRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcScm.ScmClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hcScm.NewScmClient(hcClient)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package qiniusslcert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QiniuSSLCertUploaderConfig struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QiniuSSLCertUploader struct {
|
||||||
|
config *QiniuSSLCertUploaderConfig
|
||||||
|
sdkClient *qiniuEx.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*QiniuSSLCertUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKey,
|
||||||
|
config.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &QiniuSSLCertUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合七牛云命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
||||||
|
uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = uploadSslCertResp.CertID
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) {
|
||||||
|
credential := auth.New(accessKey, secretKey)
|
||||||
|
client := qiniuEx.NewClient(credential)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package tencentcloudssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
|
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudSSLUploaderConfig struct {
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudSSLUploader struct {
|
||||||
|
config *TencentCloudSSLUploaderConfig
|
||||||
|
sdkClient *tcSsl.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*TencentCloudSSLUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.SecretId,
|
||||||
|
config.SecretKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudSSLUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TencentCloudSSLUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/41665
|
||||||
|
uploadCertificateReq := tcSsl.NewUploadCertificateRequest()
|
||||||
|
uploadCertificateReq.CertificatePublicKey = common.StringPtr(certPem)
|
||||||
|
uploadCertificateReq.CertificatePrivateKey = common.StringPtr(privkeyPem)
|
||||||
|
uploadCertificateReq.Repeatable = common.BoolPtr(false)
|
||||||
|
uploadCertificateResp, err := u.sdkClient.UploadCertificate(uploadCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.UploadCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId := *uploadCertificateResp.Response.CertificateId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(secretId, secretKey string) (*tcSsl.Client, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
client, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
27
internal/pkg/core/uploader/uploader.go
Normal file
27
internal/pkg/core/uploader/uploader.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package uploader
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// 表示定义证书上传器的抽象类型接口。
|
||||||
|
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
|
||||||
|
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
|
||||||
|
type Uploader interface {
|
||||||
|
// 上传证书。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - 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"`
|
||||||
|
CertData map[string]any `json:"certData,omitempty"`
|
||||||
|
}
|
||||||
25
internal/pkg/utils/cast/cast.go
Normal file
25
internal/pkg/utils/cast/cast.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package cast
|
||||||
|
|
||||||
|
func Int32Ptr(i int32) *int32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int64Ptr(i int64) *int64 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func UInt32Ptr(i uint32) *uint32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func UInt64Ptr(i uint64) *uint64 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
52
internal/pkg/utils/fs/fs.go
Normal file
52
internal/pkg/utils/fs/fs.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 与 [WriteFile] 类似,但写入的是字符串内容。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - path: 文件路径。
|
||||||
|
// - content: 文件内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 错误。
|
||||||
|
func WriteFileString(path string, content string) error {
|
||||||
|
return WriteFile(path, []byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将数据写入指定路径的文件。
|
||||||
|
// 如果目录不存在,将会递归创建目录。
|
||||||
|
// 如果文件不存在,将会创建该文件;如果文件已存在,将会覆盖原有内容。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - path: 文件路径。
|
||||||
|
// - data: 文件数据字节数组。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 错误。
|
||||||
|
func WriteFile(path string, data []byte) error {
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
|
err := os.MkdirAll(dir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create file")
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to write file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
164
internal/pkg/utils/maps/maps.go
Normal file
164
internal/pkg/utils/maps/maps.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package maps
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// 以字符串形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回空字符串。
|
||||||
|
func GetValueAsString(dict map[string]any, key string) string {
|
||||||
|
return GetValueOrDefaultAsString(dict, key, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以字符串形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回默认值。
|
||||||
|
func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string {
|
||||||
|
if dict == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := dict[key]; ok {
|
||||||
|
if result, ok := value.(string); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 32 位整数形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回 0。
|
||||||
|
func GetValueAsInt32(dict map[string]any, key string) int32 {
|
||||||
|
return GetValueOrDefaultAsInt32(dict, key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 32 位整数形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回默认值。
|
||||||
|
func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 {
|
||||||
|
if dict == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := dict[key]; ok {
|
||||||
|
if result, ok := value.(int32); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容字符串类型的值
|
||||||
|
if str, ok := value.(string); ok {
|
||||||
|
if result, err := strconv.ParseInt(str, 10, 32); err == nil {
|
||||||
|
return int32(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 64 位整数形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回 0。
|
||||||
|
func GetValueAsInt64(dict map[string]any, key string) int64 {
|
||||||
|
return GetValueOrDefaultAsInt64(dict, key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以 64 位整数形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回默认值。
|
||||||
|
func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 {
|
||||||
|
if dict == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := dict[key]; ok {
|
||||||
|
if result, ok := value.(int64); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容字符串类型的值
|
||||||
|
if str, ok := value.(string); ok {
|
||||||
|
if result, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以布尔形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回 false。
|
||||||
|
func GetValueAsBool(dict map[string]any, key string) bool {
|
||||||
|
return GetValueOrDefaultAsBool(dict, key, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以布尔形式从字典中获取指定键的值。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - dict: 字典。
|
||||||
|
// - key: 键。
|
||||||
|
// - defaultValue: 默认值。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回默认值。
|
||||||
|
func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool) bool {
|
||||||
|
if dict == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := dict[key]; ok {
|
||||||
|
if result, ok := value.(bool); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容字符串类型的值
|
||||||
|
if str, ok := value.(string); ok {
|
||||||
|
if result, err := strconv.ParseBool(str); err == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
22
internal/pkg/utils/x509/common.go
Normal file
22
internal/pkg/utils/x509/common.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。
|
||||||
|
// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - a: 待比较的第一个 x509.Certificate 对象。
|
||||||
|
// - b: 待比较的第二个 x509.Certificate 对象。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - 是否相同。
|
||||||
|
func EqualCertificate(a, b *x509.Certificate) bool {
|
||||||
|
return string(a.Signature) == string(b.Signature) &&
|
||||||
|
a.SignatureAlgorithm == b.SignatureAlgorithm &&
|
||||||
|
a.SerialNumber.String() == b.SerialNumber.String() &&
|
||||||
|
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
|
||||||
|
a.Subject.SerialNumber == b.Subject.SerialNumber
|
||||||
|
}
|
||||||
31
internal/pkg/utils/x509/converter.go
Normal file
31
internal/pkg/utils/x509/converter.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkey: ecdsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
// - err: 错误。
|
||||||
|
func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) {
|
||||||
|
data, err := x509.MarshalECPrivateKey(privkey)
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Wrap(err, "failed to marshal EC private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
block := &pem.Block{
|
||||||
|
Type: "EC PRIVATE KEY",
|
||||||
|
Bytes: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pem.EncodeToMemory(block)), nil
|
||||||
|
}
|
||||||
83
internal/pkg/utils/x509/parser.go
Normal file
83
internal/pkg/utils/x509/parser.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - certPem: 证书 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - cert: x509.Certificate 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParseCertificateFromPEM(certPem string) (cert *x509.Certificate, err error) {
|
||||||
|
pemData := []byte(certPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 PEM 编码的私钥字符串解析并返回一个 ecdsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkey: ecdsa.PrivateKey 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParseECPrivateKeyFromPEM(privkeyPem string) (privkey *ecdsa.PrivateKey, err error) {
|
||||||
|
pemData := []byte(privkeyPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err = x509.ParseECPrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return privkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 PEM 编码的私钥字符串解析并返回一个 rsa.PrivateKey 对象。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - privkey: rsa.PrivateKey 对象。
|
||||||
|
// - err: 错误。
|
||||||
|
func ParsePKCS1PrivateKeyFromPEM(privkeyPem string) (privkey *rsa.PrivateKey, err error) {
|
||||||
|
pemData := []byte(privkeyPem)
|
||||||
|
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return privkey, nil
|
||||||
|
}
|
||||||
183
internal/pkg/vendors/dogecloud-sdk/client.go
vendored
Normal file
183
internal/pkg/vendors/dogecloud-sdk/client.go
vendored
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package dogecloudsdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dogeHost = "https://api.dogecloud.com"
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
accessKey string
|
||||||
|
secretKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(accessKey, secretKey string) *Client {
|
||||||
|
return &Client{accessKey: accessKey, secretKey: secretKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UploadCdnCert(note, cert, private string) (*UploadCdnCertResponse, error) {
|
||||||
|
req := &UploadCdnCertRequest{
|
||||||
|
Note: note,
|
||||||
|
Certificate: cert,
|
||||||
|
PrivateKey: private,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBts, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMap := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(reqBts, &reqMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/upload.json", reqMap, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &UploadCdnCertResponse{}
|
||||||
|
err = json.Unmarshal(respBts, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BindCdnCertWithDomain(certId int64, domain string) (*BindCdnCertResponse, error) {
|
||||||
|
req := &BindCdnCertRequest{
|
||||||
|
CertId: certId,
|
||||||
|
Domain: &domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBts, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMap := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(reqBts, &reqMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &BindCdnCertResponse{}
|
||||||
|
err = json.Unmarshal(respBts, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BindCdnCertWithDomainId(certId int64, domainId int64) (*BindCdnCertResponse, error) {
|
||||||
|
req := &BindCdnCertRequest{
|
||||||
|
CertId: certId,
|
||||||
|
DomainId: &domainId,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBts, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMap := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(reqBts, &reqMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &BindCdnCertResponse{}
|
||||||
|
err = json.Unmarshal(respBts, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用多吉云的 API。
|
||||||
|
// https://docs.dogecloud.com/cdn/api-access-token?id=go
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - method:GET 或 POST
|
||||||
|
// - path:是调用的 API 接口地址,包含 URL 请求参数 QueryString,例如:/console/vfetch/add.json?url=xxx&a=1&b=2
|
||||||
|
// - data:POST 的数据,对象,例如 {a: 1, b: 2},传递此参数表示不是 GET 请求而是 POST 请求
|
||||||
|
// - jsonMode:数据 data 是否以 JSON 格式请求,默认为 false 则使用表单形式(a=1&b=2)
|
||||||
|
func (c *Client) sendReq(method string, path string, data map[string]interface{}, jsonMode bool) ([]byte, error) {
|
||||||
|
body := ""
|
||||||
|
mime := ""
|
||||||
|
if jsonMode {
|
||||||
|
_body, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body = string(_body)
|
||||||
|
mime = "application/json"
|
||||||
|
} else {
|
||||||
|
values := url.Values{}
|
||||||
|
for k, v := range data {
|
||||||
|
values.Set(k, v.(string))
|
||||||
|
}
|
||||||
|
body = values.Encode()
|
||||||
|
mime = "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
signStr := "/" + path + "\n" + body
|
||||||
|
hmacObj := hmac.New(sha1.New, []byte(c.secretKey))
|
||||||
|
hmacObj.Write([]byte(signStr))
|
||||||
|
sign := hex.EncodeToString(hmacObj.Sum(nil))
|
||||||
|
auth := fmt.Sprintf("TOKEN %s:%s", c.accessKey, sign)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", dogeHost, path), strings.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", mime)
|
||||||
|
req.Header.Add("Authorization", auth)
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
r, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
31
internal/pkg/vendors/dogecloud-sdk/models.go
vendored
Normal file
31
internal/pkg/vendors/dogecloud-sdk/models.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package dogecloudsdk
|
||||||
|
|
||||||
|
type BaseResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Message *string `json:"msg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadCdnCertRequest struct {
|
||||||
|
Note string `json:"note"`
|
||||||
|
Certificate string `json:"cert"`
|
||||||
|
PrivateKey string `json:"private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadCdnCertResponseData struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadCdnCertResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
Data *UploadCdnCertResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindCdnCertRequest struct {
|
||||||
|
CertId int64 `json:"id"`
|
||||||
|
DomainId *int64 `json:"did,omitempty"`
|
||||||
|
Domain *string `json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindCdnCertResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
}
|
||||||
26
internal/pkg/vendors/huaweicloud-cdn-sdk/client.go
vendored
Normal file
26
internal/pkg/vendors/huaweicloud-cdn-sdk/client.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package huaweicloudcdnsdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core"
|
||||||
|
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
hcCdn.CdnClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(hcClient *core.HcHttpClient) *Client {
|
||||||
|
return &Client{
|
||||||
|
CdnClient: *hcCdn.NewCdnClient(hcClient),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UploadDomainMultiCertificatesEx(request *UpdateDomainMultiCertificatesExRequest) (*UpdateDomainMultiCertificatesExResponse, error) {
|
||||||
|
requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates()
|
||||||
|
|
||||||
|
if resp, err := c.HcClient.Sync(request, requestDef); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return resp.(*UpdateDomainMultiCertificatesExResponse), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
62
internal/pkg/vendors/huaweicloud-cdn-sdk/models.go
vendored
Normal file
62
internal/pkg/vendors/huaweicloud-cdn-sdk/models.go
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package huaweicloudcdnsdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequestBodyContent struct {
|
||||||
|
hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
|
||||||
|
|
||||||
|
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求,可能需要等之后 SDK 更新。
|
||||||
|
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequestBody struct {
|
||||||
|
Https *UpdateDomainMultiCertificatesExRequestBodyContent `json:"https,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExRequest struct {
|
||||||
|
Body *UpdateDomainMultiCertificatesExRequestBody `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDomainMultiCertificatesExResponse struct {
|
||||||
|
hcCdnModel.UpdateDomainMultiCertificatesResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UpdateDomainMultiCertificatesExRequestBodyContent) MergeConfig(src *hcCdnModel.ConfigsGetBody) *UpdateDomainMultiCertificatesExRequestBodyContent {
|
||||||
|
if src == nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去。
|
||||||
|
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化。
|
||||||
|
|
||||||
|
if *src.OriginProtocol == "follow" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(1)
|
||||||
|
} else if *src.OriginProtocol == "http" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(2)
|
||||||
|
} else if *src.OriginProtocol == "https" {
|
||||||
|
m.AccessOriginWay = cast.Int32Ptr(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.ForceRedirect != nil {
|
||||||
|
m.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
|
||||||
|
|
||||||
|
if src.ForceRedirect.Status == "on" {
|
||||||
|
m.ForceRedirectConfig.Switch = 1
|
||||||
|
m.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
|
||||||
|
} else {
|
||||||
|
m.ForceRedirectConfig.Switch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Https != nil {
|
||||||
|
if *src.Https.Http2Status == "on" {
|
||||||
|
m.Http2 = cast.Int32Ptr(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
160
internal/pkg/vendors/qiniu-sdk/client.go
vendored
Normal file
160
internal/pkg/vendors/qiniu-sdk/client.go
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package qiniusdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const qiniuHost = "https://api.qiniu.com"
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
mac *auth.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(mac *auth.Credentials) *Client {
|
||||||
|
if mac == nil {
|
||||||
|
mac = auth.Default()
|
||||||
|
}
|
||||||
|
return &Client{mac: mac}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetDomainInfo(domain string) (*GetDomainInfoResponse, error) {
|
||||||
|
respBytes, err := c.sendReq(http.MethodGet, fmt.Sprintf("domain/%s", domain), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &GetDomainInfoResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) {
|
||||||
|
req := &ModifyDomainHttpsConfRequest{
|
||||||
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
|
CertID: certId,
|
||||||
|
ForceHttps: forceHttps,
|
||||||
|
Http2Enable: http2Enable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/httpsconf", domain), bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &ModifyDomainHttpsConfResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enable bool) (*EnableDomainHttpsResponse, error) {
|
||||||
|
req := &EnableDomainHttpsRequest{
|
||||||
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
|
CertID: certId,
|
||||||
|
ForceHttps: forceHttps,
|
||||||
|
Http2Enable: http2Enable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/sslize", domain), bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &EnableDomainHttpsResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UploadSslCert(name, commonName, certificate, privateKey string) (*UploadSslCertResponse, error) {
|
||||||
|
req := &UploadSslCertRequest{
|
||||||
|
Name: name,
|
||||||
|
CommonName: commonName,
|
||||||
|
Certificate: certificate,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := c.sendReq(http.MethodPost, "sslcert", bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &UploadSslCertResponse{}
|
||||||
|
err = json.Unmarshal(respBytes, resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("qiniu api error, code: %d, error: %s", *resp.Code, *resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendReq(method string, path string, body io.Reader) ([]byte, error) {
|
||||||
|
req := xhttp.BuildReq(fmt.Sprintf("%s/%s", qiniuHost, path), method, body, map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.mac.AddToken(auth.TokenQBox, req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := xhttp.ToRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer respBody.Close()
|
||||||
|
|
||||||
|
res, err := io.ReadAll(respBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
54
internal/pkg/vendors/qiniu-sdk/models.go
vendored
Normal file
54
internal/pkg/vendors/qiniu-sdk/models.go
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package qiniusdk
|
||||||
|
|
||||||
|
type BaseResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadSslCertRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CommonName string `json:"common_name"`
|
||||||
|
Certificate string `json:"ca"`
|
||||||
|
PrivateKey string `json:"pri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadSslCertResponse struct {
|
||||||
|
*BaseResponse
|
||||||
|
CertID string `json:"certID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainInfoHttpsData struct {
|
||||||
|
CertID string `json:"certId"`
|
||||||
|
ForceHttps bool `json:"forceHttps"`
|
||||||
|
Http2Enable bool `json:"http2Enable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDomainInfoResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CName string `json:"cname"`
|
||||||
|
Https *DomainInfoHttpsData `json:"https"`
|
||||||
|
PareDomain string `json:"pareDomain"`
|
||||||
|
OperationType string `json:"operationType"`
|
||||||
|
OperatingState string `json:"operatingState"`
|
||||||
|
OperatingStateDesc string `json:"operatingStateDesc"`
|
||||||
|
CreateAt string `json:"createAt"`
|
||||||
|
ModifyAt string `json:"modifyAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyDomainHttpsConfRequest struct {
|
||||||
|
DomainInfoHttpsData
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyDomainHttpsConfResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableDomainHttpsRequest struct {
|
||||||
|
DomainInfoHttpsData
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableDomainHttpsResponse struct {
|
||||||
|
BaseResponse
|
||||||
|
}
|
||||||
71
internal/repository/acme_account.go
Normal file
71
internal/repository/acme_account.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AcmeAccountRepository struct{}
|
||||||
|
|
||||||
|
func NewAcmeAccountRepository() *AcmeAccountRepository {
|
||||||
|
return &AcmeAccountRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var g singleflight.Group
|
||||||
|
|
||||||
|
func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error) {
|
||||||
|
resp, err, _ := g.Do(fmt.Sprintf("acme_account_%s_%s", ca, email), func() (interface{}, error) {
|
||||||
|
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("acme_accounts", "ca={:ca} && email={:email}", dbx.Params{"ca": ca, "email": email})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, fmt.Errorf("acme account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
record, ok := resp.(*models.Record)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("acme account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := ®istration.Resource{}
|
||||||
|
if err := record.UnmarshalJSONField("resource", resource); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.AcmeAccount{
|
||||||
|
Id: record.GetString("id"),
|
||||||
|
Ca: record.GetString("ca"),
|
||||||
|
Email: record.GetString("email"),
|
||||||
|
Key: record.GetString("key"),
|
||||||
|
Resource: resource,
|
||||||
|
Created: record.GetTime("created"),
|
||||||
|
Updated: record.GetTime("updated"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AcmeAccountRepository) Save(ca, email, key string, resource *registration.Resource) error {
|
||||||
|
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("acme_accounts")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := models.NewRecord(collection)
|
||||||
|
record.Set("ca", ca)
|
||||||
|
record.Set("email", email)
|
||||||
|
record.Set("key", key)
|
||||||
|
record.Set("resource", resource)
|
||||||
|
return app.GetApp().Dao().Save(record)
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RandStr 随机生成指定长度字符串
|
// Deprecated: this will be removed in the future.
|
||||||
|
// 随机生成指定长度字符串
|
||||||
func RandStr(n int) string {
|
func RandStr(n int) string {
|
||||||
seed := time.Now().UnixNano()
|
seed := time.Now().UnixNano()
|
||||||
source := rand.NewSource(seed)
|
source := rand.NewSource(seed)
|
||||||
|
|||||||
12
main.go
12
main.go
@@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/usual2970/certimate/ui"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/usual2970/certimate/internal/domains"
|
"github.com/usual2970/certimate/internal/domains"
|
||||||
"github.com/usual2970/certimate/internal/routes"
|
"github.com/usual2970/certimate/internal/routes"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
"github.com/usual2970/certimate/ui"
|
||||||
|
|
||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
)
|
)
|
||||||
@@ -25,6 +26,12 @@ func main() {
|
|||||||
|
|
||||||
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
||||||
|
|
||||||
|
// 获取启动命令中的http参数
|
||||||
|
var httpFlag string
|
||||||
|
flag.StringVar(&httpFlag, "http", "127.0.0.1:8090", "HTTP server address")
|
||||||
|
// "serve"影响解析
|
||||||
|
_ = flag.CommandLine.Parse(os.Args[2:])
|
||||||
|
|
||||||
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
||||||
// enable auto creation of migration files when making collection changes in the Admin UI
|
// enable auto creation of migration files when making collection changes in the Admin UI
|
||||||
// (the isGoRun check is to enable it only during development)
|
// (the isGoRun check is to enable it only during development)
|
||||||
@@ -47,6 +54,9 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer log.Println("Exit!")
|
||||||
|
log.Printf("Visit the website: http://%s", httpFlag)
|
||||||
|
|
||||||
if err := app.Start(); err != nil {
|
if err := app.Start(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_targetType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "srybpixz",
|
|
||||||
"name": "targetType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun-oss",
|
|
||||||
"aliyun-cdn",
|
|
||||||
"aliyun-dcdn",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"tencent-cdn",
|
|
||||||
"qiniu-cdn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_targetType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_targetType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_targetType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "srybpixz",
|
|
||||||
"name": "targetType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun-oss",
|
|
||||||
"aliyun-cdn",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"tencent-cdn",
|
|
||||||
"qiniu-cdn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_targetType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_targetType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,694 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
jsonData := `[
|
|
||||||
{
|
|
||||||
"id": "z3p974ainxjqlvs",
|
|
||||||
"created": "2024-07-29 10:02:48.334Z",
|
|
||||||
"updated": "2024-09-18 14:23:22.359Z",
|
|
||||||
"name": "domains",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iuaerpl2",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ukkhuw85",
|
|
||||||
"name": "email",
|
|
||||||
"type": "email",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"exceptDomains": null,
|
|
||||||
"onlyDomains": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "v98eebqq",
|
|
||||||
"name": "crontab",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "alc8e9ow",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "topsc9bj",
|
|
||||||
"name": "certUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "vixgq072",
|
|
||||||
"name": "certStableUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g3a3sza5",
|
|
||||||
"name": "privateKey",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "gr6iouny",
|
|
||||||
"name": "certificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "tk6vnrmn",
|
|
||||||
"name": "issuerCertificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "sjo6ibse",
|
|
||||||
"name": "csr",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "x03n1bkj",
|
|
||||||
"name": "expiredAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "srybpixz",
|
|
||||||
"name": "targetType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun-oss",
|
|
||||||
"aliyun-cdn",
|
|
||||||
"aliyun-dcdn",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"tencent-cdn",
|
|
||||||
"qiniu-cdn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xy7yk0mb",
|
|
||||||
"name": "targetAccess",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "6jqeyggw",
|
|
||||||
"name": "enabled",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hdsjcchf",
|
|
||||||
"name": "deployed",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "aiya3rev",
|
|
||||||
"name": "rightnow",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ixznmhzc",
|
|
||||||
"name": "lastDeployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ghtlkn5j",
|
|
||||||
"name": "lastDeployment",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "0a1o4e6sstp694f",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "zfnyj9he",
|
|
||||||
"name": "variables",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1bspzuku",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g65gfh7a",
|
|
||||||
"name": "nameservers",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4yzbv8urny5ja1e",
|
|
||||||
"created": "2024-07-29 10:04:39.685Z",
|
|
||||||
"updated": "2024-09-17 00:53:25.859Z",
|
|
||||||
"name": "access",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "geeur58v",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iql7jpwx",
|
|
||||||
"name": "config",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"cloudflare",
|
|
||||||
"qiniu",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lr33hiwg",
|
|
||||||
"name": "deleted",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hsxcnlvd",
|
|
||||||
"name": "usage",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"apply",
|
|
||||||
"deploy",
|
|
||||||
"all"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "c8egzzwj",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0a1o4e6sstp694f",
|
|
||||||
"created": "2024-07-30 06:30:27.801Z",
|
|
||||||
"updated": "2024-09-17 00:53:25.859Z",
|
|
||||||
"name": "deployments",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "farvlzk7",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "z3p974ainxjqlvs",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "jx5f69i3",
|
|
||||||
"name": "log",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "qbxdtg9q",
|
|
||||||
"name": "phase",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"check",
|
|
||||||
"apply",
|
|
||||||
"deploy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "rglrp1hz",
|
|
||||||
"name": "phaseSuccess",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lt1g1blu",
|
|
||||||
"name": "deployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "_pb_users_auth_",
|
|
||||||
"created": "2024-09-12 13:09:54.234Z",
|
|
||||||
"updated": "2024-09-17 00:53:25.859Z",
|
|
||||||
"name": "users",
|
|
||||||
"type": "auth",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_name",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_avatar",
|
|
||||||
"name": "avatar",
|
|
||||||
"type": "file",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"mimeTypes": [
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"image/svg+xml",
|
|
||||||
"image/gif",
|
|
||||||
"image/webp"
|
|
||||||
],
|
|
||||||
"thumbs": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"maxSize": 5242880,
|
|
||||||
"protected": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": "id = @request.auth.id",
|
|
||||||
"viewRule": "id = @request.auth.id",
|
|
||||||
"createRule": "",
|
|
||||||
"updateRule": "id = @request.auth.id",
|
|
||||||
"deleteRule": "id = @request.auth.id",
|
|
||||||
"options": {
|
|
||||||
"allowEmailAuth": true,
|
|
||||||
"allowOAuth2Auth": true,
|
|
||||||
"allowUsernameAuth": true,
|
|
||||||
"exceptEmailDomains": null,
|
|
||||||
"manageRule": null,
|
|
||||||
"minPasswordLength": 8,
|
|
||||||
"onlyEmailDomains": null,
|
|
||||||
"onlyVerified": false,
|
|
||||||
"requireEmail": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dy6ccjb60spfy6p",
|
|
||||||
"created": "2024-09-12 23:12:21.677Z",
|
|
||||||
"updated": "2024-09-17 00:53:25.860Z",
|
|
||||||
"name": "settings",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1tcmdsdf",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "f9wyhypi",
|
|
||||||
"name": "content",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "teolp9pl72dxlxq",
|
|
||||||
"created": "2024-09-13 12:51:05.611Z",
|
|
||||||
"updated": "2024-09-17 00:53:25.860Z",
|
|
||||||
"name": "access_groups",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "7sajiv6i",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xp8admif",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": null,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
]`
|
|
||||||
|
|
||||||
collections := []*models.Collection{}
|
|
||||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return daos.New(db).ImportCollections(collections, true, nil)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,704 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
jsonData := `[
|
|
||||||
{
|
|
||||||
"id": "z3p974ainxjqlvs",
|
|
||||||
"created": "2024-07-29 10:02:48.334Z",
|
|
||||||
"updated": "2024-09-19 00:27:35.936Z",
|
|
||||||
"name": "domains",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iuaerpl2",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ukkhuw85",
|
|
||||||
"name": "email",
|
|
||||||
"type": "email",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"exceptDomains": null,
|
|
||||||
"onlyDomains": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "v98eebqq",
|
|
||||||
"name": "crontab",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "alc8e9ow",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "topsc9bj",
|
|
||||||
"name": "certUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "vixgq072",
|
|
||||||
"name": "certStableUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g3a3sza5",
|
|
||||||
"name": "privateKey",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "gr6iouny",
|
|
||||||
"name": "certificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "tk6vnrmn",
|
|
||||||
"name": "issuerCertificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "sjo6ibse",
|
|
||||||
"name": "csr",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "x03n1bkj",
|
|
||||||
"name": "expiredAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "srybpixz",
|
|
||||||
"name": "targetType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun-oss",
|
|
||||||
"aliyun-cdn",
|
|
||||||
"aliyun-dcdn",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"tencent-cdn",
|
|
||||||
"qiniu-cdn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xy7yk0mb",
|
|
||||||
"name": "targetAccess",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "6jqeyggw",
|
|
||||||
"name": "enabled",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hdsjcchf",
|
|
||||||
"name": "deployed",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "aiya3rev",
|
|
||||||
"name": "rightnow",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ixznmhzc",
|
|
||||||
"name": "lastDeployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ghtlkn5j",
|
|
||||||
"name": "lastDeployment",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "0a1o4e6sstp694f",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "zfnyj9he",
|
|
||||||
"name": "variables",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1bspzuku",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g65gfh7a",
|
|
||||||
"name": "nameservers",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4yzbv8urny5ja1e",
|
|
||||||
"created": "2024-07-29 10:04:39.685Z",
|
|
||||||
"updated": "2024-09-19 00:27:35.936Z",
|
|
||||||
"name": "access",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "geeur58v",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iql7jpwx",
|
|
||||||
"name": "config",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"cloudflare",
|
|
||||||
"qiniu",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lr33hiwg",
|
|
||||||
"name": "deleted",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hsxcnlvd",
|
|
||||||
"name": "usage",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"apply",
|
|
||||||
"deploy",
|
|
||||||
"all"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "c8egzzwj",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0a1o4e6sstp694f",
|
|
||||||
"created": "2024-07-30 06:30:27.801Z",
|
|
||||||
"updated": "2024-09-22 11:46:35.167Z",
|
|
||||||
"name": "deployments",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "farvlzk7",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "z3p974ainxjqlvs",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "jx5f69i3",
|
|
||||||
"name": "log",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "qbxdtg9q",
|
|
||||||
"name": "phase",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"check",
|
|
||||||
"apply",
|
|
||||||
"deploy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "rglrp1hz",
|
|
||||||
"name": "phaseSuccess",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lt1g1blu",
|
|
||||||
"name": "deployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "wledpzgb",
|
|
||||||
"name": "wholeSuccess",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "_pb_users_auth_",
|
|
||||||
"created": "2024-09-12 13:09:54.234Z",
|
|
||||||
"updated": "2024-09-19 00:27:35.937Z",
|
|
||||||
"name": "users",
|
|
||||||
"type": "auth",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_name",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_avatar",
|
|
||||||
"name": "avatar",
|
|
||||||
"type": "file",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"mimeTypes": [
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"image/svg+xml",
|
|
||||||
"image/gif",
|
|
||||||
"image/webp"
|
|
||||||
],
|
|
||||||
"thumbs": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"maxSize": 5242880,
|
|
||||||
"protected": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": "id = @request.auth.id",
|
|
||||||
"viewRule": "id = @request.auth.id",
|
|
||||||
"createRule": "",
|
|
||||||
"updateRule": "id = @request.auth.id",
|
|
||||||
"deleteRule": "id = @request.auth.id",
|
|
||||||
"options": {
|
|
||||||
"allowEmailAuth": true,
|
|
||||||
"allowOAuth2Auth": true,
|
|
||||||
"allowUsernameAuth": true,
|
|
||||||
"exceptEmailDomains": null,
|
|
||||||
"manageRule": null,
|
|
||||||
"minPasswordLength": 8,
|
|
||||||
"onlyEmailDomains": null,
|
|
||||||
"onlyVerified": false,
|
|
||||||
"requireEmail": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dy6ccjb60spfy6p",
|
|
||||||
"created": "2024-09-12 23:12:21.677Z",
|
|
||||||
"updated": "2024-09-19 00:27:35.937Z",
|
|
||||||
"name": "settings",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1tcmdsdf",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "f9wyhypi",
|
|
||||||
"name": "content",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "teolp9pl72dxlxq",
|
|
||||||
"created": "2024-09-13 12:51:05.611Z",
|
|
||||||
"updated": "2024-09-19 00:27:35.937Z",
|
|
||||||
"name": "access_groups",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "7sajiv6i",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xp8admif",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": null,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
]`
|
|
||||||
|
|
||||||
collections := []*models.Collection{}
|
|
||||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return daos.New(db).ImportCollections(collections, true, nil)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,704 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
jsonData := `[
|
|
||||||
{
|
|
||||||
"id": "z3p974ainxjqlvs",
|
|
||||||
"created": "2024-07-29 10:02:48.334Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.644Z",
|
|
||||||
"name": "domains",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iuaerpl2",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ukkhuw85",
|
|
||||||
"name": "email",
|
|
||||||
"type": "email",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"exceptDomains": null,
|
|
||||||
"onlyDomains": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "v98eebqq",
|
|
||||||
"name": "crontab",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "alc8e9ow",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "topsc9bj",
|
|
||||||
"name": "certUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "vixgq072",
|
|
||||||
"name": "certStableUrl",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g3a3sza5",
|
|
||||||
"name": "privateKey",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "gr6iouny",
|
|
||||||
"name": "certificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "tk6vnrmn",
|
|
||||||
"name": "issuerCertificate",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "sjo6ibse",
|
|
||||||
"name": "csr",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "x03n1bkj",
|
|
||||||
"name": "expiredAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "srybpixz",
|
|
||||||
"name": "targetType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun-oss",
|
|
||||||
"aliyun-cdn",
|
|
||||||
"aliyun-dcdn",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"tencent-cdn",
|
|
||||||
"qiniu-cdn"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xy7yk0mb",
|
|
||||||
"name": "targetAccess",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "6jqeyggw",
|
|
||||||
"name": "enabled",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hdsjcchf",
|
|
||||||
"name": "deployed",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "aiya3rev",
|
|
||||||
"name": "rightnow",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ixznmhzc",
|
|
||||||
"name": "lastDeployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "ghtlkn5j",
|
|
||||||
"name": "lastDeployment",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "0a1o4e6sstp694f",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "zfnyj9he",
|
|
||||||
"name": "variables",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1bspzuku",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "g65gfh7a",
|
|
||||||
"name": "nameservers",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4yzbv8urny5ja1e",
|
|
||||||
"created": "2024-07-29 10:04:39.685Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.644Z",
|
|
||||||
"name": "access",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "geeur58v",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "iql7jpwx",
|
|
||||||
"name": "config",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"cloudflare",
|
|
||||||
"qiniu",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lr33hiwg",
|
|
||||||
"name": "deleted",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "hsxcnlvd",
|
|
||||||
"name": "usage",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"apply",
|
|
||||||
"deploy",
|
|
||||||
"all"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "c8egzzwj",
|
|
||||||
"name": "group",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "teolp9pl72dxlxq",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0a1o4e6sstp694f",
|
|
||||||
"created": "2024-07-30 06:30:27.801Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.644Z",
|
|
||||||
"name": "deployments",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "farvlzk7",
|
|
||||||
"name": "domain",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "z3p974ainxjqlvs",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "jx5f69i3",
|
|
||||||
"name": "log",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "qbxdtg9q",
|
|
||||||
"name": "phase",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"check",
|
|
||||||
"apply",
|
|
||||||
"deploy"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "rglrp1hz",
|
|
||||||
"name": "phaseSuccess",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "lt1g1blu",
|
|
||||||
"name": "deployedAt",
|
|
||||||
"type": "date",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": "",
|
|
||||||
"max": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "wledpzgb",
|
|
||||||
"name": "wholeSuccess",
|
|
||||||
"type": "bool",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "_pb_users_auth_",
|
|
||||||
"created": "2024-09-12 13:09:54.234Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.644Z",
|
|
||||||
"name": "users",
|
|
||||||
"type": "auth",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_name",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "users_avatar",
|
|
||||||
"name": "avatar",
|
|
||||||
"type": "file",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"mimeTypes": [
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"image/svg+xml",
|
|
||||||
"image/gif",
|
|
||||||
"image/webp"
|
|
||||||
],
|
|
||||||
"thumbs": null,
|
|
||||||
"maxSelect": 1,
|
|
||||||
"maxSize": 5242880,
|
|
||||||
"protected": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [],
|
|
||||||
"listRule": "id = @request.auth.id",
|
|
||||||
"viewRule": "id = @request.auth.id",
|
|
||||||
"createRule": "",
|
|
||||||
"updateRule": "id = @request.auth.id",
|
|
||||||
"deleteRule": "id = @request.auth.id",
|
|
||||||
"options": {
|
|
||||||
"allowEmailAuth": true,
|
|
||||||
"allowOAuth2Auth": true,
|
|
||||||
"allowUsernameAuth": true,
|
|
||||||
"exceptEmailDomains": null,
|
|
||||||
"manageRule": null,
|
|
||||||
"minPasswordLength": 8,
|
|
||||||
"onlyEmailDomains": null,
|
|
||||||
"onlyVerified": false,
|
|
||||||
"requireEmail": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dy6ccjb60spfy6p",
|
|
||||||
"created": "2024-09-12 23:12:21.677Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.644Z",
|
|
||||||
"name": "settings",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "1tcmdsdf",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "f9wyhypi",
|
|
||||||
"name": "content",
|
|
||||||
"type": "json",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSize": 2000000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "teolp9pl72dxlxq",
|
|
||||||
"created": "2024-09-13 12:51:05.611Z",
|
|
||||||
"updated": "2024-09-22 12:08:14.645Z",
|
|
||||||
"name": "access_groups",
|
|
||||||
"type": "base",
|
|
||||||
"system": false,
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "7sajiv6i",
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"min": null,
|
|
||||||
"max": null,
|
|
||||||
"pattern": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system": false,
|
|
||||||
"id": "xp8admif",
|
|
||||||
"name": "access",
|
|
||||||
"type": "relation",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"collectionId": "4yzbv8urny5ja1e",
|
|
||||||
"cascadeDelete": false,
|
|
||||||
"minSelect": null,
|
|
||||||
"maxSelect": null,
|
|
||||||
"displayFields": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
|
||||||
],
|
|
||||||
"listRule": null,
|
|
||||||
"viewRule": null,
|
|
||||||
"createRule": null,
|
|
||||||
"updateRule": null,
|
|
||||||
"deleteRule": null,
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
]`
|
|
||||||
|
|
||||||
collections := []*models.Collection{}
|
|
||||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return daos.New(db).ImportCollections(collections, true, nil)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"cloudflare",
|
|
||||||
"qiniu",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"aws",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"aws",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"k8s"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"aws",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
m.Register(func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"aws",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"pdns",
|
|
||||||
"httpreq",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"k8s"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
}, func(db dbx.Builder) error {
|
|
||||||
dao := daos.New(db)
|
|
||||||
|
|
||||||
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
edit_configType := &schema.SchemaField{}
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"system": false,
|
|
||||||
"id": "hwy7m03o",
|
|
||||||
"name": "configType",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"presentable": false,
|
|
||||||
"unique": false,
|
|
||||||
"options": {
|
|
||||||
"maxSelect": 1,
|
|
||||||
"values": [
|
|
||||||
"aliyun",
|
|
||||||
"tencent",
|
|
||||||
"huaweicloud",
|
|
||||||
"qiniu",
|
|
||||||
"aws",
|
|
||||||
"cloudflare",
|
|
||||||
"namesilo",
|
|
||||||
"godaddy",
|
|
||||||
"local",
|
|
||||||
"ssh",
|
|
||||||
"webhook",
|
|
||||||
"k8s"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`), edit_configType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection.Schema.AddField(edit_configType)
|
|
||||||
|
|
||||||
return dao.SaveCollection(collection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "z3p974ainxjqlvs",
|
"id": "z3p974ainxjqlvs",
|
||||||
"created": "2024-07-29 10:02:48.334Z",
|
"created": "2024-07-29 10:02:48.334Z",
|
||||||
"updated": "2024-10-08 06:50:56.637Z",
|
"updated": "2024-10-13 02:40:36.312Z",
|
||||||
"name": "domains",
|
"name": "domains",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -353,7 +353,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "4yzbv8urny5ja1e",
|
"id": "4yzbv8urny5ja1e",
|
||||||
"created": "2024-07-29 10:04:39.685Z",
|
"created": "2024-07-29 10:04:39.685Z",
|
||||||
"updated": "2024-10-11 13:55:13.777Z",
|
"updated": "2024-10-20 04:36:58.692Z",
|
||||||
"name": "access",
|
"name": "access",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -399,12 +399,16 @@ func init() {
|
|||||||
"tencent",
|
"tencent",
|
||||||
"huaweicloud",
|
"huaweicloud",
|
||||||
"qiniu",
|
"qiniu",
|
||||||
|
"aws",
|
||||||
"cloudflare",
|
"cloudflare",
|
||||||
"namesilo",
|
"namesilo",
|
||||||
"godaddy",
|
"godaddy",
|
||||||
|
"pdns",
|
||||||
|
"httpreq",
|
||||||
"local",
|
"local",
|
||||||
"ssh",
|
"ssh",
|
||||||
"webhook"
|
"webhook",
|
||||||
|
"k8s"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -468,7 +472,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "0a1o4e6sstp694f",
|
"id": "0a1o4e6sstp694f",
|
||||||
"created": "2024-07-30 06:30:27.801Z",
|
"created": "2024-07-30 06:30:27.801Z",
|
||||||
"updated": "2024-09-26 12:29:38.334Z",
|
"updated": "2024-10-17 15:21:58.176Z",
|
||||||
"name": "deployments",
|
"name": "deployments",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -563,7 +567,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "_pb_users_auth_",
|
"id": "_pb_users_auth_",
|
||||||
"created": "2024-09-12 13:09:54.234Z",
|
"created": "2024-09-12 13:09:54.234Z",
|
||||||
"updated": "2024-09-26 12:29:38.334Z",
|
"updated": "2024-10-13 02:40:36.312Z",
|
||||||
"name": "users",
|
"name": "users",
|
||||||
"type": "auth",
|
"type": "auth",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -626,7 +630,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "dy6ccjb60spfy6p",
|
"id": "dy6ccjb60spfy6p",
|
||||||
"created": "2024-09-12 23:12:21.677Z",
|
"created": "2024-09-12 23:12:21.677Z",
|
||||||
"updated": "2024-09-26 12:29:38.334Z",
|
"updated": "2024-10-13 02:40:36.312Z",
|
||||||
"name": "settings",
|
"name": "settings",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -671,7 +675,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "teolp9pl72dxlxq",
|
"id": "teolp9pl72dxlxq",
|
||||||
"created": "2024-09-13 12:51:05.611Z",
|
"created": "2024-09-13 12:51:05.611Z",
|
||||||
"updated": "2024-09-26 12:29:38.334Z",
|
"updated": "2024-10-13 02:40:36.312Z",
|
||||||
"name": "access_groups",
|
"name": "access_groups",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -716,6 +720,76 @@ func init() {
|
|||||||
"updateRule": null,
|
"updateRule": null,
|
||||||
"deleteRule": null,
|
"deleteRule": null,
|
||||||
"options": {}
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "012d7abbod1hwvr",
|
||||||
|
"created": "2024-10-23 06:37:13.155Z",
|
||||||
|
"updated": "2024-10-23 07:34:58.636Z",
|
||||||
|
"name": "acme_accounts",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "fmjfn0yw",
|
||||||
|
"name": "ca",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "qqwijqzt",
|
||||||
|
"name": "email",
|
||||||
|
"type": "email",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"exceptDomains": null,
|
||||||
|
"onlyDomains": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "genxqtii",
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "1aoia909",
|
||||||
|
"name": "resource",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
}
|
}
|
||||||
]`
|
]`
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "z3p974ainxjqlvs",
|
"id": "z3p974ainxjqlvs",
|
||||||
"created": "2024-07-29 10:02:48.334Z",
|
"created": "2024-07-29 10:02:48.334Z",
|
||||||
"updated": "2024-09-26 08:20:28.305Z",
|
"updated": "2024-10-23 09:25:43.083Z",
|
||||||
"name": "domains",
|
"name": "domains",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -314,6 +314,30 @@ func init() {
|
|||||||
"max": null,
|
"max": null,
|
||||||
"pattern": ""
|
"pattern": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "wwrzc3jo",
|
||||||
|
"name": "applyConfig",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "474iwy8r",
|
||||||
|
"name": "deployConfig",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"indexes": [
|
"indexes": [
|
||||||
@@ -329,7 +353,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "4yzbv8urny5ja1e",
|
"id": "4yzbv8urny5ja1e",
|
||||||
"created": "2024-07-29 10:04:39.685Z",
|
"created": "2024-07-29 10:04:39.685Z",
|
||||||
"updated": "2024-09-26 08:36:59.632Z",
|
"updated": "2024-11-05 00:21:32.129Z",
|
||||||
"name": "access",
|
"name": "access",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -373,13 +397,20 @@ func init() {
|
|||||||
"values": [
|
"values": [
|
||||||
"aliyun",
|
"aliyun",
|
||||||
"tencent",
|
"tencent",
|
||||||
"ssh",
|
"huaweicloud",
|
||||||
"webhook",
|
|
||||||
"cloudflare",
|
|
||||||
"qiniu",
|
"qiniu",
|
||||||
|
"aws",
|
||||||
|
"cloudflare",
|
||||||
"namesilo",
|
"namesilo",
|
||||||
"godaddy",
|
"godaddy",
|
||||||
"local"
|
"pdns",
|
||||||
|
"httpreq",
|
||||||
|
"local",
|
||||||
|
"ssh",
|
||||||
|
"webhook",
|
||||||
|
"k8s",
|
||||||
|
"baiducloud",
|
||||||
|
"dogecloud"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -443,7 +474,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "0a1o4e6sstp694f",
|
"id": "0a1o4e6sstp694f",
|
||||||
"created": "2024-07-30 06:30:27.801Z",
|
"created": "2024-07-30 06:30:27.801Z",
|
||||||
"updated": "2024-09-24 14:44:48.041Z",
|
"updated": "2024-10-23 09:25:43.084Z",
|
||||||
"name": "deployments",
|
"name": "deployments",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -538,7 +569,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "_pb_users_auth_",
|
"id": "_pb_users_auth_",
|
||||||
"created": "2024-09-12 13:09:54.234Z",
|
"created": "2024-09-12 13:09:54.234Z",
|
||||||
"updated": "2024-09-24 14:44:48.041Z",
|
"updated": "2024-10-23 09:25:43.085Z",
|
||||||
"name": "users",
|
"name": "users",
|
||||||
"type": "auth",
|
"type": "auth",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -601,7 +632,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "dy6ccjb60spfy6p",
|
"id": "dy6ccjb60spfy6p",
|
||||||
"created": "2024-09-12 23:12:21.677Z",
|
"created": "2024-09-12 23:12:21.677Z",
|
||||||
"updated": "2024-09-24 14:44:48.041Z",
|
"updated": "2024-10-23 09:25:43.085Z",
|
||||||
"name": "settings",
|
"name": "settings",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -646,7 +677,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
"id": "teolp9pl72dxlxq",
|
"id": "teolp9pl72dxlxq",
|
||||||
"created": "2024-09-13 12:51:05.611Z",
|
"created": "2024-09-13 12:51:05.611Z",
|
||||||
"updated": "2024-09-24 14:44:48.041Z",
|
"updated": "2024-10-23 09:25:43.086Z",
|
||||||
"name": "access_groups",
|
"name": "access_groups",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": false,
|
"system": false,
|
||||||
@@ -691,6 +722,76 @@ func init() {
|
|||||||
"updateRule": null,
|
"updateRule": null,
|
||||||
"deleteRule": null,
|
"deleteRule": null,
|
||||||
"options": {}
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "012d7abbod1hwvr",
|
||||||
|
"created": "2024-10-23 06:37:13.155Z",
|
||||||
|
"updated": "2024-10-23 09:25:43.086Z",
|
||||||
|
"name": "acme_accounts",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "fmjfn0yw",
|
||||||
|
"name": "ca",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "qqwijqzt",
|
||||||
|
"name": "email",
|
||||||
|
"type": "email",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"exceptDomains": null,
|
||||||
|
"onlyDomains": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "genxqtii",
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "1aoia909",
|
||||||
|
"name": "resource",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
}
|
}
|
||||||
]`
|
]`
|
||||||
|
|
||||||
1
ui/dist/.gitkeep
vendored
Normal file
1
ui/dist/.gitkeep
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background">
|
<body class="bg-background" style="pointer-events: auto !important">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
763
ui/package-lock.json
generated
763
ui/package-lock.json
generated
@@ -12,10 +12,11 @@
|
|||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-collapsible": "^1.1.1",
|
"@radix-ui/react-collapsible": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||||
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-radio-group": "^1.2.0",
|
"@radix-ui/react-radio-group": "^1.2.0",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"i18next": "^23.15.1",
|
"i18next": "^23.15.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
"postcss": "^8.4.40",
|
"postcss": "^8.4.40",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"tailwindcss": "^3.4.7",
|
"tailwindcss": "^3.4.7",
|
||||||
@@ -1241,6 +1245,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-dialog": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-context": "1.1.0",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.0",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.0",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.0",
|
||||||
|
"@radix-ui/react-id": "1.1.0",
|
||||||
|
"@radix-ui/react-portal": "1.1.1",
|
||||||
|
"@radix-ui/react-presence": "1.1.0",
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-slot": "1.1.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
|
"aria-hidden": "^1.1.1",
|
||||||
|
"react-remove-scroll": "2.5.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-arrow": {
|
"node_modules/@radix-ui/react-arrow": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
||||||
@@ -1383,24 +1422,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-dialog": {
|
"node_modules/@radix-ui/react-dialog": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz",
|
||||||
"integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==",
|
"integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/primitive": "1.1.0",
|
"@radix-ui/primitive": "1.1.0",
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
"@radix-ui/react-context": "1.1.0",
|
"@radix-ui/react-context": "1.1.1",
|
||||||
"@radix-ui/react-dismissable-layer": "1.1.0",
|
"@radix-ui/react-dismissable-layer": "1.1.1",
|
||||||
"@radix-ui/react-focus-guards": "1.1.0",
|
"@radix-ui/react-focus-guards": "1.1.1",
|
||||||
"@radix-ui/react-focus-scope": "1.1.0",
|
"@radix-ui/react-focus-scope": "1.1.0",
|
||||||
"@radix-ui/react-id": "1.1.0",
|
"@radix-ui/react-id": "1.1.0",
|
||||||
"@radix-ui/react-portal": "1.1.1",
|
"@radix-ui/react-portal": "1.1.2",
|
||||||
"@radix-ui/react-presence": "1.1.0",
|
"@radix-ui/react-presence": "1.1.1",
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
"@radix-ui/react-slot": "1.1.0",
|
"@radix-ui/react-slot": "1.1.0",
|
||||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
"aria-hidden": "^1.1.1",
|
"aria-hidden": "^1.1.1",
|
||||||
"react-remove-scroll": "2.5.7"
|
"react-remove-scroll": "2.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@@ -1417,6 +1456,130 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||||
|
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.6",
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.0",
|
||||||
|
"use-sidecar": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-direction": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||||
@@ -1636,6 +1799,166 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-context": "1.1.1",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.1",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.1",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.0",
|
||||||
|
"@radix-ui/react-id": "1.1.0",
|
||||||
|
"@radix-ui/react-popper": "1.2.0",
|
||||||
|
"@radix-ui/react-portal": "1.1.2",
|
||||||
|
"@radix-ui/react-presence": "1.1.1",
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-slot": "1.1.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
|
"aria-hidden": "^1.1.1",
|
||||||
|
"react-remove-scroll": "2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||||
|
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.6",
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.0",
|
||||||
|
"use-sidecar": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
||||||
@@ -2453,6 +2776,25 @@
|
|||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/fs-extra": {
|
||||||
|
"version": "11.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
||||||
|
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jsonfile": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/jsonfile": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/jsonfile/-/jsonfile-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.0.0",
|
"version": "22.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.0.0.tgz",
|
||||||
@@ -3018,6 +3360,366 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cmdk": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "1.0.5",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-dialog": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||||
|
"@radix-ui/react-focus-guards": "1.0.1",
|
||||||
|
"@radix-ui/react-focus-scope": "1.0.4",
|
||||||
|
"@radix-ui/react-id": "1.0.1",
|
||||||
|
"@radix-ui/react-portal": "1.0.4",
|
||||||
|
"@radix-ui/react-presence": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-slot": "1.0.2",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||||
|
"aria-hidden": "^1.1.1",
|
||||||
|
"react-remove-scroll": "2.5.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||||
|
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-id": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-slot": "1.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk/node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.3",
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.0",
|
||||||
|
"use-sidecar": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3699,6 +4401,20 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs-extra": {
|
||||||
|
"version": "11.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@@ -3826,6 +4542,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/graphemer": {
|
"node_modules/graphemer": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
|
||||||
@@ -4130,6 +4852,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonfile": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jszip": {
|
"node_modules/jszip": {
|
||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
|
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
|
||||||
@@ -5525,6 +6259,15 @@
|
|||||||
"integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==",
|
"integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/universalify": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||||
|
|||||||
@@ -14,10 +14,11 @@
|
|||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-collapsible": "^1.1.1",
|
"@radix-ui/react-collapsible": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||||
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-radio-group": "^1.2.0",
|
"@radix-ui/react-radio-group": "^1.2.0",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"i18next": "^23.15.1",
|
"i18next": "^23.15.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
@@ -50,6 +52,7 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
@@ -62,6 +65,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
"postcss": "^8.4.40",
|
"postcss": "^8.4.40",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"tailwindcss": "^3.4.7",
|
"tailwindcss": "^3.4.7",
|
||||||
|
|||||||
1
ui/public/imgs/providers/baiducloud.svg
Normal file
1
ui/public/imgs/providers/baiducloud.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" width="200" height="200"><path d="M482.687 74.101c10.109-5.627 19.662-12.497 30.785-15.998 8.506-2.192 16.685 2.323 23.883 6.38 117.253 68.08 235.062 135.312 352.118 203.753-40.338 22.115-79.662 46.063-119.805 68.506-27.677 15.148-62.814 13.74-89.771-2.388-48.289-28.135-96.871-55.78-145.127-84.014-8.997-5.692-21.232-5.889-30.654-1.21-49.728 28.724-99.456 57.58-149.282 86.173-27.056 15.834-61.963 15.867-89.314 0.687a26252.906 26252.906 0 0 1-113.785-65.628c-0.229-1.178-0.72-3.533-0.949-4.744 110.776-63.533 221.256-127.722 331.9-191.517z" fill="#72AF2D" p-id="1685"></path><path d="M115.552 719.744c0.49-135.148-0.622-270.329 0.556-405.477 32.617 19.367 65.595 38.08 98.441 57.088 12.76 7.427 26.27 14.199 36.74 24.864 15.769 16.39 26.042 38.67 25.845 61.637 0.033 54.57 0.131 109.172-0.065 163.774-1.047 12.203 3.304 25.65 14.493 31.963 40.567 23.72 81.396 47.045 122.095 70.6 14.362 8.638 29.771 15.9 42.4 27.057 18.156 17.11 28.756 41.777 29.116 66.707-0.033 44.559-0.196 89.15 0.066 133.709-10.175-3.468-18.877-9.848-28.201-14.984-108.55-62.716-217.167-125.366-325.652-188.148-10.207-5.66-17.143-16.947-15.834-28.79z" fill="#118CCF" p-id="1686"></path><path d="M815.143 367.397c30.753-17.47 61.015-35.824 92.095-52.705 0.196 135.017-0.066 270.035 0.13 405.052 0.819 11.582-5.3 23.163-15.637 28.627-110.416 63.86-220.896 127.558-331.312 191.386-7.328 4.45-14.722 8.8-22.508 12.432-0.098-44.82 0.065-89.64-0.098-134.428-0.426-31.538 17.47-62.16 44.558-78.06 49.074-28.43 98.245-56.664 147.286-85.159 8.245-4.515 15.311-13.053 14.919-22.9 0.13-55.683 0.065-111.398 0-167.08-0.033-23.784 8.048-47.732 24.21-65.398 12.497-14.362 30.36-22.083 46.357-31.767z" fill="#DA4525" p-id="1687"></path></svg>
|
||||||
1
ui/public/imgs/providers/dogecloud.svg
Normal file
1
ui/public/imgs/providers/dogecloud.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
1
ui/public/imgs/providers/google.svg
Normal file
1
ui/public/imgs/providers/google.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="200" height="200" viewBox="-0.5 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Color-" transform="translate(-401.000000, -860.000000)"><g id="Google" transform="translate(401.000000, 860.000000)"><path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05"></path><path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EB4335"></path><path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853"></path><path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4"></path></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,28 +1,2 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<svg class="icon" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" height="200" width="200">
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
<circle style="fill:#32BEA6;" cx="256" cy="256" r="256"/><g><path style="fill:#FFFFFF;" d="M58.016,202.296h18.168v42.48h0.296c2.192-3.368,5.128-6.152,8.936-8.2 c3.512-2.056,7.76-3.224,12.304-3.224c12.16,0,24.896,8.064,24.896,30.912v42.04H104.6v-39.992c0-10.4-3.808-18.168-13.776-18.168 c-7.032,0-12.008,4.688-13.912,10.112c-0.584,1.472-0.728,3.368-0.728,5.424v42.624H58.016V202.296z"/><path style="fill:#FFFFFF;" d="M161.76,214.6v20.368h17.144v13.48H161.76v31.496c0,8.64,2.344,13.176,9.224,13.176 c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856 c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L161.76,214.6z"/><path style="fill:#FFFFFF;" d="M213.192,214.6v20.368h17.144v13.48h-17.144v31.496c0,8.64,2.344,13.176,9.224,13.176 c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856 c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L213.192,214.6z"/><path style="fill:#FFFFFF;" d="M243.984,258.688c0-9.376-0.296-16.992-0.592-23.728h15.832l0.872,10.984h0.296 c5.264-8.056,13.616-12.6,24.464-12.6c16.408,0,30.024,14.064,30.024,36.328c0,25.784-16.256,38.232-32.512,38.232 c-8.936,0-16.408-3.808-20.072-9.512H262v36.904h-18.016V258.688z M262,276.416c0,1.76,0.144,3.368,0.584,4.976 c1.76,7.328,8.2,12.6,15.824,12.6c11.424,0,18.168-9.52,18.168-23.584c0-12.592-6.16-22.848-17.728-22.848 c-7.472,0-14.36,5.424-16.112,13.336c-0.448,1.464-0.736,3.072-0.736,4.536L262,276.416L262,276.416z"/><path style="fill:#FFFFFF;" d="M327.504,247.12c0-6.744,4.688-11.568,11.136-11.568c6.592,0,10.984,4.832,11.136,11.568 c0,6.592-4.392,11.432-11.136,11.432C332.048,258.552,327.504,253.712,327.504,247.12z M327.504,296.488 c0-6.744,4.688-11.576,11.136-11.576c6.592,0,10.984,4.688,11.136,11.576c0,6.448-4.392,11.424-11.136,11.424 C332.048,307.912,327.504,302.936,327.504,296.488z"/><path style="fill:#FFFFFF;" d="M355.8,312.16l35.744-106.2h12.6l-35.752,106.2H355.8z"/><path style="fill:#FFFFFF;" d="M405.176,312.16l35.744-106.2h12.592l-35.728,106.2H405.176z"/></g></svg>
|
||||||
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
viewBox="0 0 512 512" xml:space="preserve">
|
|
||||||
<circle style="fill:#32BEA6;" cx="256" cy="256" r="256"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#FFFFFF;" d="M58.016,202.296h18.168v42.48h0.296c2.192-3.368,5.128-6.152,8.936-8.2
|
|
||||||
c3.512-2.056,7.76-3.224,12.304-3.224c12.16,0,24.896,8.064,24.896,30.912v42.04H104.6v-39.992c0-10.4-3.808-18.168-13.776-18.168
|
|
||||||
c-7.032,0-12.008,4.688-13.912,10.112c-0.584,1.472-0.728,3.368-0.728,5.424v42.624H58.016V202.296z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M161.76,214.6v20.368h17.144v13.48H161.76v31.496c0,8.64,2.344,13.176,9.224,13.176
|
|
||||||
c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856
|
|
||||||
c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L161.76,214.6z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M213.192,214.6v20.368h17.144v13.48h-17.144v31.496c0,8.64,2.344,13.176,9.224,13.176
|
|
||||||
c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856
|
|
||||||
c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L213.192,214.6z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M243.984,258.688c0-9.376-0.296-16.992-0.592-23.728h15.832l0.872,10.984h0.296
|
|
||||||
c5.264-8.056,13.616-12.6,24.464-12.6c16.408,0,30.024,14.064,30.024,36.328c0,25.784-16.256,38.232-32.512,38.232
|
|
||||||
c-8.936,0-16.408-3.808-20.072-9.512H262v36.904h-18.016V258.688z M262,276.416c0,1.76,0.144,3.368,0.584,4.976
|
|
||||||
c1.76,7.328,8.2,12.6,15.824,12.6c11.424,0,18.168-9.52,18.168-23.584c0-12.592-6.16-22.848-17.728-22.848
|
|
||||||
c-7.472,0-14.36,5.424-16.112,13.336c-0.448,1.464-0.736,3.072-0.736,4.536L262,276.416L262,276.416z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M327.504,247.12c0-6.744,4.688-11.568,11.136-11.568c6.592,0,10.984,4.832,11.136,11.568
|
|
||||||
c0,6.592-4.392,11.432-11.136,11.432C332.048,258.552,327.504,253.712,327.504,247.12z M327.504,296.488
|
|
||||||
c0-6.744,4.688-11.576,11.136-11.576c6.592,0,10.984,4.688,11.136,11.576c0,6.448-4.392,11.424-11.136,11.424
|
|
||||||
C332.048,307.912,327.504,302.936,327.504,296.488z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M355.8,312.16l35.744-106.2h12.6l-35.752,106.2H355.8z"/>
|
|
||||||
<path style="fill:#FFFFFF;" d="M405.176,312.16l35.744-106.2h12.592l-35.728,106.2H405.176z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill-rule="evenodd"><path d="M18.97 21.14c0 5.293-4.248 9.585-9.487 9.585S0 26.432 0 21.14s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585z" fill="#e38000"/><path d="M18.97 42.865c0 5.29-4.248 9.58-9.487 9.58S0 48.156 0 42.86s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585zM41.488 21.14c0 5.293-4.25 9.585-9.49 9.585s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zm0 21.726c0 5.29-4.25 9.58-9.49 9.58s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zM64 21.14c0 5.293-4.245 9.585-9.485 9.585s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 15.848 64 21.14z" fill="#e17f03"/><path d="M64 42.865c0 5.29-4.245 9.58-9.485 9.58s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 37.57 64 42.86z" fill="#e38000"/></svg>
|
<svg class="icon" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" version="1.1" width="200" height="200"><path d="M18.97 21.14c0 5.293-4.248 9.585-9.487 9.585S0 26.432 0 21.14s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585z" fill="#e38000"/><path d="M18.97 42.865c0 5.29-4.248 9.58-9.487 9.58S0 48.156 0 42.86s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585zM41.488 21.14c0 5.293-4.25 9.585-9.49 9.585s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zm0 21.726c0 5.29-4.25 9.58-9.49 9.58s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zM64 21.14c0 5.293-4.245 9.585-9.485 9.585s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 15.848 64 21.14z" fill="#e17f03"/><path d="M64 42.865c0 5.29-4.245 9.58-9.485 9.58s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 37.57 64 42.86z" fill="#e38000"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 858 B |
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { Access, AliyunConfig, accessFormType, getUsageByConfigType } from "@/domain/access";
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type AliyunConfig } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type AccessAliyunFormProps = {
|
type AccessAliyunFormProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
@@ -19,7 +19,7 @@ type AccessAliyunFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
|
const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
@@ -27,7 +27,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
|
|||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.name.placeholder")
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessTypeFormSchema,
|
||||||
accessKeyId: z
|
accessKeyId: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.access_key_id.placeholder")
|
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||||
@@ -60,7 +60,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
|
|||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
configType: data.configType,
|
configType: data.configType,
|
||||||
usage: getUsageByConfigType(data.configType),
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
config: {
|
config: {
|
||||||
accessKeyId: data.accessKeyId,
|
accessKeyId: data.accessKeyId,
|
||||||
accessKeySecret: data.accessSecretId,
|
accessKeySecret: data.accessSecretId,
|
||||||
@@ -98,98 +98,96 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-[35em] mx-auto mt-10">
|
<Form {...form}>
|
||||||
<Form {...form}>
|
<form
|
||||||
<form
|
onSubmit={(e) => {
|
||||||
onSubmit={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
form.handleSubmit(onSubmit)(e);
|
||||||
form.handleSubmit(onSubmit)(e);
|
}}
|
||||||
}}
|
className="space-y-8"
|
||||||
className="space-y-8"
|
>
|
||||||
>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="name"
|
||||||
name="name"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accessKeyId"
|
name="accessKeyId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accessSecretId"
|
name="accessSecretId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.access_key_secret.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.access_key_secret.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t("common.save")}</Button>
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { Access, accessFormType, AwsConfig, getUsageByConfigType } from "@/domain/access";
|
import { Access, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type AccessAwsFormProps = {
|
type AccessAwsFormProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
@@ -19,7 +19,7 @@ type AccessAwsFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
@@ -27,7 +27,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
|||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.name.placeholder")
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessTypeFormSchema,
|
||||||
region: z
|
region: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.region.placeholder")
|
.min(1, "access.authorization.form.region.placeholder")
|
||||||
@@ -72,7 +72,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
|||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
configType: data.configType,
|
configType: data.configType,
|
||||||
usage: getUsageByConfigType(data.configType),
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
config: {
|
config: {
|
||||||
region: data.region,
|
region: data.region,
|
||||||
accessKeyId: data.accessKeyId,
|
accessKeyId: data.accessKeyId,
|
||||||
@@ -111,128 +111,126 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-[35em] mx-auto mt-10">
|
<Form {...form}>
|
||||||
<Form {...form}>
|
<form
|
||||||
<form
|
onSubmit={(e) => {
|
||||||
onSubmit={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
form.handleSubmit(onSubmit)(e);
|
||||||
form.handleSubmit(onSubmit)(e);
|
}}
|
||||||
}}
|
className="space-y-8"
|
||||||
className="space-y-8"
|
>
|
||||||
>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="name"
|
||||||
name="name"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="region"
|
name="region"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.region.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.region.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accessKeyId"
|
name="accessKeyId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="secretAccessKey"
|
name="secretAccessKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="hostedZoneId"
|
name="hostedZoneId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.aws_hosted_zone_id.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.aws_hosted_zone_id.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.aws_hosted_zone_id.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.aws_hosted_zone_id.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t("common.save")}</Button>
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
194
ui/src/components/certimate/AccessBaiduCloudForm.tsx
Normal file
194
ui/src/components/certimate/AccessBaiduCloudForm.tsx
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { PbErrorData } from "@/domain/base";
|
||||||
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type BaiduCloudConfig } from "@/domain/access";
|
||||||
|
import { save } from "@/repository/access";
|
||||||
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
|
type AccessBaiduCloudFormProps = {
|
||||||
|
op: "add" | "edit" | "copy";
|
||||||
|
data?: Access;
|
||||||
|
onAfterReq: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProps) => {
|
||||||
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const formSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
configType: accessTypeFormSchema,
|
||||||
|
accessKeyId: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
secretAccessKey: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.secret_access_key.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
});
|
||||||
|
|
||||||
|
let config: BaiduCloudConfig = {
|
||||||
|
accessKeyId: "",
|
||||||
|
secretAccessKey: "",
|
||||||
|
};
|
||||||
|
if (data) config = data.config as BaiduCloudConfig;
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: data?.id,
|
||||||
|
name: data?.name || "",
|
||||||
|
configType: "baiducloud",
|
||||||
|
accessKeyId: config.accessKeyId,
|
||||||
|
secretAccessKey: config.secretAccessKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
|
const req: Access = {
|
||||||
|
id: data.id as string,
|
||||||
|
name: data.name,
|
||||||
|
configType: data.configType,
|
||||||
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
|
config: {
|
||||||
|
accessKeyId: data.accessKeyId,
|
||||||
|
secretAccessKey: data.secretAccessKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.id = op == "copy" ? "" : req.id;
|
||||||
|
const rs = await save(req);
|
||||||
|
|
||||||
|
onAfterReq();
|
||||||
|
|
||||||
|
req.id = rs.id;
|
||||||
|
req.created = rs.created;
|
||||||
|
req.updated = rs.updated;
|
||||||
|
if (data.id && op == "edit") {
|
||||||
|
updateAccess(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addAccess(req);
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as ClientResponseError;
|
||||||
|
|
||||||
|
Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
|
||||||
|
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||||
|
type: "manual",
|
||||||
|
message: value.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
form.handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="configType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accessKeyId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="secretAccessKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessBaiduCloudForm;
|
||||||
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type CloudflareConfig } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type AccessCloudflareFormProps = {
|
type AccessCloudflareFormProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
@@ -19,7 +19,7 @@ type AccessCloudflareFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProps) => {
|
const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProps) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
@@ -27,7 +27,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
|
|||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.name.placeholder")
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessTypeFormSchema,
|
||||||
dnsApiToken: z
|
dnsApiToken: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
|
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
|
||||||
@@ -54,7 +54,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
|
|||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
configType: data.configType,
|
configType: data.configType,
|
||||||
usage: getUsageByConfigType(data.configType),
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
config: {
|
config: {
|
||||||
dnsApiToken: data.dnsApiToken,
|
dnsApiToken: data.dnsApiToken,
|
||||||
},
|
},
|
||||||
@@ -88,81 +88,79 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-[35em] mx-auto mt-10">
|
<Form {...form}>
|
||||||
<Form {...form}>
|
<form
|
||||||
<form
|
onSubmit={(e) => {
|
||||||
onSubmit={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
form.handleSubmit(onSubmit)(e);
|
||||||
form.handleSubmit(onSubmit)(e);
|
}}
|
||||||
}}
|
className="space-y-8"
|
||||||
className="space-y-8"
|
>
|
||||||
>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="name"
|
||||||
name="name"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="dnsApiToken"
|
name="dnsApiToken"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.cloud_dns_api_token.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.cloud_dns_api_token.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t("common.save")}</Button>
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
188
ui/src/components/certimate/AccessDogeCloudForm.tsx
Normal file
188
ui/src/components/certimate/AccessDogeCloudForm.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { PbErrorData } from "@/domain/base";
|
||||||
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type DogeCloudConfig } from "@/domain/access";
|
||||||
|
import { save } from "@/repository/access";
|
||||||
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
|
type AccessDogeCloudFormProps = {
|
||||||
|
op: "add" | "edit" | "copy";
|
||||||
|
data?: Access;
|
||||||
|
onAfterReq: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) => {
|
||||||
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const formSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
configType: accessTypeFormSchema,
|
||||||
|
accessKey: z.string().min(1, "access.authorization.form.access_key.placeholder").max(64),
|
||||||
|
secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64),
|
||||||
|
});
|
||||||
|
|
||||||
|
let config: DogeCloudConfig = {
|
||||||
|
accessKey: "",
|
||||||
|
secretKey: "",
|
||||||
|
};
|
||||||
|
if (data) config = data.config as DogeCloudConfig;
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: data?.id,
|
||||||
|
name: data?.name || "",
|
||||||
|
configType: "dogecloud",
|
||||||
|
accessKey: config.accessKey,
|
||||||
|
secretKey: config.secretKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
|
const req: Access = {
|
||||||
|
id: data.id as string,
|
||||||
|
name: data.name,
|
||||||
|
configType: data.configType,
|
||||||
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
|
config: {
|
||||||
|
accessKey: data.accessKey,
|
||||||
|
secretKey: data.secretKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.id = op == "copy" ? "" : req.id;
|
||||||
|
const rs = await save(req);
|
||||||
|
|
||||||
|
onAfterReq();
|
||||||
|
|
||||||
|
req.id = rs.id;
|
||||||
|
req.created = rs.created;
|
||||||
|
req.updated = rs.updated;
|
||||||
|
if (data.id && op == "edit") {
|
||||||
|
updateAccess(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addAccess(req);
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as ClientResponseError;
|
||||||
|
|
||||||
|
Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
|
||||||
|
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||||
|
type: "manual",
|
||||||
|
message: value.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
form.handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="configType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accessKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.access_key.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.access_key.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="secretKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.secret_key.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessDogeCloudForm;
|
||||||
@@ -5,11 +5,12 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
||||||
import AccessAliyunForm from "./AccessAliyunForm";
|
import AccessAliyunForm from "./AccessAliyunForm";
|
||||||
import AccessTencentForm from "./AccessTencentForm";
|
import AccessTencentForm from "./AccessTencentForm";
|
||||||
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
|
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
|
||||||
|
import AccessBaiduCloudForm from "./AccessBaiduCloudForm";
|
||||||
import AccessQiniuForm from "./AccessQiniuForm";
|
import AccessQiniuForm from "./AccessQiniuForm";
|
||||||
|
import AccessDogeCloudForm from "./AccessDogeCloudForm";
|
||||||
import AccessAwsForm from "./AccessAwsForm";
|
import AccessAwsForm from "./AccessAwsForm";
|
||||||
import AccessCloudflareForm from "./AccessCloudflareForm";
|
import AccessCloudflareForm from "./AccessCloudflareForm";
|
||||||
import AccessNamesiloForm from "./AccessNamesiloForm";
|
import AccessNamesiloForm from "./AccessNamesiloForm";
|
||||||
@@ -20,7 +21,8 @@ import AccessLocalForm from "./AccessLocalForm";
|
|||||||
import AccessSSHForm from "./AccessSSHForm";
|
import AccessSSHForm from "./AccessSSHForm";
|
||||||
import AccessWebhookForm from "./AccessWebhookForm";
|
import AccessWebhookForm from "./AccessWebhookForm";
|
||||||
import AccessKubernetesForm from "./AccessKubernetesForm";
|
import AccessKubernetesForm from "./AccessKubernetesForm";
|
||||||
import { Access, accessTypeMap } from "@/domain/access";
|
import { Access } from "@/domain/access";
|
||||||
|
import { AccessTypeSelect } from "./AccessTypeSelect";
|
||||||
|
|
||||||
type AccessEditProps = {
|
type AccessEditProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
@@ -29,18 +31,17 @@ type AccessEditProps = {
|
|||||||
data?: Access;
|
data?: Access;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const typeKeys = Array.from(accessTypeMap.keys());
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const [configType, setConfigType] = useState(data?.configType || "");
|
const [configType, setConfigType] = useState(data?.configType || "");
|
||||||
|
|
||||||
let form = <> </>;
|
let childComponent = <> </>;
|
||||||
switch (configType) {
|
switch (configType) {
|
||||||
case "aliyun":
|
case "aliyun":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessAliyunForm
|
<AccessAliyunForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -51,7 +52,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "tencent":
|
case "tencent":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessTencentForm
|
<AccessTencentForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -62,7 +63,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "huaweicloud":
|
case "huaweicloud":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessHuaweiCloudForm
|
<AccessHuaweiCloudForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -72,8 +73,19 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "baiducloud":
|
||||||
|
childComponent = (
|
||||||
|
<AccessBaiduCloudForm
|
||||||
|
data={data}
|
||||||
|
op={op}
|
||||||
|
onAfterReq={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "qiniu":
|
case "qiniu":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessQiniuForm
|
<AccessQiniuForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -83,8 +95,19 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "dogecloud":
|
||||||
|
childComponent = (
|
||||||
|
<AccessDogeCloudForm
|
||||||
|
data={data}
|
||||||
|
op={op}
|
||||||
|
onAfterReq={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "aws":
|
case "aws":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessAwsForm
|
<AccessAwsForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -95,7 +118,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "cloudflare":
|
case "cloudflare":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessCloudflareForm
|
<AccessCloudflareForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -106,7 +129,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "namesilo":
|
case "namesilo":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessNamesiloForm
|
<AccessNamesiloForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -117,7 +140,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "godaddy":
|
case "godaddy":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessGodaddyForm
|
<AccessGodaddyForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -128,7 +151,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "pdns":
|
case "pdns":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessPdnsForm
|
<AccessPdnsForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -139,18 +162,18 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "httpreq":
|
case "httpreq":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessHttpreqForm
|
<AccessHttpreqForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
onAfterReq={() => {
|
onAfterReq={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "local":
|
case "local":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessLocalForm
|
<AccessLocalForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -161,7 +184,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "ssh":
|
case "ssh":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessSSHForm
|
<AccessSSHForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -172,7 +195,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "webhook":
|
case "webhook":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessWebhookForm
|
<AccessWebhookForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -183,7 +206,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "k8s":
|
case "k8s":
|
||||||
form = (
|
childComponent = (
|
||||||
<AccessKubernetesForm
|
<AccessKubernetesForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
@@ -195,50 +218,53 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOptionCls = (val: string) => {
|
|
||||||
return val == configType ? "border-primary" : "";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onOpenChange={setOpen} open={open}>
|
<Dialog
|
||||||
|
onOpenChange={(openState) => {
|
||||||
|
if (openState) {
|
||||||
|
document.body.style.pointerEvents = "auto";
|
||||||
|
}
|
||||||
|
setOpen(openState);
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
modal={false}
|
||||||
|
>
|
||||||
<DialogTrigger asChild className={cn(className)}>
|
<DialogTrigger asChild className={cn(className)}>
|
||||||
{trigger}
|
{trigger}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
<DialogContent
|
||||||
|
className="sm:max-w-[600px] w-full dark:text-stone-200"
|
||||||
|
onInteractOutside={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{op == "add" ? t("access.authorization.add") : op == "edit" ? t("access.authorization.edit") : t("access.authorization.copy")}
|
{
|
||||||
|
{
|
||||||
|
["add"]: t("access.authorization.add"),
|
||||||
|
["edit"]: t("access.authorization.edit"),
|
||||||
|
["copy"]: t("access.authorization.copy"),
|
||||||
|
}[op]
|
||||||
|
}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<ScrollArea className="max-h-[80vh]">
|
<ScrollArea className="max-h-[80vh]">
|
||||||
<div className="container py-3">
|
<div className="container py-3">
|
||||||
<Label>{t("access.authorization.form.type.label")}</Label>
|
<div>
|
||||||
|
<Label>{t("access.authorization.form.type.label")}</Label>
|
||||||
|
<AccessTypeSelect
|
||||||
|
value={configType}
|
||||||
|
onChange={(val) => {
|
||||||
|
setConfigType(val);
|
||||||
|
}}
|
||||||
|
className="w-full mt-3"
|
||||||
|
placeholder={t("access.authorization.form.type.placeholder")}
|
||||||
|
searchPlaceholder={t("access.authorization.form.type.search.placeholder")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Select
|
<div className="mt-8">{childComponent}</div>
|
||||||
onValueChange={(val) => {
|
|
||||||
setConfigType(val);
|
|
||||||
}}
|
|
||||||
defaultValue={configType}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="mt-3">
|
|
||||||
<SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
|
|
||||||
{typeKeys.map((key) => (
|
|
||||||
<SelectItem value={key} key={key}>
|
|
||||||
<div className={cn("flex items-center space-x-2 rounded cursor-pointer", getOptionCls(key))}>
|
|
||||||
<img src={accessTypeMap.get(key)?.[1]} className="h-6 w-6" />
|
|
||||||
<div>{t(accessTypeMap.get(key)?.[0] || "")}</div>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{form}
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -246,4 +272,4 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccessEdit;
|
export default AccessEditDialog;
|
||||||
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { Access, accessFormType, getUsageByConfigType, GodaddyConfig } from "@/domain/access";
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type GodaddyConfig } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type AccessGodaddyFormProps = {
|
type AccessGodaddyFormProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
@@ -19,7 +19,7 @@ type AccessGodaddyFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => {
|
const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
@@ -27,7 +27,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
|
|||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.name.placeholder")
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessTypeFormSchema,
|
||||||
apiKey: z
|
apiKey: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
|
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
|
||||||
@@ -60,7 +60,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
|
|||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
configType: data.configType,
|
configType: data.configType,
|
||||||
usage: getUsageByConfigType(data.configType),
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
config: {
|
config: {
|
||||||
apiKey: data.apiKey,
|
apiKey: data.apiKey,
|
||||||
apiSecret: data.apiSecret,
|
apiSecret: data.apiSecret,
|
||||||
@@ -95,96 +95,94 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-[35em] mx-auto mt-10">
|
<Form {...form}>
|
||||||
<Form {...form}>
|
<form
|
||||||
<form
|
onSubmit={(e) => {
|
||||||
onSubmit={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
form.handleSubmit(onSubmit)(e);
|
||||||
form.handleSubmit(onSubmit)(e);
|
}}
|
||||||
}}
|
className="space-y-8"
|
||||||
className="space-y-8"
|
>
|
||||||
>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="name"
|
||||||
name="name"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.godaddy_api_key.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.godaddy_api_key.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="apiSecret"
|
name="apiSecret"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
|
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t("access.authorization.form.godaddy_api_secret.placeholder")} {...field} />
|
<Input placeholder={t("access.authorization.form.godaddy_api_secret.placeholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t("common.save")}</Button>
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { update } from "@/repository/access_group";
|
import { update } from "@/repository/access_group";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
type AccessGroupEditProps = {
|
type AccessGroupEditProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -20,7 +20,7 @@ type AccessGroupEditProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||||
const { reloadAccessGroups } = useConfig();
|
const { reloadAccessGroups } = useConfigContext();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user