Compare commits

...

57 Commits

Author SHA1 Message Date
Yoan.liu
8fe942d8d5 update to version v0.3.9 2025-04-21 21:27:13 +08:00
Yoan.liu
cf98e789d8 Merge pull request #627 from fudiwei/bugfix
bugfix
2025-04-21 17:50:02 +08:00
Fu Diwei
ff58b9a317 fix: wangsu api error 2025-04-21 09:17:52 +08:00
Yoan.liu
65e7d390b8 Merge pull request #629 from fudiwei/feat/providers
new providers
2025-04-20 08:22:56 +08:00
Fu Diwei
54ae378e30 fix: handle deployment task status check on deployment to wangsu cdnpro 2025-04-19 19:37:31 +08:00
Fu Diwei
347695cf66 feat: update default certificate paths on deployment to local and ssh 2025-04-19 14:04:06 +08:00
Fu Diwei
8f4d854b0d feat: support replacing old certificate on deployment to 1panel site 2025-04-19 14:02:55 +08:00
Yoan.liu
94bd846726 Merge pull request #625 from fondoger/fondoger/fix-volcengine
修复火山云证书上传获取空值的bug
2025-04-19 10:46:26 +08:00
Yoan.liu
0365841549 Merge pull request #621 from fondoger/fondoger/keyvault
Fix Azure KeyVault bug & Support custom certificate name in Azure KeyVault
2025-04-19 10:46:07 +08:00
Yoan.liu
74bd1f64a0 Merge pull request #610 from imlonghao/feat/bunny
feat: support bunny as dns and cdn provider
2025-04-19 10:45:51 +08:00
Fu Diwei
5bce03410e feat: add aliyun apigw deployer 2025-04-18 20:51:23 +08:00
Fu Diwei
32ff658e84 fix: #626 2025-04-18 18:20:01 +08:00
Fu Diwei
c10ceed753 feat: improve log 2025-04-18 18:04:52 +08:00
Fu Diwei
eb45b56a87 fix: ignore wangsu api responses without content 2025-04-18 17:54:51 +08:00
Fu Diwei
283b150d60 refactor: re-impl azure keyvault deployer 2025-04-18 17:52:12 +08:00
Fu Diwei
7329a22132 chore(ui): improve i18n 2025-04-17 22:11:45 +08:00
Fu Diwei
50b48d956f fix: #617 2025-04-17 22:08:53 +08:00
Fu Diwei
55d7a05af8 fix: #615 2025-04-17 21:44:40 +08:00
RHQYZ
6c70b0655a chore: modify error log 2025-04-17 13:39:04 +08:00
fondoger
0004eac764 Modify code according to code suggestions 2025-04-17 13:13:23 +08:00
fondoger
5fe24465d7 修复火山云证书上传获取空值的bug 2025-04-17 12:54:43 +08:00
fondoger
364ceb2399 Fix Azure KeyVault bug 2025-04-16 21:53:19 +08:00
Yoan.liu
88b90986b1 update to version v0.3.8 2025-04-13 20:55:33 +08:00
imlonghao
5143823e43 feat: support bunny as dns and cdn provider 2025-04-13 15:47:23 +08:00
Yoan.liu
44a6190e17 resolve build error 2025-04-13 09:14:08 +08:00
Yoan.liu
4475ed0dea resolve build error 2025-04-13 08:54:05 +08:00
Yoan.liu
6a23da3de3 Merge pull request #596 from redzl/redzl-patch-1
bugfix: tencent cloud ecdn deploy error
2025-04-13 08:24:43 +08:00
Yoan.liu
0f1d5a7730 Merge pull request #604 from banto6/main
feat(notify): add mattermost
2025-04-13 08:24:26 +08:00
Yoan.liu
5b4c3bb668 Merge branch 'main' into main 2025-04-13 08:24:16 +08:00
Yoan.liu
ad49f9d788 Merge pull request #607 from imlonghao/feat/pushover
feat: support pushover as notification
2025-04-13 08:18:59 +08:00
Yoan.liu
397ceefa02 Merge branch 'main' into feat/pushover 2025-04-13 08:18:47 +08:00
Yoan.liu
e11b1ca4e8 Merge pull request #597 from fudiwei/feat/providers
new providers
2025-04-13 08:14:50 +08:00
Yoan.liu
8e983e7286 Merge pull request #587 from fudiwei/bugfix
bugfix
2025-04-13 08:13:06 +08:00
Fu Diwei
f970ae7529 feat: add wangsu cdnpro deployer 2025-04-12 21:43:21 +08:00
Fu Diwei
b0973b5ca8 refactor: clean code 2025-04-12 20:54:02 +08:00
banto
4784bf9dba feat: add channelId tooltip 2025-04-12 20:01:03 +08:00
imlonghao
6b8dbf5235 feat: support pushover as notification 2025-04-12 13:05:37 +08:00
banto
48f698e84b style: fix code style 2025-04-12 12:45:03 +08:00
banto
ec0cdf8b96 feat(notify): add mattermost 2025-04-11 22:55:47 +08:00
Fu Diwei
2a6cc01eed feat(ui): adjust table scroll width in Dashboard 2025-04-10 21:57:22 +08:00
Fu Diwei
acc1365101 Merge branch 'feat/providers' of https://github.com/fudiwei/certimate into feat/providers 2025-04-09 23:12:52 +08:00
Fu Diwei
c5409c78ba refactor: edgio api sdk 2025-04-09 23:12:11 +08:00
RHQYZ
b97de6c06b Merge branch 'usual2970:main' into feat/providers 2025-04-09 22:56:43 +08:00
RHQYZ
4e3f499d76 chore: github issue templates 2025-04-09 10:55:53 +08:00
Fu Diwei
3cebe51796 feat: add rainyun rcdn deployer 2025-04-08 21:53:16 +08:00
Fu Diwei
25bd17dc6e feat: add rainyun ssl center uploader 2025-04-08 21:53:05 +08:00
redzl
2525f54dc3 解决腾讯云ECDN部署报错的问题
ECDN部署的时候报错:failed to execute sdk request 'ssl.DeployCertificateInstance':[TencentCloudSDKError] Code=FailedOperation.CertificateHostResourceTypeInvalid, Message=云资源类型无效。
经排查'ssl.DeployCertificateInstance接口的ResourceType不支持ecdn类型,ecdn和cdn都需要传入cdn
2025-04-08 18:06:51 +08:00
Fu Diwei
2127bb7e69 Merge branch 'feat/providers' of https://github.com/fudiwei/certimate into feat/providers 2025-04-08 16:47:49 +08:00
Fu Diwei
ed6d74f1ba feat(ui): builtin providers tag 2025-04-08 16:44:10 +08:00
Fu Diwei
02dd11f196 chore(ui): improve i18n 2025-04-08 10:19:42 +08:00
Fu Diwei
37b9ae30e2 fix: #595 2025-04-08 09:41:16 +08:00
Fu Diwei
0463dbcc75 Merge branch 'bugfix' of https://github.com/fudiwei/certimate into bugfix 2025-04-07 15:32:12 +08:00
Fu Diwei
111ef97d9c fix: migration error 2025-04-07 15:31:20 +08:00
RHQYZ
e8e854e392 Merge branch 'usual2970:main' into bugfix 2025-04-07 12:42:22 +08:00
Fu Diwei
47c4ba9dd6 feat(ui): workflow runs deleting warning 2025-04-05 21:23:55 +08:00
Fu Diwei
6ff738144a fix: #585 #586 2025-04-03 20:33:58 +08:00
Fu Diwei
26028bb1eb chore(ui): improve i18n 2025-04-03 20:30:44 +08:00
116 changed files with 4266 additions and 164 deletions

View File

@@ -1,6 +1,6 @@
name: "❓ Questions" name: "❓ Questions"
description: "遇到了困难需要求助? / Have problem in use and need help?" description: "遇到了困难需要求助? / Have problem in use and need help?"
title: "[Feature] 简要描述你遇到的问题" title: "简要描述你遇到的问题"
body: body:
- type: markdown - type: markdown
attributes: attributes:
@@ -19,6 +19,14 @@ body:
3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar. 3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar.
4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly. 4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly.
- type: input
attributes:
label: 软件版本 / Release Version
description: 请提供 Certimate 的具体版本。 / Please provide the specific version of Certimate.
placeholder: (e.g. v1.0.0)
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: 问题描述 / Description label: 问题描述 / Description

6
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0
github.com/Edgio/edgio-api v0.0.0-workspace
github.com/G-Core/gcorelabscdn-go v1.0.28 github.com/G-Core/gcorelabscdn-go v1.0.28
github.com/alibabacloud-go/alb-20200616/v2 v2.2.8 github.com/alibabacloud-go/alb-20200616/v2 v2.2.8
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4 github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
@@ -72,6 +73,8 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2 // indirect
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
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
@@ -107,6 +110,7 @@ require (
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
github.com/nrdcg/desec v0.10.0 // indirect github.com/nrdcg/desec v0.10.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect
@@ -211,6 +215,8 @@ require (
modernc.org/sqlite v1.36.1 // indirect modernc.org/sqlite v1.36.1 // indirect
) )
replace github.com/Edgio/edgio-api v0.0.0-workspace => ./internal/pkg/vendors/edgio-sdk/edgio-api@v0.0.0-workspace
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0 replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1 replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1

8
go.sum
View File

@@ -95,10 +95,14 @@ github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do2
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 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2 h1:yH84ePgqtA2tF3ly7Tf3AA5ogl2SC8kqCNG4+zz4yo4=
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2/go.mod h1:XLaCapbSH7olJTs42wisDO9JvX9BGy5acZk0bLNejDs=
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4 h1:ngRlctbt135zoujwX0lXSv9m4h1/bmg/yalQS0z1EWc= github.com/alibabacloud-go/cas-20200407/v3 v3.0.4 h1:ngRlctbt135zoujwX0lXSv9m4h1/bmg/yalQS0z1EWc=
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4/go.mod h1:6n9MZ9SH3HlSzfe2oKwjOqhJx3dxvW2gMDO+lq8t9U4= github.com/alibabacloud-go/cas-20200407/v3 v3.0.4/go.mod h1:6n9MZ9SH3HlSzfe2oKwjOqhJx3dxvW2gMDO+lq8t9U4=
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2 h1:+KJOPukTM+xMyiLOW5qBwYKG2df3Ar7coRsqc1juKO8= github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2 h1:+KJOPukTM+xMyiLOW5qBwYKG2df3Ar7coRsqc1juKO8=
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2/go.mod h1:GnPiPL3HlzCi8SGiLiVgKrAFkP1vTtcF4yGtjsl4wfo= github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2/go.mod h1:GnPiPL3HlzCi8SGiLiVgKrAFkP1vTtcF4yGtjsl4wfo=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2 h1:Ug50clztqiQAy5t0R9Vejibz2Xgxm1Tpw2Y6A9eAwRE=
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2/go.mod h1:l9Zd2FanDUO2UqHJSPnOv+cY9DVT+YXcr97zfpSHywo=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= 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-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 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
@@ -498,6 +502,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -649,6 +655,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs= github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM= github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM=
github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs=
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=

View File

@@ -11,6 +11,7 @@ import (
pAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53" pAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53"
pAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns" pAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
pBaiduCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud" pBaiduCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud"
pBunny "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/bunny"
pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare" pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud" pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud"
@@ -128,6 +129,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err return applicant, err
} }
case domain.ApplyDNSProviderTypeBunny:
{
access := domain.AccessConfigForBunny{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pBunny.NewChallengeProvider(&pBunny.ChallengeProviderConfig{
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeCloudflare: case domain.ApplyDNSProviderTypeCloudflare:
{ {
access := domain.AccessConfigForCloudflare{} access := domain.AccessConfigForCloudflare{}

View File

@@ -9,6 +9,7 @@ import (
p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console" p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console"
p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site" p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site"
pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb"
pAliyunAPIGW "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-apigw"
pAliyunCAS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas" pAliyunCAS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas"
pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy" pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy"
pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
@@ -31,6 +32,7 @@ import (
pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn" pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn"
pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console" pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console"
pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site"
pBunnyCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn"
pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly" pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly"
pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly" pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly"
@@ -49,6 +51,7 @@ import (
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili" pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
pRainYunRCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline" pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
@@ -73,6 +76,7 @@ import (
pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex" pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex"
pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos"
pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook"
"github.com/usual2970/certimate/internal/pkg/utils/maputil" "github.com/usual2970/certimate/internal/pkg/utils/maputil"
"github.com/usual2970/certimate/internal/pkg/utils/sliceutil" "github.com/usual2970/certimate/internal/pkg/utils/sliceutil"
@@ -106,7 +110,9 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
ApiUrl: access.ApiUrl, ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey, ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections, AllowInsecureConnections: access.AllowInsecureConnections,
ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))),
WebsiteId: maputil.GetInt64(options.ProviderDeployConfig, "websiteId"), WebsiteId: maputil.GetInt64(options.ProviderDeployConfig, "websiteId"),
CertificateId: maputil.GetInt64(options.ProviderDeployConfig, "certificateId"),
}) })
return deployer, err return deployer, err
@@ -115,7 +121,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
} }
} }
case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunAPIGW, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF:
{ {
access := domain.AccessConfigForAliyun{} access := domain.AccessConfigForAliyun{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
@@ -135,6 +141,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
}) })
return deployer, err return deployer, err
case domain.DeployProviderTypeAliyunAPIGW:
deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maputil.GetString(options.ProviderDeployConfig, "region"),
ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderDeployConfig, "serviceType")),
GatewayId: maputil.GetString(options.ProviderDeployConfig, "gatewayId"),
GroupId: maputil.GetString(options.ProviderDeployConfig, "groupId"),
Domain: maputil.GetString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunCAS: case domain.DeployProviderTypeAliyunCAS:
deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{
AccessKeyId: access.AccessKeyId, AccessKeyId: access.AccessKeyId,
@@ -295,11 +313,12 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
switch options.Provider { switch options.Provider {
case domain.DeployProviderTypeAzureKeyVault: case domain.DeployProviderTypeAzureKeyVault:
deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{ deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{
TenantId: access.TenantId, TenantId: access.TenantId,
ClientId: access.ClientId, ClientId: access.ClientId,
ClientSecret: access.ClientSecret, ClientSecret: access.ClientSecret,
CloudName: access.CloudName, CloudName: access.CloudName,
KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"), KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"),
CertificateName: maputil.GetString(options.ProviderDeployConfig, "certificateName"),
}) })
return deployer, err return deployer, err
@@ -414,6 +433,21 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
} }
} }
case domain.DeployProviderTypeBunnyCDN:
{
access := domain.AccessConfigForBunny{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pBunnyCDN.NewDeployer(&pBunnyCDN.DeployerConfig{
ApiKey: access.ApiKey,
PullZoneId: maputil.GetString(options.ProviderDeployConfig, "pullZoneId"),
HostName: maputil.GetString(options.ProviderDeployConfig, "hostName"),
})
return deployer, err
}
case domain.DeployProviderTypeBytePlusCDN: case domain.DeployProviderTypeBytePlusCDN:
{ {
access := domain.AccessConfigForBytePlus{} access := domain.AccessConfigForBytePlus{}
@@ -459,7 +493,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
ApiUrl: access.ApiUrl, ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey, ApiKey: access.ApiKey,
ApiSecret: access.ApiSecret, ApiSecret: access.ApiSecret,
ResourceType: pCdnfly.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))),
SiteId: maputil.GetString(options.ProviderDeployConfig, "siteId"), SiteId: maputil.GetString(options.ProviderDeployConfig, "siteId"),
CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"),
}) })
@@ -681,6 +715,27 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
} }
} }
case domain.DeployProviderTypeRainYunRCDN:
{
access := domain.AccessConfigForRainYun{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeTencentCloudCDN:
deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{
ApiKey: access.ApiKey,
InstanceId: maputil.GetInt32(options.ProviderDeployConfig, "instanceId"),
Domain: maputil.GetString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeSafeLine: case domain.DeployProviderTypeSafeLine:
{ {
access := domain.AccessConfigForSafeLine{} access := domain.AccessConfigForSafeLine{}
@@ -981,6 +1036,31 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
} }
} }
case domain.DeployProviderTypeWangsuCDNPro:
{
access := domain.AccessConfigForWangsu{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeWangsuCDNPro:
deployer, err := pWangsuCDNPro.NewDeployer(&pWangsuCDNPro.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
ApiKey: access.ApiKey,
Environment: maputil.GetOrDefaultString(options.ProviderDeployConfig, "environment", "production"),
Domain: maputil.GetString(options.ProviderDeployConfig, "domain"),
CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"),
WebhookId: maputil.GetString(options.ProviderDeployConfig, "webhookId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeWebhook: case domain.DeployProviderTypeWebhook:
{ {
access := domain.AccessConfigForWebhook{} access := domain.AccessConfigForWebhook{}

View File

@@ -64,6 +64,10 @@ type AccessConfigForBytePlus struct {
SecretKey string `json:"secretKey"` SecretKey string `json:"secretKey"`
} }
type AccessConfigForBunny struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForCacheFly struct { type AccessConfigForCacheFly struct {
ApiToken string `json:"apiToken"` ApiToken string `json:"apiToken"`
} }
@@ -228,6 +232,12 @@ type AccessConfigForVolcEngine struct {
SecretAccessKey string `json:"secretAccessKey"` SecretAccessKey string `json:"secretAccessKey"`
} }
type AccessConfigForWangsu struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
ApiKey string `json:"apiKey"`
}
type AccessConfigForWebhook struct { type AccessConfigForWebhook struct {
Url string `json:"url"` Url string `json:"url"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`

View File

@@ -14,6 +14,8 @@ const (
NotifyChannelTypeEmail = NotifyChannelType("email") NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeGotify = NotifyChannelType("gotify") NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark") NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypeMattermost = NotifyChannelType("mattermost")
NotifyChannelTypePushover = NotifyChannelType("pushover")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus") NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan") NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
NotifyChannelTypeTelegram = NotifyChannelType("telegram") NotifyChannelTypeTelegram = NotifyChannelType("telegram")

View File

@@ -61,6 +61,7 @@ const (
AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel") AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWangsu = AccessProviderType("wangsu")
AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWestcn = AccessProviderType("westcn") AccessProviderTypeWestcn = AccessProviderType("westcn")
AccessProviderTypeZeroSSL = AccessProviderType("zerossl") AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
@@ -103,6 +104,7 @@ const (
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS] ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS]
ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns") ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns")
ApplyDNSProviderTypeBunny = ApplyDNSProviderType("bunny")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud") ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud")
@@ -145,6 +147,7 @@ const (
DeployProviderType1PanelConsole = DeployProviderType("1panel-console") DeployProviderType1PanelConsole = DeployProviderType("1panel-console")
DeployProviderType1PanelSite = DeployProviderType("1panel-site") DeployProviderType1PanelSite = DeployProviderType("1panel-site")
DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
DeployProviderTypeAliyunAPIGW = DeployProviderType("aliyun-apigw")
DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas") DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas")
DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy")
DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
@@ -167,6 +170,7 @@ const (
DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn") DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn")
DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console") DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console")
DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site") DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site")
DeployProviderTypeBunnyCDN = DeployProviderType("bunny-cdn")
DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
DeployProviderTypeCacheFly = DeployProviderType("cachefly") DeployProviderTypeCacheFly = DeployProviderType("cachefly")
DeployProviderTypeCdnfly = DeployProviderType("cdnfly") DeployProviderTypeCdnfly = DeployProviderType("cdnfly")
@@ -186,6 +190,7 @@ const (
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo") DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo")
DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn")
DeployProviderTypeSafeLine = DeployProviderType("safeline") DeployProviderTypeSafeLine = DeployProviderType("safeline")
DeployProviderTypeSSH = DeployProviderType("ssh") DeployProviderTypeSSH = DeployProviderType("ssh")
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
@@ -211,5 +216,6 @@ const (
DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex") DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex")
DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro")
DeployProviderTypeWebhook = DeployProviderType("webhook") DeployProviderTypeWebhook = DeployProviderType("webhook")
) )

View File

@@ -10,6 +10,8 @@ import (
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
@@ -59,6 +61,19 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
}) })
case domain.NotifyChannelTypeMattermost:
return pMattermost.NewNotifier(&pMattermost.NotifierConfig{
ServerUrl: maputil.GetString(channelConfig, "serverUrl"),
ChannelId: maputil.GetString(channelConfig, "channelId"),
Username: maputil.GetString(channelConfig, "username"),
Password: maputil.GetString(channelConfig, "password"),
})
case domain.NotifyChannelTypePushover:
return pPushover.NewNotifier(&pPushover.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"),
User: maputil.GetString(channelConfig, "user"),
})
case domain.NotifyChannelTypePushPlus: case domain.NotifyChannelTypePushPlus:
return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"), Token: maputil.GetString(channelConfig, "token"),

View File

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

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"net/url" "net/url"
"strconv" "strconv"
@@ -23,8 +24,14 @@ type DeployerConfig struct {
ApiKey string `json:"apiKey"` ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。 // 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType ResourceType `json:"resourceType"`
// 网站 ID。 // 网站 ID。
WebsiteId int64 `json:"websiteId"` // 部署资源类型为 [RESOURCE_TYPE_WEBSITE] 时必填。
WebsiteId int64 `json:"websiteId,omitempty"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId int64 `json:"certificateId,omitempty"`
} }
type DeployerProvider struct { type DeployerProvider struct {
@@ -73,6 +80,30 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
} }
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_WEBSITE:
if err := d.deployToWebsite(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
case RESOURCE_TYPE_CERTIFICATE:
if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.WebsiteId == 0 {
return errors.New("config `websiteId` is required")
}
// 获取网站 HTTPS 配置 // 获取网站 HTTPS 配置
getHttpsConfReq := &opsdk.GetHttpsConfRequest{ getHttpsConfReq := &opsdk.GetHttpsConfRequest{
WebsiteID: d.config.WebsiteId, WebsiteID: d.config.WebsiteId,
@@ -80,13 +111,13 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq) getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq)
d.logger.Debug("sdk request '1panel.GetHttpsConf'", slog.Any("request", getHttpsConfReq), slog.Any("response", getHttpsConfResp)) d.logger.Debug("sdk request '1panel.GetHttpsConf'", slog.Any("request", getHttpsConfReq), slog.Any("response", getHttpsConfResp))
if err != nil { if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'") return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'")
} }
// 上传证书到面板 // 上传证书到面板
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil { if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file") return xerrors.Wrap(err, "failed to upload certificate file")
} else { } else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
} }
@@ -106,10 +137,42 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq) updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq)
d.logger.Debug("sdk request '1panel.UpdateHttpsConf'", slog.Any("request", updateHttpsConfReq), slog.Any("response", updateHttpsConfResp)) d.logger.Debug("sdk request '1panel.UpdateHttpsConf'", slog.Any("request", updateHttpsConfReq), slog.Any("response", updateHttpsConfResp))
if err != nil { if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'") return xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'")
} }
return &deployer.DeployResult{}, nil return nil
}
func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.CertificateId == 0 {
return errors.New("config `certificateId` is required")
}
// 获取证书详情
getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{
SSLID: d.config.CertificateId,
}
getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq)
d.logger.Debug("sdk request '1panel.GetWebsiteSSL'", slog.Any("request", getWebsiteSSLReq), slog.Any("response", getWebsiteSSLResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetWebsiteSSL'")
}
// 更新证书
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
Type: "paste",
SSLID: d.config.CertificateId,
Description: getWebsiteSSLResp.Data.Description,
Certificate: certPem,
PrivateKey: privkeyPem,
}
uploadWebsiteSSLResp, err := d.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
d.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'")
}
return nil
} }
func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) { func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) {

View File

@@ -20,7 +20,7 @@ var (
) )
func init() { func init() {
argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_" argsPrefix := "CERTIMATE_DEPLOYER_1PANELSITE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
@@ -32,12 +32,12 @@ func init() {
/* /*
Shell command to run this test: Shell command to run this test:
go test -v ./1panel_console_test.go -args \ go test -v ./1panel_site_test.go -args \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_WEBSITEID="your-website-id" --CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id"
*/ */
func TestDeploy(t *testing.T) { func TestDeploy(t *testing.T) {
flag.Parse() flag.Parse()
@@ -55,8 +55,9 @@ func TestDeploy(t *testing.T) {
deployer, err := provider.NewDeployer(&provider.DeployerConfig{ deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl, ApiUrl: fApiUrl,
ApiKey: fApiKey, ApiKey: fApiKey,
WebsiteId: fWebsiteId,
AllowInsecureConnections: true, AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_WEBSITE,
WebsiteId: fWebsiteId,
}) })
if err != nil { if err != nil {
t.Errorf("err: %+v", err) t.Errorf("err: %+v", err)

View File

@@ -0,0 +1,10 @@
package onepanelsite
type ResourceType string
const (
// 资源类型:替换指定网站的证书。
RESOURCE_TYPE_WEBSITE = ResourceType("website")
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
)

View File

@@ -0,0 +1,269 @@
package aliyunapigw
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
aliapig "github.com/alibabacloud-go/apig-20240327/v3/client"
alicloudapi "github.com/alibabacloud-go/cloudapi-20160714/v5/client"
aliopen "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/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
)
type DeployerConfig struct {
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云地域。
Region string `json:"region"`
// 服务类型。
ServiceType ServiceType `json:"serviceType"`
// API 网关 ID。
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。
GatewayId string `json:"gatewayId,omitempty"`
// API 分组 ID。
// 服务类型为 [SERVICE_TYPE_TRADITIONAL] 时必填。
GroupId string `json:"groupId,omitempty"`
// 自定义域名(支持泛域名)。
Domain string `json:"domain"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClients *wSdkClients
sslUploader uploader.Uploader
}
type wSdkClients struct {
CloudNativeAPIGateway *aliapig.Client
TraditionalAPIGateway *alicloudapi.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
clients, err := createSdkClients(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClients: clients,
sslUploader: uploader,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
switch d.config.ServiceType {
case SERVICE_TYPE_TRADITIONAL:
if err := d.deployToTraditional(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
case SERVICE_TYPE_CLOUDNATIVE:
if err := d.deployToCloudNative(ctx, certPem, privkeyPem); err != nil {
return nil, err
}
default:
return nil, xerrors.Errorf("unsupported service type: %s", string(d.config.ServiceType))
}
return &deployer.DeployResult{}, nil
}
func (d *DeployerProvider) deployToTraditional(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.GroupId == "" {
return errors.New("config `groupId` is required")
}
if d.config.Domain == "" {
return errors.New("config `domain` is required")
}
// 为自定义域名添加 SSL 证书
// REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate
setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{
GroupId: tea.String(d.config.GroupId),
DomainName: tea.String(d.config.Domain),
CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
CertificateBody: tea.String(certPem),
CertificatePrivateKey: tea.String(privkeyPem),
}
setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificate(setDomainCertificateReq)
d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'apigateway.SetDomainCertificate'")
}
return nil
}
func (d *DeployerProvider) deployToCloudNative(ctx context.Context, certPem string, privkeyPem string) error {
if d.config.GatewayId == "" {
return errors.New("config `gatewayId` is required")
}
if d.config.Domain == "" {
return errors.New("config `domain` is required")
}
// 遍历查询域名列表,获取域名 ID
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains
var domainId string
listDomainsPageNumber := int32(1)
listDomainsPageSize := int32(10)
for {
listDomainsReq := &aliapig.ListDomainsRequest{
GatewayId: tea.String(d.config.GatewayId),
NameLike: tea.String(d.config.Domain),
PageNumber: tea.Int32(listDomainsPageNumber),
PageSize: tea.Int32(listDomainsPageSize),
}
listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomains(listDomainsReq)
d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'apig.ListDomains'")
}
if listDomainsResp.Body.Data.Items != nil {
for _, domainInfo := range listDomainsResp.Body.Data.Items {
if strings.EqualFold(tea.StringValue(domainInfo.Name), d.config.Domain) {
domainId = tea.StringValue(domainInfo.DomainId)
break
}
}
if domainId != "" {
break
}
}
if listDomainsResp.Body.Data.Items == nil || len(listDomainsResp.Body.Data.Items) < int(listDomainsPageSize) {
break
} else {
listDomainsPageNumber++
}
}
if domainId == "" {
return errors.New("domain not found")
}
// 查询域名
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain
getDomainReq := &aliapig.GetDomainRequest{}
getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomain(tea.String(domainId), getDomainReq)
d.logger.Debug("sdk request 'apig.GetDomain'", slog.Any("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'apig.GetDomain'")
}
// 上传证书到 CAS
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 更新域名
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain
updateDomainReq := &aliapig.UpdateDomainRequest{
Protocol: tea.String("HTTPS"),
ForceHttps: getDomainResp.Body.Data.ForceHttps,
MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled,
Http2Option: getDomainResp.Body.Data.Http2Option,
TlsMin: getDomainResp.Body.Data.TlsMin,
TlsMax: getDomainResp.Body.Data.TlsMax,
TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig,
CertIdentifier: tea.String(upres.ExtendedData["certIdentifier"].(string)),
}
updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomain(tea.String(domainId), updateDomainReq)
d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.Any("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp))
if err != nil {
return xerrors.Wrap(err, "failed to execute sdk request 'apig.UpdateDomain'")
}
return nil
}
func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) {
// 接入点一览 https://api.aliyun.com/product/APIG
cloudNativeAPIGEndpoint := fmt.Sprintf("apig.%s.aliyuncs.com", region)
cloudNativeAPIGConfig := &aliopen.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
Endpoint: tea.String(cloudNativeAPIGEndpoint),
}
cloudNativeAPIGClient, err := aliapig.NewClient(cloudNativeAPIGConfig)
if err != nil {
return nil, err
}
// 接入点一览 https://api.aliyun.com/product/CloudAPI
traditionalAPIGEndpoint := fmt.Sprintf("apigateway.%s.aliyuncs.com", region)
traditionalAPIGConfig := &aliopen.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
Endpoint: tea.String(traditionalAPIGEndpoint),
}
traditionalAPIGClient, err := alicloudapi.NewClient(traditionalAPIGConfig)
if err != nil {
return nil, err
}
return &wSdkClients{
CloudNativeAPIGateway: cloudNativeAPIGClient,
TraditionalAPIGateway: traditionalAPIGClient,
}, nil
}
func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
casRegion := region
if casRegion != "" {
// 阿里云 CAS 服务接入点是独立于 APIGateway 服务的
// 国内版固定接入点:华东一杭州
// 国际版固定接入点:亚太东南一新加坡
if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
casRegion = "ap-southeast-1"
} else {
casRegion = "cn-hangzhou"
}
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: accessKeyId,
AccessKeySecret: accessKeySecret,
Region: casRegion,
})
return uploader, err
}

View File

@@ -0,0 +1,95 @@
package aliyunapigw_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-apigw"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
fRegion string
fServiceType string
fGatewayId string
fGroupId string
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNAPIGW_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
flag.StringVar(&fGatewayId, argsPrefix+"GATEWARYID", "", "")
flag.StringVar(&fGroupId, argsPrefix+"GROUPID", "", "")
flag.StringVar(&fServiceType, argsPrefix+"SERVICETYPE", "", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./aliyun_apigw_test.go -args \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_REGION="cn-hangzhou" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_GATEWAYID="your-api-gateway-id" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_GROUPID="your-api-group-id" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("REGION: %v", fRegion),
fmt.Sprintf("GATEWAYID: %v", fGatewayId),
fmt.Sprintf("GROUPID: %v", fGroupId),
fmt.Sprintf("SERVICETYPE: %v", fServiceType),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
Region: fRegion,
ServiceType: provider.ServiceType(fServiceType),
GatewayId: fGatewayId,
GroupId: fGroupId,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -0,0 +1,10 @@
package aliyunapigw
type ServiceType string
const (
// 服务类型:原 API 网关。
SERVICE_TYPE_TRADITIONAL = ServiceType("traditional")
// 服务类型:云原生 API 网关。
SERVICE_TYPE_CLOUDNATIVE = ServiceType("cloudnative")
)

View File

@@ -2,13 +2,23 @@
import ( import (
"context" "context"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"log/slog" "log/slog"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates"
xerrors "github.com/pkg/errors" xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader" "github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault" uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
azcommon "github.com/usual2970/certimate/internal/pkg/vendors/azure-sdk/common"
) )
type DeployerConfig struct { type DeployerConfig struct {
@@ -22,11 +32,15 @@ type DeployerConfig struct {
CloudName string `json:"cloudName,omitempty"` CloudName string `json:"cloudName,omitempty"`
// Key Vault 名称。 // Key Vault 名称。
KeyVaultName string `json:"keyvaultName"` KeyVaultName string `json:"keyvaultName"`
// Key Vault 证书名称。
// 选填。
CertificateName string `json:"certificateName,omitempty"`
} }
type DeployerProvider struct { type DeployerProvider struct {
config *DeployerConfig config *DeployerConfig
logger *slog.Logger logger *slog.Logger
sdkClient *azcertificates.Client
sslUploader uploader.Uploader sslUploader uploader.Uploader
} }
@@ -37,6 +51,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil") panic("config is nil")
} }
client, err := createSdkClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
TenantId: config.TenantId, TenantId: config.TenantId,
ClientId: config.ClientId, ClientId: config.ClientId,
@@ -51,6 +70,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
return &DeployerProvider{ return &DeployerProvider{
config: config, config: config,
logger: slog.Default(), logger: slog.Default(),
sdkClient: client,
sslUploader: uploader, sslUploader: uploader,
}, nil }, nil
} }
@@ -66,13 +86,93 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
} }
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
// 上传证书到 KeyVault // 解析证书内容
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil { if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file") return nil, err
}
// 转换证书格式
certPfx, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, "")
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate from PEM to PFX")
}
if d.config.CertificateName == "" {
// 上传证书到 KeyVault
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
} else { } else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) // 获取证书
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate
getCertificateResp, err := d.sdkClient.GetCertificate(context.TODO(), d.config.CertificateName, "", nil)
d.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("response", getCertificateResp))
if err != nil {
var respErr *azcore.ResponseError
if !errors.As(err, &respErr) || (respErr.ErrorCode != "ResourceNotFound" && respErr.ErrorCode != "CertificateNotFound") {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.GetCertificate'")
}
} else {
oldCertX509, err := x509.ParseCertificate(getCertificateResp.CER)
if err == nil {
if certutil.EqualCertificate(certX509, oldCertX509) {
return &deployer.DeployResult{}, nil
}
}
}
// 导入证书
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
importCertificateParams := azcertificates.ImportCertificateParameters{
Base64EncodedCertificate: to.Ptr(base64.StdEncoding.EncodeToString(certPfx)),
CertificatePolicy: &azcertificates.CertificatePolicy{
SecretProperties: &azcertificates.SecretProperties{
ContentType: to.Ptr("application/x-pkcs12"),
},
},
Tags: map[string]*string{
"certimate/cert-cn": to.Ptr(certX509.Subject.CommonName),
"certimate/cert-sn": to.Ptr(certX509.SerialNumber.Text(16)),
},
}
importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), d.config.CertificateName, importCertificateParams, nil)
d.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.ImportCertificate'")
}
} }
return &deployer.DeployResult{}, nil return &deployer.DeployResult{}, nil
} }
func createSdkClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) {
env, err := azcommon.GetCloudEnvironmentConfiguration(cloudName)
if err != nil {
return nil, err
}
clientOptions := azcore.ClientOptions{Cloud: env}
credential, err := azidentity.NewClientSecretCredential(tenantId, clientId, clientSecret,
&azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
if err != nil {
return nil, err
}
endpoint := fmt.Sprintf("https://%s.vault.azure.net", keyvaultName)
if azcommon.IsEnvironmentGovernment(cloudName) {
endpoint = fmt.Sprintf("https://%s.vault.usgovcloudapi.net", keyvaultName)
} else if azcommon.IsEnvironmentChina(cloudName) {
endpoint = fmt.Sprintf("https://%s.vault.azure.cn", keyvaultName)
}
client, err := azcertificates.NewClient(endpoint, credential, nil)
if err != nil {
return nil, err
}
return client, nil
}

View File

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

View File

@@ -0,0 +1,75 @@
package bunnycdn_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn"
)
var (
fInputCertPath string
fInputKeyPath string
fApiKey string
fPullZoneId string
fHostName string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_BUNNYCDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fPullZoneId, argsPrefix+"PULLZONEID", "", "")
flag.StringVar(&fHostName, argsPrefix+"HOSTNAME", "", "")
}
/*
Shell command to run this test:
go test -v ./bunny_cdn_test.go -args \
--CERTIMATE_DEPLOYER_BUNNYCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_BUNNYCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_BUNNYCDN_APITOKEN="your-api-token" \
--CERTIMATE_DEPLOYER_BUNNYCDN_PULLZONEID="your-pull-zone-id" \
--CERTIMATE_DEPLOYER_BUNNYCDN_HOSTNAME="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("PULLZONEID: %v", fPullZoneId),
fmt.Sprintf("HOSTNAME: %v", fHostName),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiKey: fApiKey,
PullZoneId: fPullZoneId,
HostName: fHostName,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -4,12 +4,12 @@ import (
"context" "context"
"log/slog" "log/slog"
edgio "github.com/Edgio/edgio-api/applications/v7"
edgiodtos "github.com/Edgio/edgio-api/applications/v7/dtos"
xerrors "github.com/pkg/errors" xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil" "github.com/usual2970/certimate/internal/pkg/utils/certutil"
edgsdk "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7"
edgsdkdtos "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
) )
type DeployerConfig struct { type DeployerConfig struct {
@@ -24,7 +24,7 @@ type DeployerConfig struct {
type DeployerProvider struct { type DeployerProvider struct {
config *DeployerConfig config *DeployerConfig
logger *slog.Logger logger *slog.Logger
sdkClient *edgsdk.EdgioClient sdkClient *edgio.EdgioClient
} }
var _ deployer.Deployer = (*DeployerProvider)(nil) var _ deployer.Deployer = (*DeployerProvider)(nil)
@@ -64,7 +64,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
// 上传 TLS 证书 // 上传 TLS 证书
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts // REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
uploadTlsCertReq := edgsdkdtos.UploadTlsCertRequest{ uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
EnvironmentID: d.config.EnvironmentId, EnvironmentID: d.config.EnvironmentId,
PrimaryCert: privateCertPem, PrimaryCert: privateCertPem,
IntermediateCert: intermediateCertPem, IntermediateCert: intermediateCertPem,
@@ -79,7 +79,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
return &deployer.DeployResult{}, nil return &deployer.DeployResult{}, nil
} }
func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error) { func createSdkClient(clientId, clientSecret string) (*edgio.EdgioClient, error) {
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "") client := edgio.NewEdgioClient(clientId, clientSecret, "", "")
return client, nil return client, nil
} }

View File

@@ -100,9 +100,15 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
SSlEnabled: true, SSlEnabled: true,
SSLData: int(updateResourceCertId), SSLData: int(updateResourceCertId),
ProxySSLEnabled: getResourceResp.ProxySSLEnabled, ProxySSLEnabled: getResourceResp.ProxySSLEnabled,
ProxySSLCA: &getResourceResp.ProxySSLCA, }
ProxySSLData: &getResourceResp.ProxySSLData, if getResourceResp.ProxySSLCA != 0 {
Options: getResourceResp.Options, updateResourceReq.ProxySSLCA = &getResourceResp.ProxySSLCA
}
if getResourceResp.ProxySSLData != 0 {
updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData
}
if getResourceResp.Options != nil {
updateResourceReq.Options = getResourceResp.Options
} }
updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq) updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq)
d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp)) d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp))

View File

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

View File

@@ -0,0 +1,75 @@
package rainyunrcdn_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
)
var (
fInputCertPath string
fInputKeyPath string
fApiKey string
fInstanceId int64
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_RAINYUNRCDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.Int64Var(&fInstanceId, argsPrefix+"INSTANCEID", 0, "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./ucloud_ucdn_test.go -args \
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_RAINYUNRCDN_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INSTANCEID="your-rcdn-instance-id" \
--CERTIMATE_DEPLOYER_RAINYUNRCDN_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("INSTANCEID: %v", fInstanceId),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
PrivateKey: fApiKey,
InstanceId: fInstanceId,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -243,7 +243,6 @@ func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error {
if err != nil { if err != nil {
return xerrors.Wrap(err, "failed to create scp client") return xerrors.Wrap(err, "failed to create scp client")
} }
defer scpCli.Close()
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{}) err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{})

View File

@@ -107,7 +107,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
// REF: https://cloud.tencent.com/document/product/400/91667 // REF: https://cloud.tencent.com/document/product/400/91667
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest() deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn") deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
deployCertificateInstanceReq.Status = common.Int64Ptr(1) deployCertificateInstanceReq.Status = common.Int64Ptr(1)
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds) deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
deployCertificateInstanceResp, err := d.sdkClients.SSL.DeployCertificateInstance(deployCertificateInstanceReq) deployCertificateInstanceResp, err := d.sdkClients.SSL.DeployCertificateInstance(deployCertificateInstanceReq)

View File

@@ -182,7 +182,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
return errors.New("config `listenerId` is required") return errors.New("config `listenerId` is required")
} }
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil { if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
return err return err
} }

View File

@@ -178,7 +178,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
return errors.New("config `listenerId` is required") return errors.New("config `listenerId` is required")
} }
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil { if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
return err return err
} }

View File

@@ -0,0 +1,281 @@
package wangsucdnpro
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"regexp"
"strconv"
"time"
"github.com/alibabacloud-go/tea/tea"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
wangsucdn "github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/cdn"
)
type DeployerConfig struct {
// 网宿云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 网宿云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 网宿云 API Key。
ApiKey string `json:"apiKey"`
// 网宿云环境。
Environment string `json:"environment"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
// 证书 ID。
// 选填。
CertificateId string `json:"certificateId,omitempty"`
// Webhook ID。
// 选填。
WebhookId string `json:"webhookId,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *wangsucdn.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
if logger == nil {
d.logger = slog.Default()
} else {
d.logger = logger
}
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
if d.config.Domain == "" {
return nil, errors.New("config `domain` is required")
}
// 解析证书内容
certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 查询已部署加速域名的详情
getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain)
d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetHostnameDetail'")
}
// 生成网宿云证书参数
encryptedPrivateKey, err := encryptPrivateKey(privkeyPem, d.config.ApiKey, time.Now().Unix())
if err != nil {
return nil, xerrors.Wrap(err, "failed to encrypt private key")
}
certificateNewVersionInfo := &wangsucdn.CertificateVersion{
PrivateKey: tea.String(encryptedPrivateKey),
Certificate: tea.String(certPem),
IdentificationInfo: &wangsucdn.CertificateVersionIdentificationInfo{
CommonName: tea.String(certX509.Subject.CommonName),
SubjectAlternativeNames: &certX509.DNSNames,
},
}
// 网宿云证书 URL 中包含证书 ID 及版本号
// 格式:
// http://open.chinanetcenter.com/cdn/certificates/5dca2205f9e9cc0001df7b33
// http://open.chinanetcenter.com/cdn/certificates/329f12c1fe6708c23c31e91f/versions/5
var wangsuCertUrl string
var wangsuCertId string
var wangsuCertVer int32
// 如果原证书 ID 为空,则创建证书;否则更新证书。
timestamp := time.Now().Unix()
if d.config.CertificateId == "" {
// 创建证书
createCertificateReq := &wangsucdn.CreateCertificateRequest{
Timestamp: timestamp,
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
AutoRenew: tea.String("Off"),
NewVersion: certificateNewVersionInfo,
}
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateCertificate'")
}
wangsuCertUrl = createCertificateResp.CertificateUrl
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
if len(wangsuCertIdMatches) > 1 {
wangsuCertId = wangsuCertIdMatches[1]
}
wangsuCertVer = 1
} else {
// 更新证书
updateCertificateReq := &wangsucdn.UpdateCertificateRequest{
Timestamp: timestamp,
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
AutoRenew: tea.String("Off"),
NewVersion: certificateNewVersionInfo,
}
updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UpdateCertificate'")
}
wangsuCertUrl = updateCertificateResp.CertificateUrl
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
if len(wangsuCertIdMatches) > 1 {
wangsuCertId = wangsuCertIdMatches[1]
}
wangsuCertVerMatches := regexp.MustCompile(`/versions/(\d+)`).FindStringSubmatch(wangsuCertUrl)
if len(wangsuCertVerMatches) > 1 {
n, _ := strconv.ParseInt(wangsuCertVerMatches[1], 10, 32)
wangsuCertVer = int32(n)
}
}
// 创建部署任务
// REF: https://www.wangsu.com/document/api-doc/27034
createDeploymentTaskReq := &wangsucdn.CreateDeploymentTaskRequest{
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
Target: tea.String(d.config.Environment),
Actions: &[]wangsucdn.DeploymentTaskAction{
{
Action: tea.String("deploy_cert"),
CertificateId: tea.String(wangsuCertId),
Version: tea.Int32(wangsuCertVer),
},
},
}
if d.config.WebhookId != "" {
createDeploymentTaskReq.Webhook = tea.String(d.config.WebhookId)
}
createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq)
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateDeploymentTask'")
}
// 循环获取部署任务详细信息,等待任务状态变更
// REF: https://www.wangsu.com/document/api-doc/27038
var wangsuTaskId string
wangsuTaskMatches := regexp.MustCompile(`/deploymentTasks/([a-zA-Z0-9-]+)`).FindStringSubmatch(createDeploymentTaskResp.DeploymentTaskUrl)
if len(wangsuTaskMatches) > 1 {
wangsuTaskId = wangsuTaskMatches[1]
}
for {
if ctx.Err() != nil {
return nil, ctx.Err()
}
getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId)
d.logger.Info("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDeploymentTaskDetail'")
}
if getDeploymentTaskDetailResp.Status == "failed" {
return nil, errors.New("unexpected deployment task status")
} else if getDeploymentTaskDetailResp.Status == "succeeded" || getDeploymentTaskDetailResp.FinishTime != "" {
break
}
d.logger.Info(fmt.Sprintf("waiting for deployment task completion (current status: %s) ...", getDeploymentTaskDetailResp.Status))
time.Sleep(time.Second * 5)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, accessKeySecret string) (*wangsucdn.Client, error) {
if accessKeyId == "" {
return nil, errors.New("invalid wangsu access key id")
}
if accessKeySecret == "" {
return nil, errors.New("invalid wangsu access key secret")
}
return wangsucdn.NewClient(accessKeyId, accessKeySecret), nil
}
func encryptPrivateKey(privkeyPem string, apiKey string, timestamp int64) (string, error) {
date := time.Unix(timestamp, 0).UTC()
dateStr := date.Format("Mon, 02 Jan 2006 15:04:05 GMT")
mac := hmac.New(sha256.New, []byte(apiKey))
mac.Write([]byte(dateStr))
aesivkey := mac.Sum(nil)
aesivkeyHex := hex.EncodeToString(aesivkey)
if len(aesivkeyHex) != 64 {
return "", fmt.Errorf("invalid hmac length: %d", len(aesivkeyHex))
}
ivHex := aesivkeyHex[:32]
keyHex := aesivkeyHex[32:64]
iv, err := hex.DecodeString(ivHex)
if err != nil {
return "", fmt.Errorf("failed to decode iv: %w", err)
}
key, err := hex.DecodeString(keyHex)
if err != nil {
return "", fmt.Errorf("failed to decode key: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
plainBytes := []byte(privkeyPem)
padlen := aes.BlockSize - len(plainBytes)%aes.BlockSize
if padlen > 0 {
paddata := bytes.Repeat([]byte{byte(padlen)}, padlen)
plainBytes = append(plainBytes, paddata...)
}
encBytes := make([]byte, len(plainBytes))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encBytes, plainBytes)
return base64.StdEncoding.EncodeToString(encBytes), nil
}

View File

@@ -0,0 +1,95 @@
package wangsucdnpro_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
fApiKey string
fEnvironment string
fDomain string
fCertificateId string
fWebhookId string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDNPRO_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fEnvironment, argsPrefix+"ENVIRONMENT", "production", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
flag.StringVar(&fWebhookId, argsPrefix+"WEBHOOKID", "", "")
}
/*
Shell command to run this test:
go test -v ./wangsu_cdnpro_test.go -args \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYSECRET="your-access-key-secret" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ENVIRONMENT="production" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_DOMAIN="example.com" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_CERTIFICATEID="your-certificate-id" \
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_WEBHOOKID="your-webhook-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("ENVIRONMENT: %v", fEnvironment),
fmt.Sprintf("DOMAIN: %v", fDomain),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
fmt.Sprintf("WEBHOOKID: %v", fWebhookId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
ApiKey: fApiKey,
Environment: fEnvironment,
Domain: fDomain,
CertificateId: fCertificateId,
WebhookId: fWebhookId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

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

View File

@@ -0,0 +1,74 @@
package mattermost_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fServerUrl string
fChannelId string
fUsername string
fPassword string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_MATTERMOST_"
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", "", "")
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
}
/*
Shell command to run this test:
go test -v ./mattermost_test.go -args \
--CERTIMATE_NOTIFIER_MATTERMOST_SERVERURL="https://example.com/your-server-url" \
--CERTIMATE_NOTIFIER_MATTERMOST_CHANNELID="your-chanel-id" \
--CERTIMATE_NOTIFIER_MATTERMOST_USERNAME="your-username" \
--CERTIMATE_NOTIFIER_MATTERMOST_PASSWORD="your-password"
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("CHANNELID: %v", fChannelId),
fmt.Sprintf("USERNAME: %v", fUsername),
fmt.Sprintf("PASSWORD: %v", fPassword),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
ServerUrl: fServerUrl,
ChannelId: fChannelId,
Username: fUsername,
Password: fPassword,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

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

View File

@@ -0,0 +1,62 @@
package pushover_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fToken string
fUser string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_PUSHOVER_"
flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "")
flag.StringVar(&fUser, argsPrefix+"USER", "", "")
}
/*
Shell command to run this test:
go test -v ./pushover_test.go -args \
--CERTIMATE_NOTIFIER_PUSHOVER_TOKEN="your-pushover-token" \
--CERTIMATE_NOTIFIER_PUSHOVER_USER="your-pushover-user" \
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("TOKEN: %v", fToken),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
Token: fToken,
User: fUser,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@@ -58,7 +58,7 @@ func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 遍历证书列表,避免重复上传 // 遍历证书列表,避免重复上传
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil { if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
return nil, err return nil, err
} else if res != nil { } else if res != nil {
u.logger.Info("ssl certificate already exists") u.logger.Info("ssl certificate already exists")
@@ -82,7 +82,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
// 遍历证书列表,获取刚刚上传证书 ID // 遍历证书列表,获取刚刚上传证书 ID
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil { if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
return nil, err return nil, err
} else if res == nil { } else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage()) return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage())
@@ -91,7 +91,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
} }
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
searchWebsiteSSLPageNumber := int32(1) searchWebsiteSSLPageNumber := int32(1)
searchWebsiteSSLPageSize := int32(100) searchWebsiteSSLPageSize := int32(100)
for { for {

View File

@@ -3,6 +3,7 @@
import ( import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/base64"
"fmt" "fmt"
"log/slog" "log/slog"
"time" "time"
@@ -141,13 +142,21 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
// 生成新证书名(需符合 Azure 命名规则) // 生成新证书名(需符合 Azure 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// Azure Key Vault 不支持导入带有 Certificiate Chain 的 PEM 证书。
// Issue Link: https://github.com/Azure/azure-cli/issues/19017
// 暂时的解决方法是,将 PEM 证书转换成 PFX 格式,然后再导入。
certPfx, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, "")
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate from PEM to PFX")
}
// 导入证书 // 导入证书
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
importCertificateParams := azcertificates.ImportCertificateParameters{ importCertificateParams := azcertificates.ImportCertificateParameters{
Base64EncodedCertificate: to.Ptr(certPem), Base64EncodedCertificate: to.Ptr(base64.StdEncoding.EncodeToString(certPfx)),
CertificatePolicy: &azcertificates.CertificatePolicy{ CertificatePolicy: &azcertificates.CertificatePolicy{
SecretProperties: &azcertificates.SecretProperties{ SecretProperties: &azcertificates.SecretProperties{
ContentType: to.Ptr("application/x-pem-file"), ContentType: to.Ptr("application/x-pkcs12"),
}, },
}, },
Tags: map[string]*string{ Tags: map[string]*string{

View File

@@ -0,0 +1,87 @@
package azurekeyvault_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault"
)
var (
fInputCertPath string
fInputKeyPath string
fTenantId string
fClientId string
fClientSecret string
fCloudName string
fKeyVaultName string
)
func init() {
argsPrefix := "CERTIMATE_UPLOADER_AZUREKEYVAULT_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fTenantId, argsPrefix+"TENANTID", "", "")
flag.StringVar(&fClientId, argsPrefix+"CLIENTID", "", "")
flag.StringVar(&fClientSecret, argsPrefix+"CLIENTSECRET", "", "")
flag.StringVar(&fCloudName, argsPrefix+"CLOUDNAME", "", "")
flag.StringVar(&fKeyVaultName, argsPrefix+"KEYVAULTNAME", "", "")
}
/*
Shell command to run this test:
go test -v ./azure_keyvault_test.go -args \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_TENANTID="your-tenant-id" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLIENTID="your-app-registration-client-id" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLIENTSECRET="your-app-registration-client-secret" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLOUDNAME="china" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_KEYVAULTNAME="your-keyvault-name"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("TENANTID: %v", fTenantId),
fmt.Sprintf("CLIENTID: %v", fClientId),
fmt.Sprintf("CLIENTSECRET: %v", fClientSecret),
fmt.Sprintf("CLOUDNAME: %v", fCloudName),
fmt.Sprintf("KEYVAULTNAME: %v", fKeyVaultName),
}, "\n"))
uploader, err := provider.NewUploader(&provider.UploaderConfig{
TenantId: fTenantId,
ClientId: fClientId,
ClientSecret: fClientSecret,
CloudName: fCloudName,
KeyVaultName: fKeyVaultName,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
sres, _ := json.Marshal(res)
t.Logf("ok: %s", string(sres))
})
}

View File

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

View File

@@ -0,0 +1,67 @@
package rainyunsslcenter_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/rainyun-sslcenter"
)
var (
fInputCertPath string
fInputKeyPath string
fApiKey string
)
func init() {
argsPrefix := "CERTIMATE_UPLOADER_RAINYUNSSLCENTER_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./rainyun_sslcenter_test.go -args \
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_APIKEY="your-api-key"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("APIKEY: %v", fApiKey),
}, "\n"))
uploader, err := provider.NewUploader(&provider.UploaderConfig{
ApiKey: fApiKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
sres, _ := json.Marshal(res)
t.Logf("ok: %s", string(sres))
})
}

View File

@@ -89,10 +89,10 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
u.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp)) u.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp))
if err != nil { if err != nil {
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 {
if res, err := u.getExistCert(ctx, certPem); err != nil { if res, err := u.getCertIfExists(ctx, certPem); err != nil {
return nil, err return nil, err
} else if res == nil { } else if res == nil {
return nil, errors.New("no certificate found") return nil, errors.New("ucloud ssl: no certificate found")
} else { } else {
u.logger.Info("ssl certificate already exists") u.logger.Info("ssl certificate already exists")
return res, nil return res, nil
@@ -112,7 +112,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
}, nil }, nil
} }
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) { func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容 // 解析证书内容
certX509, err := certutil.ParseCertificateFromPEM(certPem) certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil { if err != nil {

View File

@@ -72,12 +72,17 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
var certId string var certId string
if importCertificateResp.InstanceId != nil { if importCertificateResp.InstanceId != nil && *importCertificateResp.InstanceId != "" {
certId = *importCertificateResp.InstanceId certId = *importCertificateResp.InstanceId
} }
if importCertificateResp.RepeatId != nil { if importCertificateResp.RepeatId != nil && *importCertificateResp.RepeatId != "" {
certId = *importCertificateResp.RepeatId certId = *importCertificateResp.RepeatId
} }
if certId == "" {
return nil, xerrors.New("failed to get certificate id, both `InstanceId` and `RepeatId` are empty")
}
return &uploader.UploadResult{ return &uploader.UploadResult{
CertId: certId, CertId: certId,
}, nil }, nil

View File

@@ -74,6 +74,18 @@ func GetOrDefaultInt32(dict map[string]any, key string, defaultValue int32) int3
} }
} }
if result, ok := value.(int64); ok {
if result != 0 {
return int32(result)
}
}
if result, ok := value.(int); ok {
if result != 0 {
return int32(result)
}
}
// 兼容字符串类型的值 // 兼容字符串类型的值
if str, ok := value.(string); ok { if str, ok := value.(string); ok {
if result, err := strconv.ParseInt(str, 10, 32); err == nil { if result, err := strconv.ParseInt(str, 10, 32); err == nil {
@@ -126,6 +138,12 @@ func GetOrDefaultInt64(dict map[string]any, key string, defaultValue int64) int6
} }
} }
if result, ok := value.(int); ok {
if result != 0 {
return int64(result)
}
}
// 兼容字符串类型的值 // 兼容字符串类型的值
if str, ok := value.(string); ok { if str, ok := value.(string); ok {
if result, err := strconv.ParseInt(str, 10, 64); err == nil { if result, err := strconv.ParseInt(str, 10, 64); err == nil {

View File

@@ -17,6 +17,12 @@ func (c *Client) SearchWebsiteSSL(req *SearchWebsiteSSLRequest) (*SearchWebsiteS
return resp, err return resp, err
} }
func (c *Client) GetWebsiteSSL(req *GetWebsiteSSLRequest) (*GetWebsiteSSLResponse, error) {
resp := &GetWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/ssl/%d", req.SSLID), req, resp)
return resp, err
}
func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) { func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) {
resp := &UploadWebsiteSSLResponse{} resp := &UploadWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp) err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp)

View File

@@ -79,7 +79,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("1panel api error: failed to send request: %w", err) return resp, fmt.Errorf("1panel api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("1panel api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("1panel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -59,6 +59,28 @@ type SearchWebsiteSSLResponse struct {
} `json:"data,omitempty"` } `json:"data,omitempty"`
} }
type GetWebsiteSSLRequest struct {
SSLID int64 `json:"-"`
}
type GetWebsiteSSLResponse struct {
baseResponse
Data *struct {
ID int64 `json:"id"`
Provider string `json:"provider"`
Description string `json:"description"`
PrimaryDomain string `json:"primaryDomain"`
Domains string `json:"domains"`
Type string `json:"type"`
Organization string `json:"organization"`
Status string `json:"status"`
StartDate string `json:"startDate"`
ExpireDate string `json:"expireDate"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
} `json:"data,omitempty"`
}
type UploadWebsiteSSLRequest struct { type UploadWebsiteSSLRequest struct {
Type string `json:"type"` Type string `json:"type"`
SSLID int64 `json:"sslID"` SSLID int64 `json:"sslID"`

View File

@@ -75,7 +75,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("baishan api error: failed to send request: %w", err) return resp, fmt.Errorf("baishan api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("baishan api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("baishan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -86,7 +86,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("baota api error: failed to send request: %w", err) return resp, fmt.Errorf("baota api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("baota api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

11
internal/pkg/vendors/bunny-sdk/api.go vendored Normal file
View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package bunnysdk
type AddCustomCertificateRequest struct {
Hostname string `json:"Hostname"`
PullZoneId string `json:"-"`
Certificate string `json:"Certificate"`
CertificateKey string `json:"CertificateKey"`
}

View File

@@ -59,7 +59,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err) return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -3,17 +3,18 @@ package cdnflysdk
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
) )
func (c *Client) GetSite(req *GetSiteRequest) (*GetSiteResponse, error) { func (c *Client) GetSite(req *GetSiteRequest) (*GetSiteResponse, error) {
resp := &GetSiteResponse{} resp := &GetSiteResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/v1/sites/%s", req.Id), req, resp) err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/v1/sites/%s", url.PathEscape(req.Id)), req, resp)
return resp, err return resp, err
} }
func (c *Client) UpdateSite(req *UpdateSiteRequest) (*UpdateSiteResponse, error) { func (c *Client) UpdateSite(req *UpdateSiteRequest) (*UpdateSiteResponse, error) {
resp := &UpdateSiteResponse{} resp := &UpdateSiteResponse{}
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/sites/%s", req.Id), req, resp) err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/sites/%s", url.PathEscape(req.Id)), req, resp)
return resp, err return resp, err
} }
@@ -25,6 +26,6 @@ func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertif
func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
resp := &UpdateCertificateResponse{} resp := &UpdateCertificateResponse{}
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/certs/%s", req.Id), req, resp) err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/certs/%s", url.PathEscape(req.Id)), req, resp)
return resp, err return resp, err
} }

View File

@@ -65,7 +65,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err) return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -1,6 +1,6 @@
package cdnflysdk package cdnflysdk
import "encoding/json" import "fmt"
type BaseResponse interface { type BaseResponse interface {
GetCode() string GetCode() string
@@ -8,12 +8,24 @@ type BaseResponse interface {
} }
type baseResponse struct { type baseResponse struct {
Code json.Number `json:"code"` Code any `json:"code"`
Message string `json:"msg"` Message string `json:"msg"`
} }
func (r *baseResponse) GetCode() string { func (r *baseResponse) GetCode() string {
return r.Code.String() if r.Code == nil {
return ""
}
if code, ok := r.Code.(int); ok {
return fmt.Sprintf("%d", code)
}
if code, ok := r.Code.(string); ok {
return code
}
return ""
} }
func (r *baseResponse) GetMessage() string { func (r *baseResponse) GetMessage() string {

View File

@@ -60,7 +60,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err) return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -6,9 +6,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/Edgio/edgio-api/applications/v7/dtos"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
) )
// AccessTokenResponse represents the response from the token endpoint. // AccessTokenResponse represents the response from the token endpoint.

View File

@@ -3,7 +3,7 @@ package edgio_api
import ( import (
"context" "context"
"github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos" "github.com/Edgio/edgio-api/applications/v7/dtos"
) )
type EdgioClientInterface interface { type EdgioClientInterface interface {

View File

@@ -0,0 +1,3 @@
module github.com/Edgio/edgio-api
go 1.23.0

View File

@@ -82,7 +82,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("gname api error: failed to send request: %w", err) return resp, fmt.Errorf("gname api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("gname api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("gname api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

30
internal/pkg/vendors/rainyun-sdk/api.go vendored Normal file
View File

@@ -0,0 +1,30 @@
package rainyunsdk
import (
"fmt"
"net/http"
)
func (c *Client) SslCenterList(req *SslCenterListRequest) (*SslCenterListResponse, error) {
resp := &SslCenterListResponse{}
err := c.sendRequestWithResult(http.MethodGet, "/product/sslcenter", req, resp)
return resp, err
}
func (c *Client) SslCenterGet(id int32) (*SslCenterGetResponse, error) {
resp := &SslCenterGetResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/product/sslcenter/%d", id), nil, resp)
return resp, err
}
func (c *Client) SslCenterCreate(req *SslCenterCreateRequest) (*SslCenterCreateResponse, error) {
resp := &SslCenterCreateResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/product/sslcenter/", req, resp)
return resp, err
}
func (c *Client) RcdnInstanceSslBind(id int32, req *RcdnInstanceSslBindRequest) (*RcdnInstanceSslBindResponse, error) {
resp := &RcdnInstanceSslBindResponse{}
err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/product/rcdn/instance/%d/ssl_bind", id), req, resp)
return resp, err
}

View File

@@ -0,0 +1,74 @@
package rainyunsdk
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiKey string
client *resty.Client
}
func NewClient(apiKey string) *Client {
client := resty.New()
return &Client{
apiKey: apiKey,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
req := c.client.R().SetHeader("x-api-key", c.apiKey)
req.Method = method
req.URL = "https://api.v2.rainyun.com" + path
if strings.EqualFold(method, http.MethodGet) {
if params != nil {
jsonb, _ := json.Marshal(params)
req = req.SetQueryParam("options", string(jsonb))
}
} else {
req = req.
SetHeader("Content-Type", "application/json").
SetBody(params)
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("rainyun api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("rainyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error {
resp, err := c.sendRequest(method, path, params)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &result)
}
return err
}
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return fmt.Errorf("rainyun api error: failed to parse response: %w", err)
} else if errcode := result.GetCode(); errcode/100 != 2 {
return fmt.Errorf("rainyun api error: %d - %s", errcode, result.GetMessage())
}
return nil
}

View File

@@ -0,0 +1,83 @@
package rainyunsdk
type BaseResponse interface {
GetCode() int32
GetMessage() string
}
type baseResponse struct {
Code *int32 `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
func (r *baseResponse) GetCode() int32 {
if r.Code != nil {
return *r.Code
}
return 0
}
func (r *baseResponse) GetMessage() string {
if r.Message != nil {
return *r.Message
}
return ""
}
type SslCenterListFilters struct {
Domain *string `json:"Domain,omitempty"`
}
type SslCenterListRequest struct {
Filters *SslCenterListFilters `json:"columnFilters,omitempty"`
Sort []*string `json:"sort,omitempty"`
Page *int32 `json:"page,omitempty"`
PerPage *int32 `json:"perPage,omitempty"`
}
type SslCenterListResponse struct {
baseResponse
Data *struct {
TotalRecords int32 `json:"TotalRecords"`
Records []*struct {
ID int32 `json:"ID"`
UID int32 `json:"UID"`
Domain string `json:"Domain"`
Issuer string `json:"Issuer"`
StartDate int64 `json:"StartDate"`
ExpireDate int64 `json:"ExpDate"`
UploadTime int64 `json:"UploadTime"`
} `json:"Records"`
} `json:"data,omitempty"`
}
type SslCenterGetResponse struct {
baseResponse
Data *struct {
Cert string `json:"Cert"`
Key string `json:"Key"`
Domain string `json:"DomainName"`
Issuer string `json:"Issuer"`
StartDate int64 `json:"StartDate"`
ExpireDate int64 `json:"ExpDate"`
RemainDays int32 `json:"RemainDays"`
} `json:"data,omitempty"`
}
type SslCenterCreateRequest struct {
Cert string `json:"cert"`
Key string `json:"key"`
}
type SslCenterCreateResponse struct {
baseResponse
}
type RcdnInstanceSslBindRequest struct {
CertId int32 `json:"cert_id"`
Domains []string `json:"domains"`
}
type RcdnInstanceSslBindResponse struct {
baseResponse
}

View File

@@ -47,7 +47,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("safeline api error: failed to send request: %w", err) return resp, fmt.Errorf("safeline api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("safeline api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("safeline api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -64,7 +64,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("upyun api error: failed to send request: %w", err) return resp, fmt.Errorf("upyun api error: failed to send request: %w", err)
} else if resp.IsError() { } else if resp.IsError() {
return resp, fmt.Errorf("upyun api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) return resp, fmt.Errorf("upyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
} }
return resp, nil return resp, nil

View File

@@ -0,0 +1,70 @@
package cdn
import (
"fmt"
"net/http"
"net/url"
"github.com/go-resty/resty/v2"
)
func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
resp := &CreateCertificateResponse{}
r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) {
r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp))
})
if err != nil {
return resp, err
}
resp.CertificateUrl = r.Header().Get("Location")
return resp, err
}
func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
if certificateId == "" {
return nil, fmt.Errorf("invalid parameter: certificateId")
}
resp := &UpdateCertificateResponse{}
r, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) {
r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp))
})
if err != nil {
return resp, err
}
resp.CertificateUrl = r.Header().Get("Location")
return resp, err
}
func (c *Client) GetHostnameDetail(hostname string) (*GetHostnameDetailResponse, error) {
if hostname == "" {
return nil, fmt.Errorf("invalid parameter: hostname")
}
resp := &GetHostnameDetailResponse{}
_, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/hostnames/%s", url.PathEscape(hostname)), nil, resp)
return resp, err
}
func (c *Client) CreateDeploymentTask(req *CreateDeploymentTaskRequest) (*CreateDeploymentTaskResponse, error) {
resp := &CreateDeploymentTaskResponse{}
r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp)
if err != nil {
return resp, err
}
resp.DeploymentTaskUrl = r.Header().Get("Location")
return resp, err
}
func (c *Client) GetDeploymentTaskDetail(deploymentTaskId string) (*GetDeploymentTaskDetailResponse, error) {
if deploymentTaskId == "" {
return nil, fmt.Errorf("invalid parameter: deploymentTaskId")
}
resp := &GetDeploymentTaskDetailResponse{}
_, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/deploymentTasks/%s", url.PathEscape(deploymentTaskId)), nil, resp)
return resp, err
}

View File

@@ -0,0 +1,20 @@
package cdn
import (
"time"
"github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/openapi"
)
type Client struct {
client *openapi.Client
}
func NewClient(accessKey, secretKey string) *Client {
return &Client{client: openapi.NewClient(accessKey, secretKey)}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.WithTimeout(timeout)
return c
}

View File

@@ -0,0 +1,108 @@
package cdn
import (
"github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/openapi"
)
type baseResponse struct {
RequestId *string `json:"-"`
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
var _ openapi.Result = (*baseResponse)(nil)
func (r *baseResponse) SetRequestId(requestId string) {
r.RequestId = &requestId
}
type CertificateVersion struct {
Comments *string `json:"comments,omitempty"`
PrivateKey *string `json:"privateKey,omitempty"`
Certificate *string `json:"certificate,omitempty"`
ChainCert *string `json:"chainCert,omitempty"`
IdentificationInfo *CertificateVersionIdentificationInfo `json:"identificationInfo,omitempty"`
}
type CertificateVersionIdentificationInfo struct {
Country *string `json:"country,omitempty"`
State *string `json:"state,omitempty"`
City *string `json:"city,omitempty"`
Company *string `json:"company,omitempty"`
Department *string `json:"department,omitempty"`
CommonName *string `json:"commonName,omitempty" required:"true"`
Email *string `json:"email,omitempty"`
SubjectAlternativeNames *[]string `json:"subjectAlternativeNames,omitempty" required:"true"`
}
type CreateCertificateRequest struct {
Timestamp int64 `json:"-"`
Name *string `json:"name,omitempty" required:"true"`
Description *string `json:"description,omitempty"`
AutoRenew *string `json:"autoRenew,omitempty"`
ForceRenew *bool `json:"forceRenew,omitempty"`
NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"`
}
type CreateCertificateResponse struct {
baseResponse
CertificateUrl string `json:"-"`
}
type UpdateCertificateRequest struct {
Timestamp int64 `json:"-"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
AutoRenew *string `json:"autoRenew,omitempty"`
ForceRenew *bool `json:"forceRenew,omitempty"`
NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"`
}
type UpdateCertificateResponse struct {
baseResponse
CertificateUrl string `json:"-"`
}
type HostnameProperty struct {
PropertyId string `json:"propertyId"`
Version int32 `json:"version"`
CertificateId *string `json:"certificateId,omitempty"`
}
type GetHostnameDetailResponse struct {
baseResponse
Hostname string `json:"hostname"`
PropertyInProduction *HostnameProperty `json:"propertyInProduction,omitempty"`
PropertyInStaging *HostnameProperty `json:"propertyInStaging,omitempty"`
}
type DeploymentTaskAction struct {
Action *string `json:"action,omitempty" required:"true"`
PropertyId *string `json:"propertyId,omitempty"`
CertificateId *string `json:"certificateId,omitempty"`
Version *int32 `json:"version,omitempty"`
}
type CreateDeploymentTaskRequest struct {
Name *string `json:"name,omitempty"`
Target *string `json:"target,omitempty" required:"true"`
Actions *[]DeploymentTaskAction `json:"actions,omitempty" required:"true"`
Webhook *string `json:"webhook,omitempty"`
}
type CreateDeploymentTaskResponse struct {
baseResponse
DeploymentTaskUrl string `json:"-"`
}
type GetDeploymentTaskDetailResponse struct {
baseResponse
Name string `json:"name"`
Target string `json:"target"`
Actions []DeploymentTaskAction `json:"actions"`
Status string `json:"status"`
StatusDetails string `json:"statusDetails"`
SubmissionTime string `json:"submissionTime"`
FinishTime string `json:"finishTime"`
ApiRequestId string `json:"apiRequestId"`
}

View File

@@ -0,0 +1,190 @@
package openapi
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
accessKey string
secretKey string
client *resty.Client
}
type Result interface {
SetRequestId(requestId string)
}
func NewClient(accessKey, secretKey string) *Client {
client := resty.New().
SetBaseURL("https://open.chinanetcenter.com").
SetHeader("Host", "open.chinanetcenter.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// Step 1: Get request method
method := req.Method
method = strings.ToUpper(method)
// Step 2: Get request path
path := "/"
if req.URL != nil {
path = req.URL.Path
}
// Step 3: Get unencoded query string
queryString := ""
if method != http.MethodPost && req.URL != nil {
queryString = req.URL.RawQuery
s, err := url.QueryUnescape(queryString)
if err != nil {
return err
}
queryString = s
}
// Step 4: Get canonical headers & signed headers
canonicalHeaders := "" +
"content-type:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Content-Type"))) + "\n" +
"host:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Host"))) + "\n"
signedHeaders := "content-type;host"
// Step 5: Get request payload
payload := ""
if method != http.MethodGet && req.Body != nil {
reader, err := req.GetBody()
if err != nil {
return err
}
defer reader.Close()
payloadb, err := io.ReadAll(reader)
if err != nil {
return err
}
payload = string(payloadb)
}
hashedPayload := sha256.Sum256([]byte(payload))
hashedPayloadHex := strings.ToLower(hex.EncodeToString(hashedPayload[:]))
// Step 6: Get timestamp
var reqtime time.Time
timestampString := req.Header.Get("x-cnc-timestamp")
if timestampString == "" {
reqtime = time.Now().UTC()
timestampString = fmt.Sprintf("%d", reqtime.Unix())
} else {
timestamp, err := strconv.ParseInt(timestampString, 10, 64)
if err != nil {
return err
}
reqtime = time.Unix(timestamp, 0).UTC()
}
// Step 7: Get canonical request string
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, path, queryString, canonicalHeaders, signedHeaders, hashedPayloadHex)
hashedCanonicalRequest := sha256.Sum256([]byte(canonicalRequest))
hashedCanonicalRequestHex := strings.ToLower(hex.EncodeToString(hashedCanonicalRequest[:]))
// Step 8: String to sign
const SignAlgorithmHeader = "CNC-HMAC-SHA256"
stringToSign := fmt.Sprintf("%s\n%s\n%s", SignAlgorithmHeader, timestampString, hashedCanonicalRequestHex)
hmac := hmac.New(sha256.New, []byte(secretKey))
hmac.Write([]byte(stringToSign))
sign := hmac.Sum(nil)
signHex := strings.ToLower(hex.EncodeToString(sign))
// Step 9: Add headers to request
req.Header.Set("x-cnc-accesskey", accessKey)
req.Header.Set("x-cnc-timestamp", timestampString)
req.Header.Set("x-cnc-auth-method", "AKSK")
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s", SignAlgorithmHeader, accessKey, signedHeaders, signHex))
req.Header.Set("Date", reqtime.Format("Mon, 02 Jan 2006 15:04:05 GMT"))
return nil
})
return &Client{
accessKey: accessKey,
secretKey: secretKey,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) sendRequest(method string, path string, params interface{}, configureReq ...func(req *resty.Request)) (*resty.Response, error) {
req := c.client.R()
req.Method = method
req.URL = path
if strings.EqualFold(method, http.MethodGet) {
qs := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
qs[k] = fmt.Sprintf("%v", v)
}
}
}
req = req.SetQueryParams(qs)
} else {
req = req.SetBody(params)
}
for _, fn := range configureReq {
fn(req)
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("wangsu api error: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("wangsu api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) SendRequestWithResult(method string, path string, params interface{}, result Result, configureReq ...func(req *resty.Request)) (*resty.Response, error) {
resp, err := c.sendRequest(method, path, params, configureReq...)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &result)
result.SetRequestId(resp.Header().Get("x-cnc-request-id"))
}
return resp, err
}
respBody := resp.Body()
if len(respBody) != 0 {
if err := json.Unmarshal(respBody, &result); err != nil {
return resp, fmt.Errorf("wangsu api error: failed to parse response: %w", err)
}
}
result.SetRequestId(resp.Header().Get("x-cnc-request-id"))
return resp, nil
}

View File

@@ -258,15 +258,15 @@ func init() {
} }
type dWorkflowNode struct { type dWorkflowNode struct {
Id string `json:"id"` Id string `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Config map[string]any `json:"config"` Config map[string]any `json:"config"`
Inputs map[string]any `json:"inputs"` Inputs []map[string]any `json:"inputs"`
Outputs map[string]any `json:"outputs"` Outputs []map[string]any `json:"outputs"`
Next *dWorkflowNode `json:"next,omitempty"` Next *dWorkflowNode `json:"next,omitempty"`
Branches []dWorkflowNode `json:"branches,omitempty"` Branches []dWorkflowNode `json:"branches,omitempty"`
Validated bool `json:"validated"` Validated bool `json:"validated"`
} }
for _, workflowRun := range workflowRuns { for _, workflowRun := range workflowRuns {

View File

@@ -0,0 +1,91 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"hidden": false,
"id": "hwy7m03o",
"maxSelect": 1,
"name": "provider",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"1panel",
"acmehttpreq",
"akamai",
"aliyun",
"aws",
"azure",
"baiducloud",
"baishan",
"baotapanel",
"byteplus",
"buypass",
"cachefly",
"cdnfly",
"cloudflare",
"cloudns",
"cmcccloud",
"ctcccloud",
"cucccloud",
"desec",
"dnsla",
"dogecloud",
"dynv6",
"edgio",
"fastly",
"gname",
"gcore",
"godaddy",
"goedge",
"googletrustservices",
"huaweicloud",
"jdcloud",
"k8s",
"letsencrypt",
"letsencryptstaging",
"local",
"namecheap",
"namedotcom",
"namesilo",
"ns1",
"porkbun",
"powerdns",
"qiniu",
"qingcloud",
"rainyun",
"safeline",
"ssh",
"sslcom",
"tencentcloud",
"ucloud",
"upyun",
"vercel",
"volcengine",
"wangsu",
"webhook",
"westcn",
"zerossl"
]
}`)); err != nil {
return err
}
return app.Save(collection)
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,98 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// update collection `access`
{
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update field
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
"hidden": false,
"id": "hwy7m03o",
"maxSelect": 1,
"name": "provider",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"1panel",
"acmehttpreq",
"akamai",
"aliyun",
"aws",
"azure",
"baiducloud",
"baishan",
"baotapanel",
"bunny",
"byteplus",
"buypass",
"cachefly",
"cdnfly",
"cloudflare",
"cloudns",
"cmcccloud",
"ctcccloud",
"cucccloud",
"desec",
"dnsla",
"dogecloud",
"dynv6",
"edgio",
"fastly",
"gname",
"gcore",
"godaddy",
"goedge",
"googletrustservices",
"huaweicloud",
"jdcloud",
"k8s",
"letsencrypt",
"letsencryptstaging",
"local",
"namecheap",
"namedotcom",
"namesilo",
"ns1",
"porkbun",
"powerdns",
"qiniu",
"qingcloud",
"rainyun",
"safeline",
"ssh",
"sslcom",
"tencentcloud",
"ucloud",
"upyun",
"vercel",
"volcengine",
"webhook",
"westcn",
"zerossl"
]
}`)); err != nil {
return err
}
if err := app.Save(collection); err != nil {
return err
}
}
return nil
}, func(app core.App) error {
return nil
})
}

View File

@@ -0,0 +1,108 @@
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 38.1 42.7" style="enable-background:new 0 0 38.1 42.7;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_3_);}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_4_);}
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_5_);}
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_6_);}
.st6{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_7_);}
.st7{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_8_);}
.st8{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_9_);}
</style>
<metadata>
<sfw xmlns="ns_sfw;">
<slices>
</slices>
<sliceSourceBounds bottomLeftOrigin="true" height="42.7" width="38.1" x="0" y="0">
</sliceSourceBounds>
</sfw>
</metadata>
<g id="Layer_2_1_">
<g id="Layer_1-2">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="16.85" y1="37.895" x2="36.49" y2="37.895" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#FBAA19">
</stop>
<stop offset="1" style="stop-color:#EF3E23">
</stop>
</linearGradient>
<path class="st0" d="M21,6.8l9.9,5.4L21.8,0C20.3,2,20,4.6,21,6.8z">
</path>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="15.7715" y1="12.9194" x2="17.3115" y2="17.1294" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#F78D1E">
</stop>
<stop offset="1" style="stop-color:#F37121">
</stop>
</linearGradient>
<path class="st1" d="M16.5,26.7c1.2,0,2.2,1,2.2,2.3c0,1.2-1,2.2-2.3,2.2c-1.2,0-2.2-1-2.2-2.2C14.3,27.8,15.3,26.7,16.5,26.7
C16.5,26.7,16.5,26.7,16.5,26.7z">
</path>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="3.5604" y1="11.4696" x2="27.4904" y2="37.1196" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#FEBE2D">
</stop>
<stop offset="1" style="stop-color:#F04E23">
</stop>
</linearGradient>
<path class="st2" d="M9.7,1.8l27.6,15c0.7,0.3,0.9,1.1,0.6,1.8c-0.1,0.3-0.3,0.5-0.6,0.6c-2.1,1.3-4.4,2.2-6.8,2.6l-5.8,11.8
c0,0-1.8,4.1-6.8,2.5c2.1-2.1,4.6-4,4.6-7.2c0-3.4-2.7-6.1-6.1-6.1s-6.1,2.7-6.1,6.1l0,0c0,4.2,4.2,6,6.5,8.9
c1,1.5,0.9,3.5-0.3,4.8C13.7,39.8,8.2,35,5.9,31.9c-1.2-1.6-1.9-3.5-2-5.5c0.2-4.4,3.2-8.2,7.4-9.5c1.3-0.4,2.6-0.5,3.9-0.5
c1.8,0.1,3.6,0.7,5.2,1.6c2.5,1.4,3.6,1.1,5.3-0.4c1-0.8,2.1-3.5,0.4-4.1c-0.6-0.2-1.1-0.3-1.7-0.4c-3.1-0.6-8.6-1.2-10.6-2.3
C10.6,9,8.4,5.3,9.7,1.8z">
</path>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="20.5027" y1="26.4387" x2="7.2627" y2="6.0587" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#EA4425">
</stop>
<stop offset="1" style="stop-color:#FDBB27">
</stop>
</linearGradient>
<path class="st3" d="M22.5,29.4L22.5,29.4z M22.5,29c1.3-6.7-5.5-13.1-10.8-12.2l0.4-0.1c-0.3,0.1-0.6,0.1-0.8,0.2
c-4.2,1.3-7.2,5.1-7.4,9.5c0,2,0.7,4,2,5.5c2.3,3.1,7.8,7.9,10.7,10.8c1.2-1.3,1.4-3.3,0.3-4.8c-2.4-2.9-6.5-4.7-6.5-8.9
c0-3.4,2.7-6.1,6.1-6.1C19.9,22.9,22.6,25.6,22.5,29L22.5,29z">
</path>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="0.24" y1="33.4281" x2="42.04" y2="33.4281" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#F47920">
</stop>
<stop offset="1" style="stop-color:#E93825">
</stop>
</linearGradient>
<path class="st4" d="M9.7,1.8l21,11.4l0,0l0.6,0.3c0.5,0.4,1,1.2,0.4,2.6c-1,2.1-5,4.2-9.6,2.6c1.4,0.4,2.4-0.1,3.7-1.1
c1-0.8,2.1-3.5,0.4-4.1c-0.6-0.2-1.1-0.3-1.7-0.4c-3.1-0.6-8.6-1.2-10.6-2.3C10.6,9,8.4,5.3,9.7,1.8z">
</path>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-21.84" y1="36.21" x2="63.21" y2="36.21" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#FDCA0B">
</stop>
<stop offset="1" style="stop-color:#F5841F">
</stop>
</linearGradient>
<path class="st5" d="M9.7,1.8c2.2,8,15.4,8.7,22,12L9.7,1.8z">
</path>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="8.5447" y1="25.9314" x2="17.3947" y2="-4.9386" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#E73C25">
</stop>
<stop offset="1" style="stop-color:#FAA21B">
</stop>
</linearGradient>
<path class="st6" d="M16.9,37.9c-2.3-2.9-6.5-4.7-6.5-8.9c0-3.1,2.3-5.6,5.3-6C10.9,23,7,26.9,7,31.8c0,0.6,0.1,1.2,0.2,1.8
c1.9,2.2,4.7,4.7,7,6.9c0.9,0.8,1.8,1.7,2.4,2.3c0.6-0.7,0.9-1.5,1-2.4l0,0C17.7,39.5,17.4,38.6,16.9,37.9z">
</path>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="-51.37" y1="20.9197" x2="74.88" y2="20.9197" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#FDBA12">
</stop>
<stop offset="1" style="stop-color:#F7921E">
</stop>
</linearGradient>
<path class="st7" d="M22.5,29.7c0-0.2,0-0.5,0-0.7c1.3-6.7-5.6-13.1-10.8-12.2c1.1-0.3,2.3-0.4,3.4-0.3C22,16.7,24,24.1,22.5,29.7
z">
</path>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="8.023048e-02" y1="27.2813" x2="4.8102" y2="26.4413" gradientTransform="matrix(1 0 0 -1 4.903450e-08 44)">
<stop offset="0" style="stop-color:#FEBE2D">
</stop>
<stop offset="1" style="stop-color:#F04E23">
</stop>
</linearGradient>
<path class="st8" d="M2.3,14.8L2.3,14.8c1.2,0,2.3,1,2.3,2.3v2.3H2.3c-1.2,0-2.3-1-2.3-2.3c0,0,0,0,0,0l0,0
C0,15.9,1,14.8,2.3,14.8z">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0" y="0" width="200" height="200" viewBox="0 0 340 100"><g><path d="M290,61.5c-4.1,4.6-29.3,36.7-56.9,26.7c-3.6-1.3-7.2-3.3-10.9-6.2c3.8,0.2,7.5,0,10.9-0.3 c5.3-0.5,9.8-1.5,12.4-2.9c6.1-3,4.5-13.2-12.4-16.2c-0.4-0.1-0.9-0.2-1.3-0.2c-11.3-1.7-19.5,1.7-23.7,5.3 c-0.1,0-0.3-0.1-0.4-0.2c-2.6-1.1-5.2-2.2-7.8-3.2c20-0.9,23.4-14.1,4.8-26.4c-7.5-5-16.3-9.4-25.6-13.4 c-10.7-4.5-22.3-8.5-33.7-11.7c-7.9-2.2-15.7-4.1-23.1-5.7C109.9,4.6,98.7,2.8,90,2C71.1,0.3,58.1,1.5,50.8,5.8 C47.7,7.6,46,10,46,13v0.3c0,1.6,0.5,3.3,1.4,5.2c3,6.3,11.1,13.5,24.1,21.5c11.5,7.1,28.8,15.2,43.5,20.3 c0.1,0.8,0.2,1.6,0.6,2.6c0.9,2.4,3.1,5.2,6.7,8.3c0.5,0.5,1.1,1,1.8,1.5c5.7,4.5,12.7,8.7,21.1,12.2l0.2,0.1 c10.9,4.6,22.6,8.2,33.7,10.8c16.7,3.8,32.2,5.4,42.6,4.9c0.5-0.2,0.7-0.4,0.4-0.6c-0.1-0.1-0.2-0.2-0.4-0.2 c-5.7-0.4-12.9-1.2-21.7-2.3c-7.3-1-14.3-2.3-21-3.8c-12.1-2.7-23.1-6.2-33-10.5c-0.2-0.1-0.4-0.2-0.7-0.3 c-6.7-2.9-10.5-5.7-11.4-8.2c-1.2-3.4,1.9-5.2,5-6c2,0.7,4.1,1.3,6.4,1.9c10.2,2.9,22.2,5.5,33.7,7.4c11.1,1.9,21.7,3.2,29.5,3.6 c7.6,6.7,16.1,10.2,24.6,11.3c15.2,1.8,30.4-4.1,41.1-13.8c7.3-6.6,13.2-13,16.3-17L290,61.5z M145.3,30.3 c8.5,0.3,17.4,1.3,26.7,3.1c2.5,0.5,4.8,1,7,1.5c6.7,1.6,11.5,3.3,14.5,5.2c3.4,2.1,4.1,3.7,2.1,4.9c-1.5,0.9-4.6,1.5-9.4,2 c-2.4,0.2-4.8,0.4-7.2,0.4c-2.4,0.1-4.9,0-7.4,0c-4.9-0.2-9.8-0.9-14.8-1.8c-4.9-1-8.3-2-10-3c-1.3-0.8-1.6-1.4-1-1.9 c0.4-0.2,1.1-0.4,2.3-0.5c0.9-0.1,1.5-0.4,2.1-0.7c1.1-0.7,1.4-1.6,0.8-2.9c-0.7-1.3-2-2.6-4-3.8c-0.5-0.3-1.1-0.6-1.7-1 c-0.7-0.4-1.6-0.8-2.6-1.3C143.6,30.3,144.4,30.3,145.3,30.3z M145.3,51c-6-0.5-11.4-0.5-16.2,0.1c-2.7,0.3-5,0.7-6.9,1.3 c-1.3,0.4-2.5,0.9-3.4,1.4c-0.3-0.1-0.6-0.2-1-0.4c-12.8-4.5-26.4-14.1-19.6-18.1c4.8-2.8,12.8-4.4,24-4.8c2-0.1,4.1-0.1,6.3-0.1 c-1.7,5.1,2.7,10.8,12.9,17.1c1.3,0.8,2.6,1.6,3.8,2.3c0.9,0.5,1.7,1,2.6,1.4C147,51.1,146.1,51.1,145.3,51z M206.7,79.4 c-8.5-0.8-18.3-2.9-27.7-5.2c-10.6-2.7-20.7-5.7-27.7-7.9c6.4-1.5,14.8-2.2,25.6-2c0.7,0,1.4,0,2.1,0c10.8,0.4,20,1.9,27.8,4.6 C204.6,71.5,203.8,74.9,206.7,79.4z M233.1,66.9c0.4,0,0.7,0,1.1,0.1c3.6,0.4,6.7,0.9,9.8,3.1c3.5,2.4,3.5,4.4,2.1,6.5 c-1.3,2.1-7.1,2.9-13,3.2c-4.6,0.2-9.3,0.1-12.2,0C216.1,74.7,221.9,66.4,233.1,66.9z" fill="#005BAC"/></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -17,6 +17,7 @@ import AccessFormAzureConfig from "./AccessFormAzureConfig";
import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig";
import AccessFormBaishanConfig from "./AccessFormBaishanConfig"; import AccessFormBaishanConfig from "./AccessFormBaishanConfig";
import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig"; import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig";
import AccessFormBunnyConfig from "./AccessFormBunnyConfig";
import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig"; import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig";
import AccessFormCacheFlyConfig from "./AccessFormCacheFlyConfig"; import AccessFormCacheFlyConfig from "./AccessFormCacheFlyConfig";
import AccessFormCdnflyConfig from "./AccessFormCdnflyConfig"; import AccessFormCdnflyConfig from "./AccessFormCdnflyConfig";
@@ -51,6 +52,7 @@ import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
import AccessFormVercelConfig from "./AccessFormVercelConfig"; import AccessFormVercelConfig from "./AccessFormVercelConfig";
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
import AccessFormWangsuConfig from "./AccessFormWangsuConfig";
import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig"; import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
@@ -161,6 +163,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormBaishanConfig {...nestedFormProps} />; return <AccessFormBaishanConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.BAOTAPANEL: case ACCESS_PROVIDERS.BAOTAPANEL:
return <AccessFormBaotaPanelConfig {...nestedFormProps} />; return <AccessFormBaotaPanelConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.BUNNY:
return <AccessFormBunnyConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.BYTEPLUS: case ACCESS_PROVIDERS.BYTEPLUS:
return <AccessFormBytePlusConfig {...nestedFormProps} />; return <AccessFormBytePlusConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.CACHEFLY: case ACCESS_PROVIDERS.CACHEFLY:
@@ -229,6 +233,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormVercelConfig {...nestedFormProps} />; return <AccessFormVercelConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.VOLCENGINE: case ACCESS_PROVIDERS.VOLCENGINE:
return <AccessFormVolcEngineConfig {...nestedFormProps} />; return <AccessFormVolcEngineConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WANGSU:
return <AccessFormWangsuConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WEBHOOK: case ACCESS_PROVIDERS.WEBHOOK:
return <AccessFormWebhookConfig {...nestedFormProps} />; return <AccessFormWebhookConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WESTCN: case ACCESS_PROVIDERS.WESTCN:

View File

@@ -0,0 +1,62 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AccessConfigForBunny } from "@/domain/access";
type AccessFormBunnyConfigFieldValues = Nullish<AccessConfigForBunny>;
export type AccessFormBunnyConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessFormBunnyConfigFieldValues;
onValuesChange?: (values: AccessFormBunnyConfigFieldValues) => void;
};
const initFormModel = (): AccessFormBunnyConfigFieldValues => {
return {
apiKey: "",
};
};
const AccessFormBunnyConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormBunnyConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z
.string()
.nonempty(t("access.form.bunny_api_key.placeholder"))
.trim(),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item
name="apiKey"
label={t("access.form.bunny_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.bunny_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.bunny_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessFormBunnyConfig;

View File

@@ -0,0 +1,91 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AccessConfigForWangsu } from "@/domain/access";
type AccessFormWangsuConfigFieldValues = Nullish<AccessConfigForWangsu>;
export type AccessFormWangsuConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessFormWangsuConfigFieldValues;
onValuesChange?: (values: AccessFormWangsuConfigFieldValues) => void;
};
const initFormModel = (): AccessFormWangsuConfigFieldValues => {
return {
accessKeyId: "",
accessKeySecret: "",
apiKey: "",
};
};
const AccessFormWangsuConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormWangsuConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.min(1, t("access.form.wangsu_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.trim(),
accessKeySecret: z
.string()
.min(1, t("access.form.wangsu_access_key_secret.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.trim(),
apiKey: z
.string()
.min(1, t("access.form.wangsu_api_key.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.trim(),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item
name="accessKeyId"
label={t("access.form.wangsu_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wangsu_access_key_id.tooltip") }}></span>}
>
<Input autoComplete="new-password" placeholder={t("access.form.wangsu_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="accessKeySecret"
label={t("access.form.wangsu_access_key_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wangsu_access_key_secret.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.wangsu_access_key_secret.placeholder")} />
</Form.Item>
<Form.Item
name="apiKey"
label={t("access.form.wangsu_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wangsu_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.wangsu_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessFormWangsuConfig;

View File

@@ -9,6 +9,8 @@ import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalk
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields"; import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx"; import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormMattermostFields from "./NotifyChannelEditFormMattermostFields.tsx";
import NotifyChannelEditFormPushoverFields from "./NotifyChannelEditFormPushoverFields";
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields"; import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields"; import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
@@ -54,6 +56,10 @@ const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyCh
return <NotifyChannelEditFormGotifyFields />; return <NotifyChannelEditFormGotifyFields />;
case NOTIFY_CHANNELS.LARK: case NOTIFY_CHANNELS.LARK:
return <NotifyChannelEditFormLarkFields />; return <NotifyChannelEditFormLarkFields />;
case NOTIFY_CHANNELS.MATTERMOST:
return <NotifyChannelEditFormMattermostFields />;
case NOTIFY_CHANNELS.PUSHOVER:
return <NotifyChannelEditFormPushoverFields />;
case NOTIFY_CHANNELS.PUSHPLUS: case NOTIFY_CHANNELS.PUSHPLUS:
return <NotifyChannelEditFormPushPlusFields />; return <NotifyChannelEditFormPushPlusFields />;
case NOTIFY_CHANNELS.SERVERCHAN: case NOTIFY_CHANNELS.SERVERCHAN:

View File

@@ -0,0 +1,64 @@
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
const NotifyChannelEditFormMattermostFields = () => {
const { t } = useTranslation();
const formSchema = z.object({
serverUrl: z
.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") })
.url(t("common.errmsg.url_invalid")),
channelId: z
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
username: z
.string({ message: t("settings.notification.channel.form.mattermost_username.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_username.placeholder")),
password: z
.string({ message: t("settings.notification.channel.form.mattermost_password.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_password.placeholder")),
});
const formRule = createSchemaFieldRule(formSchema);
return (
<>
<Form.Item
name="serverUrl"
label={t("settings.notification.channel.form.mattermost_server_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_server_url.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_server_url.placeholder")} />
</Form.Item>
<Form.Item
name="channelId"
label={t("settings.notification.channel.form.mattermost_channel_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_channel_id.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
</Form.Item>
<Form.Item
name="username"
label={t("settings.notification.channel.form.mattermost_username.label")}
rules={[formRule]}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
</Form.Item>
<Form.Item
name="password"
label={t("settings.notification.channel.form.mattermost_password.label")}
rules={[formRule]}
>
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
</Form.Item>
</>
);
};
export default NotifyChannelEditFormMattermostFields;

View File

@@ -0,0 +1,41 @@
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
const NotifyChannelEditFormPushoverFields = () => {
const { t } = useTranslation();
const formSchema = z.object({
token: z
.string({ message: t("settings.notification.channel.form.pushover_token.placeholder") })
.nonempty(t("settings.notification.channel.form.pushover_token.placeholder")),
user: z
.string({ message: t("settings.notification.channel.form.pushover_user.placeholder") })
.nonempty(t("settings.notification.channel.form.pushover_user.placeholder")),
});
const formRule = createSchemaFieldRule(formSchema);
return (
<>
<Form.Item
name="token"
label={t("settings.notification.channel.form.pushover_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.pushover_token.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.pushover_token.placeholder")} />
</Form.Item>
<Form.Item
name="user"
label={t("settings.notification.channel.form.pushover_user.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.pushover_user.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.pushover_user.placeholder")} />
</Form.Item>
</>
);
};
export default NotifyChannelEditFormPushoverFields;

View File

@@ -50,26 +50,27 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden"> <div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
<Space className="max-w-full grow truncate" size={4}> <Space className="max-w-full grow truncate" size={4}>
<Avatar src={provider.icon} size="small" /> <Avatar src={provider.icon} size="small" />
<Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} delete={provider.builtin ? true : undefined} ellipsis> <Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} ellipsis>
{t(provider.name)} {t(provider.name)}
</Typography.Text> </Typography.Text>
</Space> </Space>
{showOptionTags && ( <div>
<div> <Show when={provider.builtin}>
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}> <Tag color="grey">{t("access.props.provider.builtin")}</Tag>
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag> </Show>
</Show> <Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}> <Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
<Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag> </Show>
</Show> <Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}> <Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag>
<Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag> </Show>
</Show> <Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}> <Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag>
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag> </Show>
</Show> <Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
</div> <Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
)} </Show>
</div>
</div> </div>
); );
}; };

View File

@@ -11,7 +11,7 @@ import {
SyncOutlined as SyncOutlinedIcon, SyncOutlined as SyncOutlinedIcon,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Button, Empty, Modal, Space, Table, type TableProps, Tag, Tooltip, notification } from "antd"; import { Alert, Button, Empty, Modal, Space, Table, type TableProps, Tag, Tooltip, notification } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@@ -284,6 +284,8 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
{NotificationContextHolder} {NotificationContextHolder}
<div className={className} style={style}> <div className={className} style={style}>
<Alert className="mb-4" type="warning" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_run.table.alert") }}></span>} />
<Table<WorkflowRunModel> <Table<WorkflowRunModel>
columns={tableColumns} columns={tableColumns}
dataSource={tableData} dataSource={tableData}

View File

@@ -18,6 +18,7 @@ import { useWorkflowStore } from "@/stores/workflow";
import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig"; import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig";
import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig"; import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig";
import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig"; import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig";
import DeployNodeConfigFormAliyunAPIGWConfig from "./DeployNodeConfigFormAliyunAPIGWConfig";
import DeployNodeConfigFormAliyunCASConfig from "./DeployNodeConfigFormAliyunCASConfig"; import DeployNodeConfigFormAliyunCASConfig from "./DeployNodeConfigFormAliyunCASConfig";
import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig"; import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig";
import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig";
@@ -39,6 +40,7 @@ import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaidu
import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig"; import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig";
import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig"; import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig";
import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig"; import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig";
import DeployNodeConfigFormBunnyCDNConfig from "./DeployNodeConfigFormBunnyCDNConfig.tsx";
import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig";
import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig";
import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig";
@@ -56,6 +58,7 @@ import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig"; import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig"; import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig";
import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig";
import DeployNodeConfigFormRainYunRCDNConfig from "./DeployNodeConfigFormRainYunRCDNConfig";
import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig"; import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig";
import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx";
import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx"; import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx";
@@ -80,6 +83,7 @@ import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolc
import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx"; import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx";
import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx"; import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx";
import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx"; import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx";
import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx";
import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx"; import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx";
type DeployNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForDeploy>; type DeployNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForDeploy>;
@@ -175,6 +179,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigForm1PanelSiteConfig {...nestedFormProps} />; return <DeployNodeConfigForm1PanelSiteConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_ALB: case DEPLOY_PROVIDERS.ALIYUN_ALB:
return <DeployNodeConfigFormAliyunALBConfig {...nestedFormProps} />; return <DeployNodeConfigFormAliyunALBConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_APIGW:
return <DeployNodeConfigFormAliyunAPIGWConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_CAS: case DEPLOY_PROVIDERS.ALIYUN_CAS:
return <DeployNodeConfigFormAliyunCASConfig {...nestedFormProps} />; return <DeployNodeConfigFormAliyunCASConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY: case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY:
@@ -217,6 +223,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigFormBaotaPanelConsoleConfig {...nestedFormProps} />; return <DeployNodeConfigFormBaotaPanelConsoleConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.BAOTAPANEL_SITE: case DEPLOY_PROVIDERS.BAOTAPANEL_SITE:
return <DeployNodeConfigFormBaotaPanelSiteConfig {...nestedFormProps} />; return <DeployNodeConfigFormBaotaPanelSiteConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.BUNNY_CDN:
return <DeployNodeConfigFormBunnyCDNConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.BYTEPLUS_CDN: case DEPLOY_PROVIDERS.BYTEPLUS_CDN:
return <DeployNodeConfigFormBytePlusCDNConfig {...nestedFormProps} />; return <DeployNodeConfigFormBytePlusCDNConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.CDNFLY: case DEPLOY_PROVIDERS.CDNFLY:
@@ -251,6 +259,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />; return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.QINIU_PILI: case DEPLOY_PROVIDERS.QINIU_PILI:
return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />; return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.RAINYUN_RCDN:
return <DeployNodeConfigFormRainYunRCDNConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.SAFELINE: case DEPLOY_PROVIDERS.SAFELINE:
return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />; return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.SSH: case DEPLOY_PROVIDERS.SSH:
@@ -299,6 +309,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />; return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.VOLCENGINE_TOS: case DEPLOY_PROVIDERS.VOLCENGINE_TOS:
return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />; return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.WANGSU_CDNPRO:
return <DeployNodeConfigFormWangsuCDNProConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.WEBHOOK: case DEPLOY_PROVIDERS.WEBHOOK:
return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />; return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />;
} }

View File

@@ -1,10 +1,14 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd"; import { Form, type FormInstance, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import Show from "@/components/Show";
type DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{ type DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{
websiteId: string | number; resourceType: string;
websiteId?: string | number;
certificateId?: string | number;
}>; }>;
export type DeployNodeConfigForm1PanelSiteConfigProps = { export type DeployNodeConfigForm1PanelSiteConfigProps = {
@@ -15,8 +19,13 @@ export type DeployNodeConfigForm1PanelSiteConfigProps = {
onValuesChange?: (values: DeployNodeConfigForm1PanelSiteConfigFieldValues) => void; onValuesChange?: (values: DeployNodeConfigForm1PanelSiteConfigFieldValues) => void;
}; };
const RESOURCE_TYPE_WEBSITE = "website" as const;
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
const initFormModel = (): DeployNodeConfigForm1PanelSiteConfigFieldValues => { const initFormModel = (): DeployNodeConfigForm1PanelSiteConfigFieldValues => {
return {}; return {
resourceType: RESOURCE_TYPE_WEBSITE,
};
}; };
const DeployNodeConfigForm1PanelSiteConfig = ({ const DeployNodeConfigForm1PanelSiteConfig = ({
@@ -29,12 +38,28 @@ const DeployNodeConfigForm1PanelSiteConfig = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
websiteId: z.union([z.string(), z.number()]).refine((v) => { resourceType: z.union([z.literal(RESOURCE_TYPE_WEBSITE), z.literal(RESOURCE_TYPE_CERTIFICATE)], {
return /^\d+$/.test(v + "") && +v > 0; message: t("workflow_node.deploy.form.1panel_site_resource_type.placeholder"),
}, t("workflow_node.deploy.form.1panel_site_website_id.placeholder")), }),
websiteId: z
.union([z.string(), z.number().int()])
.nullish()
.refine((v) => {
if (fieldResourceType !== RESOURCE_TYPE_WEBSITE) return true;
return /^\d+$/.test(v + "") && +v! > 0;
}, t("workflow_node.deploy.form.1panel_site_website_id.placeholder")),
certificateId: z
.union([z.string(), z.number().int()])
.nullish()
.refine((v) => {
if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true;
return /^\d+$/.test(v + "") && +v! > 0;
}, t("workflow_node.deploy.form.1panel_site_certificate_id.placeholder")),
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
const fieldResourceType = Form.useWatch("resourceType", formInst);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => { const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values); onValuesChange?.(values);
}; };
@@ -48,14 +73,38 @@ const DeployNodeConfigForm1PanelSiteConfig = ({
name={formName} name={formName}
onValuesChange={handleFormChange} onValuesChange={handleFormChange}
> >
<Form.Item <Form.Item name="resourceType" label={t("workflow_node.deploy.form.1panel_site_resource_type.label")} rules={[formRule]}>
name="websiteId" <Select placeholder={t("workflow_node.deploy.form.1panel_site_resource_type.placeholder")}>
label={t("workflow_node.deploy.form.1panel_site_website_id.label")} <Select.Option key={RESOURCE_TYPE_WEBSITE} value={RESOURCE_TYPE_WEBSITE}>
rules={[formRule]} {t("workflow_node.deploy.form.1panel_site_resource_type.option.website.label")}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.1panel_site_website_id.tooltip") }}></span>} </Select.Option>
> <Select.Option key={RESOURCE_TYPE_CERTIFICATE} value={RESOURCE_TYPE_CERTIFICATE}>
<Input type="number" placeholder={t("workflow_node.deploy.form.1panel_site_website_id.placeholder")} /> {t("workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label")}
</Select.Option>
</Select>
</Form.Item> </Form.Item>
<Show when={fieldResourceType === RESOURCE_TYPE_WEBSITE}>
<Form.Item
name="websiteId"
label={t("workflow_node.deploy.form.1panel_site_website_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.1panel_site_website_id.tooltip") }}></span>}
>
<Input type="number" placeholder={t("workflow_node.deploy.form.1panel_site_website_id.placeholder")} />
</Form.Item>
</Show>
<Show when={fieldResourceType === RESOURCE_TYPE_CERTIFICATE}>
<Form.Item
name="certificateId"
label={t("workflow_node.deploy.form.1panel_site_certificate_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.1panel_site_certificate_id.tooltip") }}></span>}
>
<Input type="number" placeholder={t("workflow_node.deploy.form.1panel_site_certificate_id.placeholder")} />
</Form.Item>
</Show>
</Form> </Form>
); );
}; };

View File

@@ -0,0 +1,133 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import Show from "@/components/Show";
import { validDomainName } from "@/utils/validators";
type DeployNodeConfigFormAliyunAPIGWConfigFieldValues = Nullish<{
serviceType: string;
region: string;
gatewayId?: string;
groupId?: string;
domain?: string;
}>;
export type DeployNodeConfigFormAliyunAPIGWConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigFormAliyunAPIGWConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigFormAliyunAPIGWConfigFieldValues) => void;
};
const SERVICE_TYPE_CLOUDNATIVE = "cloudnative" as const;
const SERVICE_TYPE_TRADITIONAL = "traditional" as const;
const initFormModel = (): DeployNodeConfigFormAliyunAPIGWConfigFieldValues => {
return {};
};
const DeployNodeConfigFormAliyunAPIGWConfig = ({
form: formInst,
formName,
disabled,
initialValues,
onValuesChange,
}: DeployNodeConfigFormAliyunAPIGWConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
serviceType: z.union([z.literal(SERVICE_TYPE_CLOUDNATIVE), z.literal(SERVICE_TYPE_TRADITIONAL)], {
message: t("workflow_node.deploy.form.aliyun_apigw_service_type.placeholder"),
}),
region: z
.string({ message: t("workflow_node.deploy.form.aliyun_apigw_region.placeholder") })
.nonempty(t("workflow_node.deploy.form.aliyun_apigw_region.placeholder"))
.trim(),
gatewayId: z
.string()
.nullish()
.refine((v) => fieldServiceType !== SERVICE_TYPE_CLOUDNATIVE || !!v?.trim(), t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder")),
groupId: z
.string()
.nullish()
.refine((v) => fieldServiceType !== SERVICE_TYPE_TRADITIONAL || !!v?.trim(), t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder")),
domain: z
.string()
.nonempty(t("workflow_node.deploy.form.aliyun_apigw_domain.placeholder"))
.refine((v) => validDomainName(v!, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const fieldServiceType = Form.useWatch("serviceType", formInst);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item name="serviceType" label={t("workflow_node.deploy.form.aliyun_apigw_service_type.label")} rules={[formRule]}>
<Select placeholder={t("workflow_node.deploy.form.aliyun_apigw_service_type.placeholder")}>
<Select.Option key={SERVICE_TYPE_CLOUDNATIVE} value={SERVICE_TYPE_CLOUDNATIVE}>
{t("workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label")}
</Select.Option>
<Select.Option key={SERVICE_TYPE_TRADITIONAL} value={SERVICE_TYPE_TRADITIONAL}>
{t("workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="region"
label={t("workflow_node.deploy.form.aliyun_apigw_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")} />
</Form.Item>
<Show when={fieldServiceType === SERVICE_TYPE_CLOUDNATIVE}>
<Form.Item
name="gatewayId"
label={t("workflow_node.deploy.form.aliyun_apigw_gateway_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder")} />
</Form.Item>
</Show>
<Show when={fieldServiceType === SERVICE_TYPE_TRADITIONAL}>
<Form.Item
name="groupId"
label={t("workflow_node.deploy.form.aliyun_apigw_group_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_group_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder")} />
</Form.Item>
</Show>
<Form.Item
name="domain"
label={t("workflow_node.deploy.form.aliyun_apigw_domain.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_apigw_domain.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aliyun_apigw_domain.placeholder")} />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigFormAliyunAPIGWConfig;

View File

@@ -5,6 +5,7 @@ import { z } from "zod";
type DeployNodeConfigFormAzureKeyVaultConfigFieldValues = Nullish<{ type DeployNodeConfigFormAzureKeyVaultConfigFieldValues = Nullish<{
keyvaultName: string; keyvaultName: string;
certificateName?: string;
}>; }>;
export type DeployNodeConfigFormAzureKeyVaultConfigProps = { export type DeployNodeConfigFormAzureKeyVaultConfigProps = {
@@ -33,6 +34,13 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
.string({ message: t("workflow_node.deploy.form.azure_keyvault_name.placeholder") }) .string({ message: t("workflow_node.deploy.form.azure_keyvault_name.placeholder") })
.nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")) .nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder"))
.trim(), .trim(),
certificateName: z
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
.nullish()
.refine((v) =>{
if (!v) return true;
return /^[a-zA-Z0-9-]{1,127}$/.test(v);
}, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")),
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
@@ -57,6 +65,15 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
> >
<Input placeholder={t("workflow_node.deploy.form.azure_keyvault_name.placeholder")} /> <Input placeholder={t("workflow_node.deploy.form.azure_keyvault_name.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item
name="certificateName"
label={t("workflow_node.deploy.form.azure_keyvault_certificate_name.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder")} />
</Form.Item>
</Form> </Form>
); );
}; };

View File

@@ -0,0 +1,76 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { validDomainName } from "@/utils/validators";
type DeployNodeConfigFormBunnyCDNConfigFieldValues = Nullish<{
pullZoneId: string | number;
hostName: string;
}>;
export type DeployNodeConfigFormBunnyCDNConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigFormBunnyCDNConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigFormBunnyCDNConfigFieldValues) => void;
};
const initFormModel = (): DeployNodeConfigFormBunnyCDNConfigFieldValues => {
return {};
};
const DeployNodeConfigFormBunnyCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormBunnyCDNConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
pullZoneId: z
.union([z.string(), z.number().int()])
.refine((v) => {
return /^\d+$/.test(v + "") && +v! > 0;
}, t("workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder")),
hostName: z
.string({ message: t("workflow_node.deploy.form.bunny_cdn_host_name.placeholder") })
.nonempty(t("workflow_node.deploy.form.bunny_cdn_host_name.placeholder"))
.refine((v) => {
return !v || validDomainName(v!, { allowWildcard: true });
}, t("common.errmsg.domain_invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item
name="pullZoneId"
label={t("workflow_node.deploy.form.bunny_cdn_pull_zone_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder")} />
</Form.Item>
<Form.Item
name="hostName"
label={t("workflow_node.deploy.form.bunny_cdn_host_name.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.bunny_cdn_host_name.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.bunny_cdn_host_name.placeholder")} />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigFormBunnyCDNConfig;

View File

@@ -7,6 +7,7 @@ import Show from "@/components/Show";
type DeployNodeConfigFormCdnflyConfigFieldValues = Nullish<{ type DeployNodeConfigFormCdnflyConfigFieldValues = Nullish<{
resourceType: string; resourceType: string;
siteId?: string | number;
certificateId?: string | number; certificateId?: string | number;
}>; }>;
@@ -34,10 +35,13 @@ const DeployNodeConfigFormCdnflyConfig = ({ form: formInst, formName, disabled,
resourceType: z.union([z.literal(RESOURCE_TYPE_SITE), z.literal(RESOURCE_TYPE_CERTIFICATE)], { resourceType: z.union([z.literal(RESOURCE_TYPE_SITE), z.literal(RESOURCE_TYPE_CERTIFICATE)], {
message: t("workflow_node.deploy.form.cdnfly_resource_type.placeholder"), message: t("workflow_node.deploy.form.cdnfly_resource_type.placeholder"),
}), }),
siteId: z.union([z.string(), z.number().int()]).refine((v) => { siteId: z
if (fieldResourceType !== RESOURCE_TYPE_SITE) return true; .union([z.string(), z.number().int()])
return /^\d+$/.test(v + "") && +v > 0; .nullish()
}, t("workflow_node.deploy.form.cdnfly_site_id.placeholder")), .refine((v) => {
if (fieldResourceType !== RESOURCE_TYPE_SITE) return true;
return /^\d+$/.test(v + "") && +v! > 0;
}, t("workflow_node.deploy.form.cdnfly_site_id.placeholder")),
certificateId: z certificateId: z
.union([z.string(), z.number().int()]) .union([z.string(), z.number().int()])
.nullish() .nullish()

View File

@@ -4,7 +4,7 @@ import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{ type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{
resourceId?: string | number; resourceId: string | number;
}>; }>;
export type DeployNodeConfigFormGcoreCDNConfigProps = { export type DeployNodeConfigFormGcoreCDNConfigProps = {
@@ -27,7 +27,7 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled
const formSchema = z.object({ const formSchema = z.object({
resourceId: z.union([z.string(), z.number()]).refine((v) => { resourceId: z.union([z.string(), z.number()]).refine((v) => {
return /^\d+$/.test(v + "") && +v > 0; return /^\d+$/.test(v + "") && +v > 0;
}, t("workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder")), }, t("workflow_node.deploy.form.gcore_cdn_resource_id.placeholder")),
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);

View File

@@ -39,8 +39,8 @@ const SHELLENV_POWERSHELL = "powershell" as const;
const initFormModel = (): DeployNodeConfigFormLocalConfigFieldValues => { const initFormModel = (): DeployNodeConfigFormLocalConfigFieldValues => {
return { return {
format: FORMAT_PEM, format: FORMAT_PEM,
certPath: "/etc/ssl/certs/cert.crt", certPath: "/etc/ssl/certimate/cert.crt",
keyPath: "/etc/ssl/certs/cert.key", keyPath: "/etc/ssl/certimate/cert.key",
shellEnv: SHELLENV_SH, shellEnv: SHELLENV_SH,
}; };
}; };

View File

@@ -0,0 +1,80 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { validDomainName } from "@/utils/validators";
type DeployNodeConfigFormRainYunRCDNConfigFieldValues = Nullish<{
instanceId: string | number;
domain: string;
}>;
export type DeployNodeConfigFormRainYunRCDNConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigFormRainYunRCDNConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigFormRainYunRCDNConfigFieldValues) => void;
};
const initFormModel = (): DeployNodeConfigFormRainYunRCDNConfigFieldValues => {
return {
instanceId: "",
};
};
const DeployNodeConfigFormRainYunRCDNConfig = ({
form: formInst,
formName,
disabled,
initialValues,
onValuesChange,
}: DeployNodeConfigFormRainYunRCDNConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
instanceId: z.union([z.string(), z.number()]).refine((v) => {
return /^\d+$/.test(v + "") && +v > 0;
}, t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")),
domain: z
.string({ message: t("workflow_node.deploy.form.rainyun_rcdn_domain.placeholder") })
.refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item
name="instanceId"
label={t("workflow_node.deploy.form.rainyun_rcdn_instance_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.rainyun_rcdn_instance_id.tooltip") }}></span>}
>
<Input type="number" placeholder={t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")} />
</Form.Item>
<Form.Item
name="domain"
label={t("workflow_node.deploy.form.rainyun_rcdn_domain.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.rainyun_rcdn_domain.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.rainyun_rcdn_domain.placeholder")} />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigFormRainYunRCDNConfig;

View File

@@ -35,8 +35,8 @@ const FORMAT_JKS = CERTIFICATE_FORMATS.JKS;
const initFormModel = (): DeployNodeConfigFormSSHConfigFieldValues => { const initFormModel = (): DeployNodeConfigFormSSHConfigFieldValues => {
return { return {
format: FORMAT_PEM, format: FORMAT_PEM,
certPath: "/etc/ssl/certs/cert.crt", certPath: "/etc/ssl/certimate/cert.crt",
keyPath: "/etc/ssl/certs/cert.key", keyPath: "/etc/ssl/certimate/cert.key",
}; };
}; };

View File

@@ -113,7 +113,7 @@ const DeployNodeConfigFormVolcEngineALBConfig = ({
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}> <Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
<Form.Item <Form.Item
name="listenerId" name="loadbalancerId"
label={t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.label")} label={t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.label")}
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.tooltip") }}></span>}

View File

@@ -104,7 +104,7 @@ const DeployNodeConfigFormVolcEngineCLBConfig = ({
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}> <Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
<Form.Item <Form.Item
name="listenerId" name="loadbalancerId"
label={t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.label")} label={t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.label")}
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.tooltip") }}></span>}

View File

@@ -0,0 +1,107 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { validDomainName } from "@/utils/validators";
type DeployNodeConfigFormBaishanCDNConfigFieldValues = Nullish<{
environment: string;
domain: string;
certificateId?: string;
webhookId?: string;
}>;
export type DeployNodeConfigFormBaishanCDNConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigFormBaishanCDNConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigFormBaishanCDNConfigFieldValues) => void;
};
const ENVIRONMENT_PRODUCTION = "production" as const;
const ENVIRONMENT_STAGING = "stating" as const;
const initFormModel = (): DeployNodeConfigFormBaishanCDNConfigFieldValues => {
return {
environment: ENVIRONMENT_PRODUCTION,
};
};
const DeployNodeConfigFormBaishanCDNConfig = ({
form: formInst,
formName,
disabled,
initialValues,
onValuesChange,
}: DeployNodeConfigFormBaishanCDNConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
resourceType: z.union([z.literal(ENVIRONMENT_PRODUCTION), z.literal(ENVIRONMENT_STAGING)], {
message: t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder"),
}),
domain: z
.string({ message: t("workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder") })
.refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
certificateId: z.string().nullish(),
webhookId: z.string().nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
>
<Form.Item name="environment" label={t("workflow_node.deploy.form.wangsu_cdnpro_environment.label")} rules={[formRule]}>
<Select placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder")}>
<Select.Option key={ENVIRONMENT_PRODUCTION} value={ENVIRONMENT_PRODUCTION}>
{t("workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label")}
</Select.Option>
<Select.Option key={ENVIRONMENT_STAGING} value={ENVIRONMENT_STAGING}>
{t("workflow_node.deploy.form.wangsu_cdnpro_environment.option.staging.label")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="domain"
label={t("workflow_node.deploy.form.wangsu_cdnpro_domain.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_domain.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder")} />
</Form.Item>
<Form.Item
name="certificateId"
label={t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.placeholder")} />
</Form.Item>
<Form.Item
name="webhookId"
label={t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigFormBaishanCDNConfig;

View File

@@ -14,6 +14,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForBaiduCloud | AccessConfigForBaiduCloud
| AccessConfigForBaishan | AccessConfigForBaishan
| AccessConfigForBaotaPanel | AccessConfigForBaotaPanel
| AccessConfigForBunny
| AccessConfigForBytePlus | AccessConfigForBytePlus
| AccessConfigForCacheFly | AccessConfigForCacheFly
| AccessConfigForCdnfly | AccessConfigForCdnfly
@@ -47,6 +48,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForUpyun | AccessConfigForUpyun
| AccessConfigForVercel | AccessConfigForVercel
| AccessConfigForVolcEngine | AccessConfigForVolcEngine
| AccessConfigForWangsu
| AccessConfigForWebhook | AccessConfigForWebhook
| AccessConfigForWestcn | AccessConfigForWestcn
| AccessConfigForZeroSSL | AccessConfigForZeroSSL
@@ -99,6 +101,10 @@ export type AccessConfigForBaotaPanel = {
allowInsecureConnections?: boolean; allowInsecureConnections?: boolean;
}; };
export type AccessConfigForBunny = {
apiKey: string;
};
export type AccessConfigForBytePlus = { export type AccessConfigForBytePlus = {
accessKey: string; accessKey: string;
secretKey: string; secretKey: string;
@@ -268,6 +274,12 @@ export type AccessConfigForVolcEngine = {
secretAccessKey: string; secretAccessKey: string;
}; };
export type AccessConfigForWangsu = {
accessKeyId: string;
accessKeySecret: string;
apiKey: string;
};
export type AccessConfigForWebhook = { export type AccessConfigForWebhook = {
url: string; url: string;
allowInsecureConnections?: boolean; allowInsecureConnections?: boolean;

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