diff --git a/README.md b/README.md
index 745cec19..b7cfc56e 100644
--- a/README.md
+++ b/README.md
@@ -95,10 +95,14 @@ make local.run
| [AWS Route53](https://aws.amazon.com/route53/) | |
| [Azure](https://azure.microsoft.com/) | |
| [CloudFlare](https://www.cloudflare.com/) | |
+| [ClouDNS](https://www.cloudns.net//) | |
+| [GNAME](https://www.gname.com/) | |
| [GoDaddy](https://www.godaddy.com/) | |
| [Name.com](https://www.name.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | |
+| [雨云](https://www.rainyun.com/) | |
+| [西部数码](https://www.west.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |
@@ -112,22 +116,23 @@ make local.run
[展开查看]
-| 提供商 | 备注 |
-| :-------------------------------------- | :------------------------------------------------------------- |
-| 本地部署 | 可部署到本地服务器 |
-| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP) |
-| Webhook 回调 | 可部署到 Webhook |
-| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret |
-| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、Live 等服务 |
-| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 |
-| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 |
-| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 |
-| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 |
-| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN |
-| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN |
-| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 |
-| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 |
-| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 |
+| 提供商 | 备注 |
+| :-------------------------------------- | :------------------------------------------------------------------ |
+| 本地部署 | 可部署到本地服务器 |
+| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP/SCP) |
+| Webhook 回调 | 可部署到 Webhook |
+| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret |
+| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 |
+| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 |
+| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 |
+| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 |
+| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 |
+| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 |
+| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN |
+| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 |
+| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 |
+| [AWS](https://aws.amazon.com/) | 可部署到 AWS CloudFront 等服务 |
+| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 |
diff --git a/README_EN.md b/README_EN.md
index ece9591c..c2174fad 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -94,10 +94,14 @@ The following DNS providers are supported:
| [AWS Route53](https://aws.amazon.com/route53/) | |
| [Azure DNS](https://azure.microsoft.com/) | |
| [CloudFlare](https://www.cloudflare.com/) | |
+| [ClouDNS](https://www.cloudns.net//) | |
+| [GNAME](https://www.gname.com/) | |
| [GoDaddy](https://www.godaddy.com/) | |
| [Name.com](https://www.name.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | |
+| [Rain Yun](https://www.rainyun.com/) | |
+| [West.cn](https://www.west.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME Proxy HTTP Request | Supports managing DNS by HTTP request |
@@ -111,22 +115,23 @@ The following hosting providers are supported:
[Fold/Unfold to view ...]
-| Provider | Remarks |
-| :---------------------------------------------- | :-------------------------------------------------------------------------- |
-| Local | Supports deployment to local servers |
-| SSH | Supports deployment to remote servers (via SSH+SFTP) |
-| Webhook | Supports deployment to Webhook |
-| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret |
-| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), Live |
-| [Tencent Cloud](https://www.tencentcloud.com/) | Supports deployment to Tencent Cloud COS, CDN, ECDN, EdgeOne, CLB, CSS |
-| [Baidu AI Cloud](https://intl.cloud.baidu.com/) | Supports deployment to Baidu AI CLoud CDN |
-| [Huawei Cloud](https://www.huaweicloud.com/) | Supports deployment to Huawei Cloud CDN, ELB |
-| [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live |
-| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN |
-| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN |
-| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN |
-| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN |
-| [Edgio](https://edg.io/) | Supports deployment to Edgio Applications |
+| Provider | Remarks |
+| :---------------------------------------------- | :------------------------------------------------------------------------------- |
+| Local | Supports deployment to local servers |
+| SSH | Supports deployment to remote servers (via SSH+SFTP/SCP) |
+| Webhook | Supports deployment to Webhook |
+| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret |
+| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), WAF, Live |
+| [Tencent Cloud](https://www.tencentcloud.com/) | Supports deployment to Tencent Cloud COS, CDN, ECDN, EdgeOne, CLB, CSS |
+| [Baidu AI Cloud](https://intl.cloud.baidu.com/) | Supports deployment to Baidu AI CLoud CDN |
+| [Huawei Cloud](https://www.huaweicloud.com/) | Supports deployment to Huawei Cloud CDN, ELB |
+| [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live |
+| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN, Pili |
+| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN |
+| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN |
+| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN |
+| [AWS](https://aws.amazon.com/) | Supports deployment to AWS CloudFront |
+| [Edgio](https://edg.io/) | Supports deployment to Edgio Applications |
diff --git a/go.mod b/go.mod
index 1567878b..6cd082bf 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,10 @@ require (
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
github.com/alibabacloud-go/slb-20140515/v4 v4.0.10
github.com/alibabacloud-go/tea v1.2.2
+ github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
+ github.com/aws/aws-sdk-go-v2/service/acm v1.30.13
+ github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5
github.com/baidubce/bce-sdk-go v0.9.214
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.40
github.com/go-acme/lego/v4 v4.21.0
@@ -26,6 +29,7 @@ require (
github.com/pkg/sftp v1.13.7
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.24.4
+ github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
github.com/qiniu/go-sdk/v7 v7.25.2
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1084
@@ -66,6 +70,9 @@ require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@@ -75,10 +82,14 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
+ github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
+ github.com/qiniu/dyn v1.3.0 // indirect
+ github.com/qiniu/x v1.10.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
@@ -109,10 +120,10 @@ require (
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
github.com/aliyun/credentials-go v1.4.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
- github.com/aws/aws-sdk-go-v2 v1.33.0 // indirect
+ github.com/aws/aws-sdk-go-v2 v1.33.0
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
- github.com/aws/aws-sdk-go-v2/config v1.29.1 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.17.54 // indirect
+ github.com/aws/aws-sdk-go-v2/config v1.29.1
+ github.com/aws/aws-sdk-go-v2/credentials v1.17.54
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.52 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect
diff --git a/go.sum b/go.sum
index bbb65a6a..7dde5dc8 100644
--- a/go.sum
+++ b/go.sum
@@ -186,6 +186,8 @@ github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
+github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 h1:Od0KgA73DyG9X2XFwuZZTkDv2pzA6B5mhYapyyca6QE=
+github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4/go.mod h1:DohGoS8BnMxHXghHebtjPP7+GMdxPsRN19T3nn2HcCU=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
@@ -230,6 +232,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 h1:7kpeALOUeThs2kEjlAxlADAVfxKmkYAedlpZ3kdoSJ4=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28/go.mod h1:pyaOYEdp1MJWgtXLy6q80r3DhsVdOIOZNB9hdTcJIvI=
+github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 h1:aPCPsgDxQqOS3zPJKYJQVh02q8stjSQ1haHaUucCAUM=
+github.com/aws/aws-sdk-go-v2/service/acm v1.30.13/go.mod h1:3pfuOCVLzWu3aiavTB9bOIdZpVadNYt6fyZdp+fDOSU=
+github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5 h1:oBLlEuSL5G9W8M4GtEVdNi+xsQP+9lphVkbYf38Isgs=
+github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5/go.mod h1:H/t3dGwvHy2WJ+ZwyDBWva7ttsoxSxt5qC1OMcc0iJ0=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
@@ -388,11 +394,19 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
+github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
+github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-resty/resty/v2 v2.16.4 h1:81IjtszQKwbz7dot4LLYGwhJNUsNwECD2O7nru5q60E=
github.com/go-resty/resty/v2 v2.16.4/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -615,6 +629,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -675,6 +691,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/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
+github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
+github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -725,6 +743,8 @@ github.com/pocketbase/pocketbase v0.24.4 h1:kw/c23HccoxMV/19U9QlDcvNJgQ66vlUrxGQ
github.com/pocketbase/pocketbase v0.24.4/go.mod h1:EfXV/8RUY76jA6g1RPNHjOuW7wTd2bz0QlvAI/RU8YY=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw=
+github.com/povsister/scp v0.0.0-20240802064259-28781e87b246/go.mod h1:i1Au86ZXK0ZalQNyBp2njCcyhSCR/QP/AMfILip+zNI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
@@ -745,9 +765,11 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/qiniu/dyn v1.3.0 h1:s+xPTeV0H8yikgM4ZMBc7Rrefam8UNI3asBlkaOQg5o=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.25.2 h1:URwgZpxySdiwu2yQpHk93X4LXWHyFRp1x3Vmlk/YWvo=
github.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o=
+github.com/qiniu/x v1.10.5 h1:7V/CYWEmo9axJULvrJN6sMYh2FdY+esN5h8jwDkA4b0=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -913,6 +935,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 248af1b4..f53c7287 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -11,14 +11,18 @@ import (
providerAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53"
providerAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
providerCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
+ providerClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
+ providerGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname"
providerGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
providerNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
providerNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
providerNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
+ providerRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun"
providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
+ providerWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
)
@@ -112,6 +116,38 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeClouDNS:
+ {
+ access := domain.AccessConfigForClouDNS{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerClouDNS.NewChallengeProvider(&providerClouDNS.ClouDNSApplicantConfig{
+ AuthId: access.AuthId,
+ AuthPassword: access.AuthPassword,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
+ case domain.ApplyDNSProviderTypeGname:
+ {
+ access := domain.AccessConfigForGname{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerGname.NewChallengeProvider(&providerGname.GnameApplicantConfig{
+ AppId: access.AppId,
+ AppKey: access.AppKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypeGoDaddy:
{
access := domain.AccessConfigForGoDaddy{}
@@ -207,6 +243,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeRainYun:
+ {
+ access := domain.AccessConfigForRainYun{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerRainYun.NewChallengeProvider(&providerRainYun.RainYunApplicantConfig{
+ ApiKey: access.ApiKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
{
access := domain.AccessConfigForTencentCloud{}
@@ -238,6 +289,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
})
return applicant, err
}
+
+ case domain.ApplyDNSProviderTypeWestcn:
+ {
+ access := domain.AccessConfigForWestcn{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerWestcn.NewChallengeProvider(&providerWestcn.WestcnApplicantConfig{
+ Username: access.Username,
+ ApiPassword: access.ApiPassword,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
}
return nil, fmt.Errorf("unsupported applicant provider: %s", string(options.Provider))
diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go
index 6fd3457c..344c78e6 100644
--- a/internal/deployer/providers.go
+++ b/internal/deployer/providers.go
@@ -12,6 +12,8 @@ import (
providerAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live"
providerAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb"
providerAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss"
+ providerAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf"
+ providerAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront"
providerBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn"
providerBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
providerDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn"
@@ -21,6 +23,7 @@ import (
providerK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
providerLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
providerQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
+ providerQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
providerSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
providerTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
providerTencentCloudCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb"
@@ -48,7 +51,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
NOTICE: If you add new constant, please keep ASCII order.
*/
switch options.Provider {
- case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS:
+ case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF:
{
access := domain.AccessConfigForAliyun{}
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
@@ -126,6 +129,37 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
}, logger)
return deployer, logger, err
+ case domain.DeployProviderTypeAliyunWAF:
+ deployer, err := providerAliyunWAF.NewWithLogger(&providerAliyunWAF.AliyunWAFDeployerConfig{
+ AccessKeyId: access.AccessKeyId,
+ AccessKeySecret: access.AccessKeySecret,
+ Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
+ InstanceId: maps.GetValueAsString(options.ProviderDeployConfig, "instanceId"),
+ }, logger)
+ return deployer, logger, err
+
+ default:
+ break
+ }
+ }
+
+ case domain.DeployProviderTypeAWSCloudFront:
+ {
+ access := domain.AccessConfigForAWS{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ switch options.Provider {
+ case domain.DeployProviderTypeAWSCloudFront:
+ deployer, err := providerAWSCloudFront.NewWithLogger(&providerAWSCloudFront.AWSCloudFrontDeployerConfig{
+ AccessKeyId: access.AccessKeyId,
+ SecretAccessKey: access.SecretAccessKey,
+ Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
+ DistributionId: maps.GetValueAsString(options.ProviderDeployConfig, "distributionId"),
+ }, logger)
+ return deployer, logger, err
+
default:
break
}
@@ -138,12 +172,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
}
- deployer, err := providerBaiduCloudCDN.NewWithLogger(&providerBaiduCloudCDN.BaiduCloudCDNDeployerConfig{
- AccessKeyId: access.AccessKeyId,
- SecretAccessKey: access.SecretAccessKey,
- Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
- }, logger)
- return deployer, logger, err
+ switch options.Provider {
+ case domain.DeployProviderTypeBaiduCloudCDN:
+ deployer, err := providerBaiduCloudCDN.NewWithLogger(&providerBaiduCloudCDN.BaiduCloudCDNDeployerConfig{
+ AccessKeyId: access.AccessKeyId,
+ SecretAccessKey: access.SecretAccessKey,
+ Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
+ }, logger)
+ return deployer, logger, err
+
+ default:
+ break
+ }
}
case domain.DeployProviderTypeBytePlusCDN:
@@ -153,12 +193,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
}
- deployer, err := providerBytePlusCDN.NewWithLogger(&providerBytePlusCDN.BytePlusCDNDeployerConfig{
- AccessKey: access.AccessKey,
- SecretKey: access.SecretKey,
- Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
- }, logger)
- return deployer, logger, err
+ switch options.Provider {
+ case domain.DeployProviderTypeBytePlusCDN:
+ deployer, err := providerBytePlusCDN.NewWithLogger(&providerBytePlusCDN.BytePlusCDNDeployerConfig{
+ AccessKey: access.AccessKey,
+ SecretKey: access.SecretKey,
+ Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
+ }, logger)
+ return deployer, logger, err
+
+ default:
+ break
+ }
}
case domain.DeployProviderTypeDogeCloudCDN:
@@ -260,19 +306,34 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
return deployer, logger, err
}
- case domain.DeployProviderTypeQiniuCDN:
+ case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili:
{
access := domain.AccessConfigForQiniu{}
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
}
- deployer, err := providerQiniuCDN.NewWithLogger(&providerQiniuCDN.QiniuCDNDeployerConfig{
- AccessKey: access.AccessKey,
- SecretKey: access.SecretKey,
- Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
- }, logger)
- return deployer, logger, err
+ switch options.Provider {
+ case domain.DeployProviderTypeQiniuCDN:
+ deployer, err := providerQiniuCDN.NewWithLogger(&providerQiniuCDN.QiniuCDNDeployerConfig{
+ AccessKey: access.AccessKey,
+ SecretKey: access.SecretKey,
+ Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
+ }, logger)
+ return deployer, logger, err
+
+ case domain.DeployProviderTypeQiniuPili:
+ deployer, err := providerQiniuPili.NewWithLogger(&providerQiniuPili.QiniuPiliDeployerConfig{
+ AccessKey: access.AccessKey,
+ SecretKey: access.SecretKey,
+ Hub: maps.GetValueAsString(options.ProviderDeployConfig, "hub"),
+ Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
+ }, logger)
+ return deployer, logger, err
+
+ default:
+ break
+ }
}
case domain.DeployProviderTypeSSH:
@@ -289,6 +350,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
SshPassword: access.Password,
SshKey: access.Key,
SshKeyPassphrase: access.KeyPassphrase,
+ UseSCP: maps.GetValueAsBool(options.ProviderDeployConfig, "useSCP"),
PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))),
diff --git a/internal/domain/access.go b/internal/domain/access.go
index dda7d2b4..c8448c21 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -63,6 +63,11 @@ type AccessConfigForCloudflare struct {
DnsApiToken string `json:"dnsApiToken"`
}
+type AccessConfigForClouDNS struct {
+ AuthId string `json:"authId"`
+ AuthPassword string `json:"authPassword"`
+}
+
type AccessConfigForDogeCloud struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
@@ -73,6 +78,11 @@ type AccessConfigForEdgio struct {
ClientSecret string `json:"clientSecret"`
}
+type AccessConfigForGname struct {
+ AppId string `json:"appId"`
+ AppKey string `json:"appKey"`
+}
+
type AccessConfigForGoDaddy struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
@@ -112,6 +122,10 @@ type AccessConfigForQiniu struct {
SecretKey string `json:"secretKey"`
}
+type AccessConfigForRainYun struct {
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForSSH struct {
Host string `json:"host"`
Port int32 `json:"port"`
@@ -140,3 +154,8 @@ type AccessConfigForVolcEngine struct {
type AccessConfigForWebhook struct {
Url string `json:"url"`
}
+
+type AccessConfigForWestcn struct {
+ Username string `json:"username"`
+ ApiPassword string `json:"password"`
+}
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 34666531..894f8007 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -16,8 +16,10 @@ const (
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
+ AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeEdgio = AccessProviderType("edgio")
+ AccessProviderTypeGname = AccessProviderType("gname")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
@@ -27,11 +29,13 @@ const (
AccessProviderTypeNS1 = AccessProviderType("ns1")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
+ AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWebhook = AccessProviderType("webhook")
+ AccessProviderTypeWestcn = AccessProviderType("westcn")
)
type ApplyDNSProviderType string
@@ -51,6 +55,8 @@ const (
ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53")
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
+ ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
+ ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname")
ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy")
ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS]
ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns")
@@ -58,10 +64,12 @@ const (
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns")
+ ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun")
ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS]
ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns")
ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS]
ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns")
+ ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn")
)
type DeployProviderType string
@@ -81,6 +89,8 @@ const (
DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
+ DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf")
+ DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront")
DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn")
DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn")
@@ -90,6 +100,7 @@ const (
DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret")
DeployProviderTypeLocal = DeployProviderType("local")
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
+ DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
DeployProviderTypeSSH = DeployProviderType("ssh")
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb")
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go
new file mode 100644
index 00000000..09aac6df
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go
@@ -0,0 +1,39 @@
+package cloudns
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/cloudns"
+)
+
+type ClouDNSApplicantConfig struct {
+ AuthId string `json:"authId"`
+ AuthPassword string `json:"authPassword"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ClouDNSApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := cloudns.NewDefaultConfig()
+ providerConfig.AuthID = config.AuthId
+ providerConfig.AuthPassword = config.AuthPassword
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := cloudns.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go
new file mode 100644
index 00000000..90cec017
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go
@@ -0,0 +1,40 @@
+package gname
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+
+ internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal"
+)
+
+type GnameApplicantConfig struct {
+ AppId string `json:"appId"`
+ AppKey string `json:"appKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *GnameApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := internal.NewDefaultConfig()
+ providerConfig.AppID = config.AppId
+ providerConfig.AppKey = config.AppKey
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := internal.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go
new file mode 100644
index 00000000..979a803b
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go
@@ -0,0 +1,196 @@
+package lego_gname
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/challenge/dns01"
+ "github.com/go-acme/lego/v4/platform/config/env"
+
+ gnamesdk "github.com/usual2970/certimate/internal/pkg/vendors/gname-sdk"
+)
+
+const (
+ envNamespace = "GNAME_"
+
+ EnvAppID = envNamespace + "APP_ID"
+ EnvAppKey = envNamespace + "APP_KEY"
+
+ EnvTTL = envNamespace + "TTL"
+ EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+ EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
+ EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
+)
+
+var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
+
+type Config struct {
+ AppID string
+ AppKey string
+
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ HTTPTimeout time.Duration
+}
+
+type DNSProvider struct {
+ client *gnamesdk.GnameClient
+ config *Config
+}
+
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
+ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
+ PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+ HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+ }
+}
+
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get(EnvAppID, EnvAppKey)
+ if err != nil {
+ return nil, fmt.Errorf("gname: %w", err)
+ }
+
+ config := NewDefaultConfig()
+ config.AppID = values[EnvAppID]
+ config.AppKey = values[EnvAppKey]
+
+ return NewDNSProviderConfig(config)
+}
+
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("gname: the configuration of the DNS provider is nil")
+ }
+
+ client := gnamesdk.NewGnameClient(config.AppID, config.AppKey).
+ WithTimeout(config.HTTPTimeout)
+
+ return &DNSProvider{
+ client: client,
+ config: config,
+ }, nil
+}
+
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ info := dns01.GetChallengeInfo(domain, keyAuth)
+
+ zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
+ if err != nil {
+ return fmt.Errorf("gname: %w", err)
+ }
+
+ subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
+ if err != nil {
+ return fmt.Errorf("gname: %w", err)
+ }
+
+ if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil {
+ return fmt.Errorf("gname: %w", err)
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, value := dns01.GetRecord(domain, keyAuth)
+ subDomain := dns01.UnFqdn(fqdn)
+
+ if err := d.removeDNSRecord(domain, subDomain, value); err != nil {
+ return fmt.Errorf("gname: %w", err)
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+func (d *DNSProvider) getDNSRecord(domain, subDomain string) (*gnamesdk.ResolutionRecord, error) {
+ page := 1
+ pageSize := 20
+ for {
+ request := &gnamesdk.ListDomainResolutionRequest{}
+ request.ZoneName = domain
+ request.Page = &page
+ request.PageSize = &pageSize
+
+ response, err := d.client.ListDomainResolution(request)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, record := range response.Data {
+ if record.RecordType == "TXT" && record.RecordName == subDomain {
+ return record, nil
+ }
+ }
+
+ if len(response.Data) == 0 {
+ break
+ }
+ if response.Page*response.PageSize >= response.Count {
+ break
+ }
+
+ page++
+ }
+
+ return nil, nil
+}
+
+func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error {
+ record, err := d.getDNSRecord(domain, subDomain)
+ if err != nil {
+ return err
+ }
+
+ if record == nil {
+ request := &gnamesdk.AddDomainResolutionRequest{
+ ZoneName: domain,
+ RecordType: "TXT",
+ RecordName: subDomain,
+ RecordValue: value,
+ TTL: d.config.TTL,
+ }
+ _, err := d.client.AddDomainResolution(request)
+ return err
+ } else {
+ request := &gnamesdk.ModifyDomainResolutionRequest{
+ ID: record.ID,
+ ZoneName: domain,
+ RecordType: "TXT",
+ RecordName: subDomain,
+ RecordValue: value,
+ TTL: d.config.TTL,
+ }
+ _, err := d.client.ModifyDomainResolution(request)
+ return err
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
+ record, err := d.getDNSRecord(domain, subDomain)
+ if err != nil {
+ return err
+ }
+
+ if record == nil {
+ return nil
+ }
+
+ request := &gnamesdk.DeleteDomainResolutionRequest{
+ ZoneName: domain,
+ RecordID: record.ID,
+ }
+ _, err = d.client.DeleteDomainResolution(request)
+ return err
+}
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go
new file mode 100644
index 00000000..d250a279
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go
@@ -0,0 +1,37 @@
+package rainyun
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/rainyun"
+)
+
+type RainYunApplicantConfig struct {
+ ApiKey string `json:"apiKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *RainYunApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := rainyun.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 := rainyun.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go
new file mode 100644
index 00000000..f20b5d21
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go
@@ -0,0 +1,39 @@
+package westcn
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/westcn"
+)
+
+type WestcnApplicantConfig struct {
+ Username string `json:"username"`
+ ApiPassword string `json:"apiPassword"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *WestcnApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := westcn.NewDefaultConfig()
+ providerConfig.Username = config.Username
+ providerConfig.Password = config.ApiPassword
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := westcn.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go
index 355f4c0b..9878b6f4 100644
--- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go
+++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go
@@ -18,7 +18,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
)
type AliyunALBDeployerConfig struct {
@@ -73,22 +73,7 @@ func NewWithLogger(config *AliyunALBDeployerConfig, logger logger.Logger) (*Aliy
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- aliyunCasRegion := config.Region
- if aliyunCasRegion != "" {
- // 阿里云 CAS 服务接入点是独立于 ALB 服务的
- // 国内版固定接入点:华东一杭州
- // 国际版固定接入点:亚太东南一新加坡
- if !strings.HasPrefix(aliyunCasRegion, "cn-") {
- aliyunCasRegion = "ap-southeast-1"
- } else {
- aliyunCasRegion = "cn-hangzhou"
- }
- }
- uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{
- AccessKeyId: config.AccessKeyId,
- AccessKeySecret: config.AccessKeySecret,
- Region: aliyunCasRegion,
- })
+ uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
@@ -210,7 +195,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI
// 遍历更新监听证书
if len(listenerIds) == 0 {
- return xerrors.New("listener not found")
+ return errors.New("listener not found")
} else {
var errs []error
@@ -446,3 +431,24 @@ func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients
cas: casClient,
}, nil
}
+
+func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
+ casRegion := region
+ if casRegion != "" {
+ // 阿里云 CAS 服务接入点是独立于 ALB 服务的
+ // 国内版固定接入点:华东一杭州
+ // 国际版固定接入点:亚太东南一新加坡
+ if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
+ casRegion = "ap-southeast-1"
+ } else {
+ casRegion = "cn-hangzhou"
+ }
+ }
+
+ uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{
+ AccessKeyId: accessKeyId,
+ AccessKeySecret: accessKeySecret,
+ Region: casRegion,
+ })
+ return uploader, err
+}
diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go
index 1fe99458..4bd91611 100644
--- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go
+++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
)
type AliyunCLBDeployerConfig struct {
@@ -63,7 +63,7 @@ func NewWithLogger(config *AliyunCLBDeployerConfig, logger logger.Logger) (*Aliy
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := providerSlb.New(&providerSlb.AliyunSLBUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.AliyunSLBUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
@@ -161,7 +161,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI
// 遍历更新监听证书
if len(listenerPorts) == 0 {
- return xerrors.New("listener not found")
+ return errors.New("listener not found")
} else {
var errs []error
diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go
index 2b273bc2..6deee907 100644
--- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go
+++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go
@@ -14,7 +14,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
)
type AliyunNLBDeployerConfig struct {
@@ -61,22 +61,7 @@ func NewWithLogger(config *AliyunNLBDeployerConfig, logger logger.Logger) (*Aliy
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- aliyunCasRegion := config.Region
- if aliyunCasRegion != "" {
- // 阿里云 CAS 服务接入点是独立于 NLB 服务的
- // 国内版固定接入点:华东一杭州
- // 国际版固定接入点:亚太东南一新加坡
- if !strings.HasPrefix(aliyunCasRegion, "cn-") {
- aliyunCasRegion = "ap-southeast-1"
- } else {
- aliyunCasRegion = "cn-hangzhou"
- }
- }
- uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{
- AccessKeyId: config.AccessKeyId,
- AccessKeySecret: config.AccessKeySecret,
- Region: aliyunCasRegion,
- })
+ uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
@@ -168,7 +153,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI
// 遍历更新监听证书
if len(listenerIds) == 0 {
- return xerrors.New("listener not found")
+ return errors.New("listener not found")
} else {
var errs []error
@@ -249,3 +234,24 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Cl
return client, nil
}
+
+func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
+ casRegion := region
+ if casRegion != "" {
+ // 阿里云 CAS 服务接入点是独立于 NLB 服务的
+ // 国内版固定接入点:华东一杭州
+ // 国际版固定接入点:亚太东南一新加坡
+ if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
+ casRegion = "ap-southeast-1"
+ } else {
+ casRegion = "cn-hangzhou"
+ }
+ }
+
+ uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{
+ AccessKeyId: accessKeyId,
+ AccessKeySecret: accessKeySecret,
+ Region: casRegion,
+ })
+ return uploader, err
+}
diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go
new file mode 100644
index 00000000..75c634b8
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go
@@ -0,0 +1,150 @@
+package aliyunwaf
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+ aliyunWaf "github.com/alibabacloud-go/waf-openapi-20211001/v5/client"
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ "github.com/usual2970/certimate/internal/pkg/core/logger"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
+)
+
+type AliyunWAFDeployerConfig struct {
+ // 阿里云 AccessKeyId。
+ AccessKeyId string `json:"accessKeyId"`
+ // 阿里云 AccessKeySecret。
+ AccessKeySecret string `json:"accessKeySecret"`
+ // 阿里云地域。
+ Region string `json:"region"`
+ // 阿里云 WAF 实例 ID。
+ InstanceId string `json:"instanceId"`
+}
+
+type AliyunWAFDeployer struct {
+ config *AliyunWAFDeployerConfig
+ logger logger.Logger
+ sdkClient *aliyunWaf.Client
+ sslUploader uploader.Uploader
+}
+
+var _ deployer.Deployer = (*AliyunWAFDeployer)(nil)
+
+func New(config *AliyunWAFDeployerConfig) (*AliyunWAFDeployer, error) {
+ return NewWithLogger(config, logger.NewNilLogger())
+}
+
+func NewWithLogger(config *AliyunWAFDeployerConfig, logger logger.Logger) (*AliyunWAFDeployer, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ if logger == nil {
+ return nil, errors.New("logger is nil")
+ }
+
+ client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create ssl uploader")
+ }
+
+ return &AliyunWAFDeployer{
+ logger: logger,
+ config: config,
+ sdkClient: client,
+ sslUploader: uploader,
+ }, nil
+}
+
+func (d *AliyunWAFDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
+ if d.config.InstanceId == "" {
+ return nil, errors.New("config `instanceId` is required")
+ }
+
+ // 上传证书到 CAS
+ upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to upload certificate file")
+ }
+
+ d.logger.Logt("certificate file uploaded", upres)
+
+ // 查询默认 SSL/TLS 设置
+ // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps
+ describeDefaultHttpsReq := &aliyunWaf.DescribeDefaultHttpsRequest{
+ InstanceId: tea.String(d.config.InstanceId),
+ RegionId: tea.String(d.config.Region),
+ }
+ describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttps(describeDefaultHttpsReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDefaultHttps'")
+ }
+
+ d.logger.Logt("已查询到默认 SSL/TLS 设置", describeDefaultHttpsResp)
+
+ // 修改默认 SSL/TLS 设置
+ // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydefaulthttps
+ modifyDefaultHttpsReq := &aliyunWaf.ModifyDefaultHttpsRequest{
+ InstanceId: tea.String(d.config.InstanceId),
+ RegionId: tea.String(d.config.Region),
+ CertId: tea.String(upres.CertId),
+ TLSVersion: describeDefaultHttpsResp.Body.DefaultHttps.TLSVersion,
+ EnableTLSv3: describeDefaultHttpsResp.Body.DefaultHttps.EnableTLSv3,
+ }
+ modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttps(modifyDefaultHttpsReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDefaultHttps'")
+ }
+
+ d.logger.Logt("已修改默认 SSL/TLS 设置", modifyDefaultHttpsResp)
+
+ return &deployer.DeployResult{}, nil
+}
+
+func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunWaf.Client, error) {
+ config := &aliyunOpen.Config{
+ AccessKeyId: tea.String(accessKeyId),
+ AccessKeySecret: tea.String(accessKeySecret),
+ Endpoint: tea.String(fmt.Sprintf("wafopenapi.%s.aliyuncs.com", region)),
+ }
+
+ client, err := aliyunWaf.NewClient(config)
+ if err != nil {
+ return nil, err
+ }
+
+ return client, nil
+}
+
+func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
+ casRegion := region
+ if casRegion != "" {
+ // 阿里云 CAS 服务接入点是独立于 WAF 服务的
+ // 国内版固定接入点:华东一杭州
+ // 国际版固定接入点:亚太东南一新加坡
+ if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
+ casRegion = "ap-southeast-1"
+ } else {
+ casRegion = "cn-hangzhou"
+ }
+ }
+
+ uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{
+ AccessKeyId: accessKeyId,
+ AccessKeySecret: accessKeySecret,
+ Region: casRegion,
+ })
+ return uploader, err
+}
diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go
new file mode 100644
index 00000000..2498beca
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go
@@ -0,0 +1,80 @@
+package aliyunwaf_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fAccessKeyId string
+ fAccessKeySecret string
+ fRegion string
+ fInstanceId string
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNWAF_"
+
+ 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(&fInstanceId, argsPrefix+"INSTANCEID", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./aliyun_waf_test.go -args \
+ --CERTIMATE_DEPLOYER_ALIYUNWAF_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_ALIYUNWAF_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYID="your-access-key-id" \
+ --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYSECRET="your-access-key-secret" \
+ --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \
+ --CERTIMATE_DEPLOYER_ALIYUNWAF_INSTANCEID="your-waf-instance-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("REGION: %v", fRegion),
+ fmt.Sprintf("INSTANCEID: %v", fInstanceId),
+ }, "\n"))
+
+ deployer, err := provider.New(&provider.AliyunWAFDeployerConfig{
+ AccessKeyId: fAccessKeyId,
+ AccessKeySecret: fAccessKeySecret,
+ Region: fRegion,
+ InstanceId: fInstanceId,
+ })
+ 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)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go
new file mode 100644
index 00000000..086dd415
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go
@@ -0,0 +1,133 @@
+package awscloudfront
+
+import (
+ "context"
+ "errors"
+
+ aws "github.com/aws/aws-sdk-go-v2/aws"
+ awsCfg "github.com/aws/aws-sdk-go-v2/config"
+ awsCred "github.com/aws/aws-sdk-go-v2/credentials"
+ awsCf "github.com/aws/aws-sdk-go-v2/service/cloudfront"
+ awsCfTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ "github.com/usual2970/certimate/internal/pkg/core/logger"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
+)
+
+type AWSCloudFrontDeployerConfig struct {
+ // AWS AccessKeyId。
+ AccessKeyId string `json:"accessKeyId"`
+ // AWS SecretAccessKey。
+ SecretAccessKey string `json:"secretAccessKey"`
+ // AWS 区域。
+ Region string `json:"region"`
+ // AWS CloudFront 分配 ID。
+ DistributionId string `json:"distributionId"`
+}
+
+type AWSCloudFrontDeployer struct {
+ config *AWSCloudFrontDeployerConfig
+ logger logger.Logger
+ sdkClient *awsCf.Client
+ sslUploader uploader.Uploader
+}
+
+var _ deployer.Deployer = (*AWSCloudFrontDeployer)(nil)
+
+func New(config *AWSCloudFrontDeployerConfig) (*AWSCloudFrontDeployer, error) {
+ return NewWithLogger(config, logger.NewNilLogger())
+}
+
+func NewWithLogger(config *AWSCloudFrontDeployerConfig, logger logger.Logger) (*AWSCloudFrontDeployer, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ if logger == nil {
+ return nil, errors.New("logger is nil")
+ }
+
+ client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ uploader, err := uploaderp.New(&uploaderp.AWSCertificateManagerUploaderConfig{
+ AccessKeyId: config.AccessKeyId,
+ SecretAccessKey: config.SecretAccessKey,
+ Region: config.Region,
+ })
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create ssl uploader")
+ }
+
+ return &AWSCloudFrontDeployer{
+ logger: logger,
+ config: config,
+ sdkClient: client,
+ sslUploader: uploader,
+ }, nil
+}
+
+func (d *AWSCloudFrontDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
+ if d.config.DistributionId == "" {
+ return nil, errors.New("config `distribuitionId` is required")
+ }
+
+ // 上传证书到 ACM
+ upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to upload certificate file")
+ }
+
+ d.logger.Logt("certificate file uploaded", upres)
+
+ // 获取分配配置
+ // REF: https://docs.aws.amazon.com/en_us/cloudfront/latest/APIReference/API_GetDistributionConfig.html
+ getDistributionConfigReq := &awsCf.GetDistributionConfigInput{
+ Id: aws.String(d.config.DistributionId),
+ }
+ getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(context.TODO(), getDistributionConfigReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.GetDistributionConfig'")
+ }
+
+ d.logger.Logt("已获取分配配置", getDistributionConfigResp)
+
+ // 更新分配配置
+ // REF: https://docs.aws.amazon.com/zh_cn/cloudfront/latest/APIReference/API_UpdateDistribution.html
+ updateDistributionReq := &awsCf.UpdateDistributionInput{
+ Id: aws.String(d.config.DistributionId),
+ DistributionConfig: getDistributionConfigResp.DistributionConfig,
+ IfMatch: getDistributionConfigResp.ETag,
+ }
+ if updateDistributionReq.DistributionConfig.ViewerCertificate == nil {
+ updateDistributionReq.DistributionConfig.ViewerCertificate = &awsCfTypes.ViewerCertificate{}
+ }
+ updateDistributionReq.DistributionConfig.ViewerCertificate.CloudFrontDefaultCertificate = aws.Bool(false)
+ updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = aws.String(upres.CertId)
+ updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.UpdateDistribution'")
+ }
+
+ d.logger.Logt("已更新分配配置", updateDistributionResp)
+
+ return &deployer.DeployResult{}, nil
+}
+
+func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsCf.Client, error) {
+ cfg, err := awsCfg.LoadDefaultConfig(context.TODO())
+ if err != nil {
+ return nil, err
+ }
+
+ client := awsCf.NewFromConfig(cfg, func(o *awsCf.Options) {
+ o.Region = region
+ o.Credentials = aws.NewCredentialsCache(awsCred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
+ })
+ return client, nil
+}
diff --git a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go
new file mode 100644
index 00000000..89997b37
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go
@@ -0,0 +1,80 @@
+package awscloudfront_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fAccessKeyId string
+ fSecretAccessKey string
+ fRegion string
+ fDistribuitionId string
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_AWSCLOUDFRONT_"
+
+ flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
+ flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
+ flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
+ flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
+ flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
+ flag.StringVar(&fDistribuitionId, argsPrefix+"DISTRIBUTIONID", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./aws_cloudfront_test.go -args \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_ACCESSKEYID="your-access-key-id" \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_SECRETACCESSKEY="your-secret-access-id" \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_REGION="us-east-1" \
+ --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_DISTRIBUTIONID="your-distribution-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("SECRETACCESSKEY: %v", fSecretAccessKey),
+ fmt.Sprintf("REGION: %v", fRegion),
+ fmt.Sprintf("DISTRIBUTIONID: %v", fDistribuitionId),
+ }, "\n"))
+
+ deployer, err := provider.New(&provider.AWSCloudFrontDeployerConfig{
+ AccessKeyId: fAccessKeyId,
+ SecretAccessKey: fSecretAccessKey,
+ Region: fRegion,
+ DistribuitionId: fDistribuitionId,
+ })
+ 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)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go
index 9497bb6a..2a695513 100644
--- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go
+++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go
@@ -12,7 +12,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn"
)
type BytePlusCDNDeployerConfig struct {
@@ -50,7 +50,7 @@ func NewWithLogger(config *BytePlusCDNDeployerConfig, logger logger.Logger) (*By
client.Client.SetAccessKey(config.AccessKey)
client.Client.SetSecretKey(config.SecretKey)
- uploader, err := providerCdn.New(&providerCdn.ByteplusCDNUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.ByteplusCDNUploaderConfig{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
})
@@ -103,7 +103,7 @@ func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privke
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
// 所有可关联的域名都配置了该证书,跳过部署
} else {
- return nil, xerrors.New("domain not found")
+ return nil, errors.New("domain not found")
}
}
} else {
diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
index 4d9f4ac4..283d9907 100644
--- a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
+++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
@@ -10,7 +10,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
)
@@ -47,7 +47,7 @@ func NewWithLogger(config *DogeCloudCDNDeployerConfig, logger logger.Logger) (*D
client := dogesdk.NewClient(config.AccessKey, config.SecretKey)
- uploader, err := providerDoge.New(&providerDoge.DogeCloudUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.DogeCloudUploaderConfig{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go
index 5c303ce4..31782af3 100644
--- a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go
+++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
)
@@ -59,7 +59,7 @@ func NewWithLogger(config *HuaweiCloudCDNDeployerConfig, logger logger.Logger) (
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := providerScm.New(&providerScm.HuaweiCloudSCMUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.HuaweiCloudSCMUploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
})
diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
index 5a12e101..3fa17a70 100644
--- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
+++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
@@ -19,7 +19,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
)
@@ -70,7 +70,7 @@ func NewWithLogger(config *HuaweiCloudELBDeployerConfig, logger logger.Logger) (
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := providerElb.New(&providerElb.HuaweiCloudELBUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.HuaweiCloudELBUploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
Region: config.Region,
@@ -205,7 +205,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certP
// 遍历更新监听器证书
if len(listenerIds) == 0 {
- return xerrors.New("listener not found")
+ return errors.New("listener not found")
} else {
var errs []error
diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go
index 03ae1762..6591da1b 100644
--- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go
+++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go
@@ -11,7 +11,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
qiniusdk "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
)
@@ -48,7 +48,7 @@ func NewWithLogger(config *QiniuCDNDeployerConfig, logger logger.Logger) (*Qiniu
client := qiniusdk.NewClient(auth.New(config.AccessKey, config.SecretKey))
- uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.QiniuSSLCertUploaderConfig{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go
new file mode 100644
index 00000000..4530071c
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go
@@ -0,0 +1,91 @@
+package qiniupili
+
+import (
+ "context"
+ "errors"
+
+ xerrors "github.com/pkg/errors"
+ "github.com/qiniu/go-sdk/v7/pili"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ "github.com/usual2970/certimate/internal/pkg/core/logger"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
+)
+
+type QiniuPiliDeployerConfig struct {
+ // 七牛云 AccessKey。
+ AccessKey string `json:"accessKey"`
+ // 七牛云 SecretKey。
+ SecretKey string `json:"secretKey"`
+ // 直播空间名。
+ Hub string `json:"hub"`
+ // 直播流域名(不支持泛域名)。
+ Domain string `json:"domain"`
+}
+
+type QiniuPiliDeployer struct {
+ config *QiniuPiliDeployerConfig
+ logger logger.Logger
+ sdkClient *pili.Manager
+ sslUploader uploader.Uploader
+}
+
+var _ deployer.Deployer = (*QiniuPiliDeployer)(nil)
+
+func New(config *QiniuPiliDeployerConfig) (*QiniuPiliDeployer, error) {
+ return NewWithLogger(config, logger.NewNilLogger())
+}
+
+func NewWithLogger(config *QiniuPiliDeployerConfig, logger logger.Logger) (*QiniuPiliDeployer, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ if logger == nil {
+ return nil, errors.New("logger is nil")
+ }
+
+ manager := pili.NewManager(pili.ManagerConfig{AccessKey: config.AccessKey, SecretKey: config.SecretKey})
+
+ uploader, err := uploaderp.New(&uploaderp.QiniuSSLCertUploaderConfig{
+ AccessKey: config.AccessKey,
+ SecretKey: config.SecretKey,
+ })
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create ssl uploader")
+ }
+
+ return &QiniuPiliDeployer{
+ logger: logger,
+ config: config,
+ sdkClient: manager,
+ sslUploader: uploader,
+ }, nil
+}
+
+func (d *QiniuPiliDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
+ // 上传证书到 CDN
+ upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to upload certificate file")
+ }
+
+ d.logger.Logt("certificate file uploaded", upres)
+
+ // 修改域名证书配置
+ // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#66
+ setDomainCertReq := pili.SetDomainCertRequest{
+ Hub: d.config.Hub,
+ Domain: d.config.Domain,
+ CertName: upres.CertName,
+ }
+ err = d.sdkClient.SetDomainCert(context.TODO(), setDomainCertReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'pili.SetDomainCert'")
+ }
+
+ d.logger.Logt("已修改域名证书配置")
+
+ return &deployer.DeployResult{}, nil
+}
diff --git a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go
new file mode 100644
index 00000000..06ef47e4
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go
@@ -0,0 +1,79 @@
+package qiniupili_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fAccessKey string
+ fSecretKey string
+ fHub string
+ fDomain string
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_QINIUPILI_"
+
+ flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
+ flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
+ flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
+ flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
+ flag.StringVar(&fHub, argsPrefix+"HUB", "", "")
+ flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./qiniu_pili_test.go -args \
+ --CERTIMATE_DEPLOYER_QINIUPILI_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_QINIUPILI_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_QINIUPILI_ACCESSKEY="your-access-key" \
+ --CERTIMATE_DEPLOYER_QINIUPILI_SECRETKEY="your-secret-key" \
+ --CERTIMATE_DEPLOYER_QINIUPILI_HUB="your-hub-name" \
+ --CERTIMATE_DEPLOYER_QINIUPILI_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("ACCESSKEY: %v", fAccessKey),
+ fmt.Sprintf("SECRETKEY: %v", fSecretKey),
+ fmt.Sprintf("HUB: %v", fHub),
+ fmt.Sprintf("DOMAIN: %v", fDomain),
+ }, "\n"))
+
+ deployer, err := provider.New(&provider.QiniuPiliDeployerConfig{
+ AccessKey: fAccessKey,
+ SecretKey: fSecretKey,
+ 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)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go
index 4fffce74..20ea348a 100644
--- a/internal/pkg/core/deployer/providers/ssh/ssh.go
+++ b/internal/pkg/core/deployer/providers/ssh/ssh.go
@@ -10,6 +10,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/pkg/sftp"
+ "github.com/povsister/scp"
"golang.org/x/crypto/ssh"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
@@ -32,6 +33,8 @@ type SshDeployerConfig struct {
SshKey string `json:"sshKey,omitempty"`
// SSH 登录私钥口令。
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
+ // 是否回退使用 SCP。
+ UseSCP bool `json:"useSCP,omitempty"`
// 前置命令。
PreCommand string `json:"preCommand,omitempty"`
// 后置命令。
@@ -112,13 +115,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
// 上传证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
- if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil {
+ if err := writeFileString(client, d.config.UseSCP, d.config.OutputCertPath, certPem); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
d.logger.Logt("certificate file uploaded")
- if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil {
+ if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPem); err != nil {
return nil, xerrors.Wrap(err, "failed to upload private key file")
}
@@ -132,7 +135,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
d.logger.Logt("certificate transformed to PFX")
- if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil {
+ if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, pfxData); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
@@ -146,7 +149,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
d.logger.Logt("certificate transformed to JKS")
- if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil {
+ if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, jksData); err != nil {
return nil, xerrors.Wrap(err, "failed to upload certificate file")
}
@@ -223,11 +226,47 @@ func execSshCommand(sshCli *ssh.Client, command string) (string, string, error)
return stdoutBuf.String(), stderrBuf.String(), nil
}
-func writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
- return writeSftpFile(sshCli, path, []byte(content))
+func writeFileString(sshCli *ssh.Client, useSCP bool, path string, content string) error {
+ if useSCP {
+ return writeFileStringWithSCP(sshCli, path, content)
+ }
+
+ return writeFileStringWithSFTP(sshCli, path, content)
}
-func writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
+func writeFile(sshCli *ssh.Client, useSCP bool, path string, data []byte) error {
+ if useSCP {
+ return writeFileWithSCP(sshCli, path, data)
+ }
+
+ return writeFileWithSFTP(sshCli, path, data)
+}
+
+func writeFileStringWithSCP(sshCli *ssh.Client, path string, content string) error {
+ return writeFileWithSCP(sshCli, path, []byte(content))
+}
+
+func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error {
+ scpCli, err := scp.NewClientFromExistingSSH(sshCli, &scp.ClientOption{})
+ if err != nil {
+ return xerrors.Wrap(err, "failed to create scp client")
+ }
+ defer scpCli.Close()
+
+ reader := bytes.NewReader(data)
+ err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{})
+ if err != nil {
+ return xerrors.Wrap(err, "failed to write to remote file")
+ }
+
+ return nil
+}
+
+func writeFileStringWithSFTP(sshCli *ssh.Client, path string, content string) error {
+ return writeFileWithSFTP(sshCli, path, []byte(content))
+}
+
+func writeFileWithSFTP(sshCli *ssh.Client, path string, data []byte) error {
sftpCli, err := sftp.NewClient(sshCli)
if err != nil {
return xerrors.Wrap(err, "failed to create sftp client")
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go
index a4a05a1b..61418845 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go
@@ -15,7 +15,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudCDNDeployerConfig struct {
@@ -59,7 +59,7 @@ func NewWithLogger(config *TencentCloudCDNDeployerConfig, logger logger.Logger)
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go
index 17958eac..d67eb383 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go
@@ -14,7 +14,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudCLBDeployerConfig struct {
@@ -69,7 +69,7 @@ func NewWithLogger(config *TencentCloudCLBDeployerConfig, logger logger.Logger)
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
@@ -183,7 +183,7 @@ func (d *TencentCloudCLBDeployer) deployToLoadbalancer(ctx context.Context, clou
// 遍历更新监听器证书
if len(listenerIds) == 0 {
- return xerrors.New("listener not found")
+ return errors.New("listener not found")
} else {
var errs []error
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go
index 23c2d11a..c2af84fb 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudCOSDeployerConfig struct {
@@ -56,7 +56,7 @@ func NewWithLogger(config *TencentCloudCOSDeployerConfig, logger logger.Logger)
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go b/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go
index 391fc05b..31057017 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go
@@ -12,7 +12,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudCSSDeployerConfig struct {
@@ -51,7 +51,7 @@ func NewWithLogger(config *TencentCloudCSSDeployerConfig, logger logger.Logger)
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go
index 29f6e33c..4cddbc59 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go
@@ -14,7 +14,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudECDNDeployerConfig struct {
@@ -58,7 +58,7 @@ func NewWithLogger(config *TencentCloudECDNDeployerConfig, logger logger.Logger)
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
diff --git a/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go
index fb45ea9e..2de4dcc5 100644
--- a/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go
+++ b/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
)
type TencentCloudEODeployerConfig struct {
@@ -59,7 +59,7 @@ func NewWithLogger(config *TencentCloudEODeployerConfig, logger logger.Logger) (
return nil, xerrors.Wrap(err, "failed to create sdk clients")
}
- uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{
SecretId: config.SecretId,
SecretKey: config.SecretKey,
})
@@ -77,7 +77,7 @@ func NewWithLogger(config *TencentCloudEODeployerConfig, logger logger.Logger) (
func (d *TencentCloudEODeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
if d.config.ZoneId == "" {
- return nil, xerrors.New("config `zoneId` is required")
+ return nil, errors.New("config `zoneId` is required")
}
// 上传证书到 SSL
diff --git a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go
index ec127f41..fa104ba3 100644
--- a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go
+++ b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl"
)
type UCloudUCDNDeployerConfig struct {
@@ -54,7 +54,7 @@ func NewWithLogger(config *UCloudUCDNDeployerConfig, logger logger.Logger) (*UCl
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.UCloudUSSLUploaderConfig{
PrivateKey: config.PrivateKey,
PublicKey: config.PublicKey,
ProjectId: config.ProjectId,
diff --git a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go
index b55d1acc..ccf03224 100644
--- a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go
+++ b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go
@@ -11,7 +11,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl"
usdkFile "github.com/usual2970/certimate/internal/pkg/vendors/ucloud-sdk/ufile"
)
@@ -57,7 +57,7 @@ func NewWithLogger(config *UCloudUS3DeployerConfig, logger logger.Logger) (*UClo
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.UCloudUSSLUploaderConfig{
PrivateKey: config.PrivateKey,
PublicKey: config.PublicKey,
ProjectId: config.ProjectId,
diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go
index 02adaf1b..de3e76e4 100644
--- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go
+++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go
@@ -12,7 +12,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
)
type VolcEngineCDNDeployerConfig struct {
@@ -50,7 +50,7 @@ func NewWithLogger(config *VolcEngineCDNDeployerConfig, logger logger.Logger) (*
client.Client.SetAccessKey(config.AccessKeyId)
client.Client.SetSecretKey(config.AccessKeySecret)
- uploader, err := uploaderCdn.New(&uploaderCdn.VolcEngineCDNUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.VolcEngineCDNUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
})
@@ -103,7 +103,7 @@ func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, priv
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
// 所有可关联的域名都配置了该证书,跳过部署
} else {
- return nil, xerrors.New("domain not found")
+ return nil, errors.New("domain not found")
}
}
} else {
diff --git a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
index b4acbfa0..08c096fd 100644
--- a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
+++ b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
)
type VolcEngineCLBDeployerConfig struct {
@@ -57,7 +57,7 @@ func NewWithLogger(config *VolcEngineCLBDeployerConfig, logger logger.Logger) (*
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
diff --git a/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go b/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go
index 86c8776d..0f9f8e51 100644
--- a/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go
+++ b/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
)
type VolcEngineDCDNDeployerConfig struct {
@@ -54,7 +54,7 @@ func NewWithLogger(config *VolcEngineDCDNDeployerConfig, logger logger.Logger) (
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go
index 083837b7..b23f4c6c 100644
--- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go
+++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go
@@ -13,7 +13,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderLive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live"
)
type VolcEngineLiveDeployerConfig struct {
@@ -51,7 +51,7 @@ func NewWithLogger(config *VolcEngineLiveDeployerConfig, logger logger.Logger) (
client.SetAccessKey(config.AccessKeyId)
client.SetSecretKey(config.AccessKeySecret)
- uploader, err := uploaderLive.New(&uploaderLive.VolcEngineLiveUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.VolcEngineLiveUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
})
diff --git a/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go b/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go
index 0c5501ec..9a238f27 100644
--- a/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go
+++ b/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go
@@ -11,7 +11,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter"
)
type VolcEngineTOSDeployerConfig struct {
@@ -54,7 +54,7 @@ func NewWithLogger(config *VolcEngineTOSDeployerConfig, logger logger.Logger) (*
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
- uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{
+ uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
Region: config.Region,
diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go
new file mode 100644
index 00000000..cac4d833
--- /dev/null
+++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go
@@ -0,0 +1,96 @@
+package awsacm
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ aws "github.com/aws/aws-sdk-go-v2/aws"
+ awsCfg "github.com/aws/aws-sdk-go-v2/config"
+ awsCred "github.com/aws/aws-sdk-go-v2/credentials"
+ awsAcm "github.com/aws/aws-sdk-go-v2/service/acm"
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
+)
+
+type AWSCertificateManagerUploaderConfig struct {
+ // AWS AccessKeyId。
+ AccessKeyId string `json:"accessKeyId"`
+ // AWS SecretAccessKey。
+ SecretAccessKey string `json:"secretAccessKey"`
+ // AWS 区域。
+ Region string `json:"region"`
+}
+
+type AWSCertificateManagerUploader struct {
+ config *AWSCertificateManagerUploaderConfig
+ sdkClient *awsAcm.Client
+}
+
+var _ uploader.Uploader = (*AWSCertificateManagerUploader)(nil)
+
+func New(config *AWSCertificateManagerUploaderConfig) (*AWSCertificateManagerUploader, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ return &AWSCertificateManagerUploader{
+ config: config,
+ sdkClient: client,
+ }, nil
+}
+
+func (u *AWSCertificateManagerUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
+ // 解析证书内容
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
+ if err != nil {
+ return nil, err
+ }
+
+ // 生成 AWS 所需的服务端证书和证书链参数
+ scertPem, _ := certs.ConvertCertificateToPEM(certX509)
+ bcertPem := certPem
+
+ // 生成新证书名(需符合 AWS 命名规则)
+ var certId, certName string
+ certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
+
+ // 导入证书
+ // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
+ importCertificateReq := &awsAcm.ImportCertificateInput{
+ Certificate: ([]byte)(scertPem),
+ CertificateChain: ([]byte)(bcertPem),
+ PrivateKey: ([]byte)(privkeyPem),
+ }
+ importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'acm.ImportCertificate'")
+ }
+
+ certId = *importCertificateResp.CertificateArn
+ return &uploader.UploadResult{
+ CertId: certId,
+ CertName: certName,
+ }, nil
+}
+
+func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsAcm.Client, error) {
+ cfg, err := awsCfg.LoadDefaultConfig(context.TODO())
+ if err != nil {
+ return nil, err
+ }
+
+ client := awsAcm.NewFromConfig(cfg, func(o *awsAcm.Options) {
+ o.Region = region
+ o.Credentials = aws.NewCredentialsCache(awsCred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
+ })
+ return client, nil
+}
diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
index 223c108e..9c5fa2b3 100644
--- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
+++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
@@ -76,7 +76,7 @@ func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkey
uploadNormalCertificateResp, err := u.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq)
if err != nil {
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 {
- return u.getExistCert(ctx, certPem, privkeyPem)
+ return u.getExistCert(ctx, certPem)
}
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.UploadNormalCertificate'")
@@ -92,7 +92,7 @@ func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkey
}, nil
}
-func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
+func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
diff --git a/internal/pkg/utils/certs/common.go b/internal/pkg/utils/certs/common.go
index fe5d041a..03fbeaeb 100644
--- a/internal/pkg/utils/certs/common.go
+++ b/internal/pkg/utils/certs/common.go
@@ -14,6 +14,10 @@ import (
// 出参:
// - 是否相同。
func EqualCertificate(a, b *x509.Certificate) bool {
+ if a == nil || b == nil {
+ return false
+ }
+
return string(a.Signature) == string(b.Signature) &&
a.SignatureAlgorithm == b.SignatureAlgorithm &&
a.SerialNumber.String() == b.SerialNumber.String() &&
diff --git a/internal/pkg/utils/certs/converter.go b/internal/pkg/utils/certs/converter.go
index a3272c45..74a9a5ab 100644
--- a/internal/pkg/utils/certs/converter.go
+++ b/internal/pkg/utils/certs/converter.go
@@ -4,10 +4,32 @@ import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
+ "errors"
xerrors "github.com/pkg/errors"
)
+// 将 x509.Certificate 对象转换为 PEM 编码的字符串。
+//
+// 入参:
+// - cert: x509.Certificate 对象。
+//
+// 出参:
+// - certPem: 证书 PEM 内容。
+// - err: 错误。
+func ConvertCertificateToPEM(cert *x509.Certificate) (certPem string, err error) {
+ if cert == nil {
+ return "", errors.New("`cert` is nil")
+ }
+
+ block := &pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: cert.Raw,
+ }
+
+ return string(pem.EncodeToMemory(block)), nil
+}
+
// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。
//
// 入参:
@@ -17,6 +39,10 @@ import (
// - privkeyPem: 私钥 PEM 内容。
// - err: 错误。
func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) {
+ if privkey == nil {
+ return "", errors.New("`privkey` is nil")
+ }
+
data, err := x509.MarshalECPrivateKey(privkey)
if err != nil {
return "", xerrors.Wrap(err, "failed to marshal EC private key")
diff --git a/internal/pkg/vendors/gname-sdk/api.go b/internal/pkg/vendors/gname-sdk/api.go
new file mode 100644
index 00000000..33972adc
--- /dev/null
+++ b/internal/pkg/vendors/gname-sdk/api.go
@@ -0,0 +1,102 @@
+package gnamesdk
+
+type BaseResponse interface {
+ GetCode() int
+ GetMsg() string
+}
+
+type AddDomainResolutionRequest struct {
+ ZoneName string `json:"ym"`
+ RecordType string `json:"lx"`
+ RecordName string `json:"zj"`
+ RecordValue string `json:"jlz"`
+ MX int `json:"mx"`
+ TTL int `json:"ttl"`
+}
+
+type AddDomainResolutionResponse struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+ Data int `json:"data"`
+}
+
+func (r *AddDomainResolutionResponse) GetCode() int {
+ return r.Code
+}
+
+func (r *AddDomainResolutionResponse) GetMsg() string {
+ return r.Msg
+}
+
+type ModifyDomainResolutionRequest struct {
+ ID string `json:"jxid"`
+ ZoneName string `json:"ym"`
+ RecordType string `json:"lx"`
+ RecordName string `json:"zj"`
+ RecordValue string `json:"jlz"`
+ MX int `json:"mx"`
+ TTL int `json:"ttl"`
+}
+
+type ModifyDomainResolutionResponse struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+}
+
+func (r *ModifyDomainResolutionResponse) GetCode() int {
+ return r.Code
+}
+
+func (r *ModifyDomainResolutionResponse) GetMsg() string {
+ return r.Msg
+}
+
+type DeleteDomainResolutionRequest struct {
+ ZoneName string `json:"ym"`
+ RecordID string `json:"jxid"`
+}
+
+type DeleteDomainResolutionResponse struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+}
+
+func (r *DeleteDomainResolutionResponse) GetCode() int {
+ return r.Code
+}
+
+func (r *DeleteDomainResolutionResponse) GetMsg() string {
+ return r.Msg
+}
+
+type ListDomainResolutionRequest struct {
+ ZoneName string `json:"ym"`
+ Page *int `json:"page,omitempty"`
+ PageSize *int `json:"limit,omitempty"`
+}
+
+type ListDomainResolutionResponse struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+ Count int `json:"count"`
+ Data []*ResolutionRecord `json:"data"`
+ Page int `json:"page"`
+ PageSize int `json:"pagesize"`
+}
+
+type ResolutionRecord struct {
+ ID string `json:"id"`
+ ZoneName string `json:"ym"`
+ RecordType string `json:"lx"`
+ RecordName string `json:"zjt"`
+ RecordValue string `json:"jxz"`
+ MX int `json:"mx"`
+}
+
+func (r *ListDomainResolutionResponse) GetCode() int {
+ return r.Code
+}
+
+func (r *ListDomainResolutionResponse) GetMsg() string {
+ return r.Msg
+}
diff --git a/internal/pkg/vendors/gname-sdk/client.go b/internal/pkg/vendors/gname-sdk/client.go
new file mode 100644
index 00000000..d034cfeb
--- /dev/null
+++ b/internal/pkg/vendors/gname-sdk/client.go
@@ -0,0 +1,162 @@
+package gnamesdk
+
+import (
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+
+ "github.com/usual2970/certimate/internal/pkg/utils/maps"
+)
+
+type GnameClient struct {
+ appId string
+ appKey string
+ client *resty.Client
+}
+
+func NewGnameClient(appId, appKey string) *GnameClient {
+ client := resty.New()
+
+ return &GnameClient{
+ appId: appId,
+ appKey: appKey,
+ client: client,
+ }
+}
+
+func (c *GnameClient) WithTimeout(timeout time.Duration) *GnameClient {
+ c.client.SetTimeout(timeout)
+ return c
+}
+
+func (c *GnameClient) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) {
+ params := make(map[string]any)
+ jsonData, _ := json.Marshal(req)
+ json.Unmarshal(jsonData, ¶ms)
+
+ result := AddDomainResolutionResponse{}
+ err := c.sendRequestWithResult("/api/resolution/add", params, &result)
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (c *GnameClient) ModifyDomainResolution(req *ModifyDomainResolutionRequest) (*ModifyDomainResolutionResponse, error) {
+ params := make(map[string]any)
+ jsonData, _ := json.Marshal(req)
+ json.Unmarshal(jsonData, ¶ms)
+
+ result := ModifyDomainResolutionResponse{}
+ err := c.sendRequestWithResult("/api/resolution/edit", params, &result)
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (c *GnameClient) DeleteDomainResolution(req *DeleteDomainResolutionRequest) (*DeleteDomainResolutionResponse, error) {
+ params := make(map[string]any)
+ jsonData, _ := json.Marshal(req)
+ json.Unmarshal(jsonData, ¶ms)
+
+ result := DeleteDomainResolutionResponse{}
+ err := c.sendRequestWithResult("/api/resolution/delete", params, &result)
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (c *GnameClient) ListDomainResolution(req *ListDomainResolutionRequest) (*ListDomainResolutionResponse, error) {
+ params := make(map[string]any)
+ jsonData, _ := json.Marshal(req)
+ json.Unmarshal(jsonData, ¶ms)
+
+ result := ListDomainResolutionResponse{}
+ err := c.sendRequestWithResult("/api/resolution/list", params, &result)
+ if err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (c *GnameClient) generateSignature(params map[string]string) string {
+ // Step 1: Sort parameters by ASCII order
+ var keys []string
+ for k := range params {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ // Step 2: Create string A with URL-encoded values
+ var pairs []string
+ for _, k := range keys {
+ encodedValue := url.QueryEscape(params[k])
+ pairs = append(pairs, fmt.Sprintf("%s=%s", k, encodedValue))
+ }
+ stringA := strings.Join(pairs, "&")
+
+ // Step 3: Append appkey to create string B
+ stringB := stringA + c.appKey
+
+ // Step 4: Calculate MD5 and convert to uppercase
+ hash := md5.Sum([]byte(stringB))
+ return strings.ToUpper(fmt.Sprintf("%x", hash))
+}
+
+func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Response, error) {
+ if params == nil {
+ params = make(map[string]any)
+ }
+
+ data := make(map[string]string)
+ for k, v := range params {
+ data[k] = fmt.Sprintf("%v", v)
+ }
+ data["appid"] = c.appId
+ data["gntime"] = fmt.Sprintf("%d", time.Now().Unix())
+ data["gntoken"] = c.generateSignature(data)
+
+ url := "http://api.gname.com" + path
+ req := c.client.R().
+ SetHeader("Content-Type", "application/x-www-form-urlencoded").
+ SetFormData(data)
+ resp, err := req.Post(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
+ }
+
+ return resp, nil
+}
+
+func (c *GnameClient) sendRequestWithResult(path string, params map[string]any, result BaseResponse) error {
+ resp, err := c.sendRequest(path, params)
+ if err != nil {
+ return err
+ }
+
+ jsonResp := make(map[string]any)
+ if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
+ }
+ if err := maps.Decode(jsonResp, &result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ if result.GetCode() != 1 {
+ return fmt.Errorf("API error: %s", result.GetMsg())
+ }
+
+ return nil
+}
diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs
index 6bd9b5fd..d82915f5 100644
--- a/ui/.eslintrc.cjs
+++ b/ui/.eslintrc.cjs
@@ -84,6 +84,7 @@ module.exports = {
pathGroupsExcludedImportTypes: ["builtin"],
alphabetize: {
order: "asc",
+ caseInsensitive: true,
},
},
],
diff --git a/ui/public/imgs/providers/cloudns.svg b/ui/public/imgs/providers/cloudns.svg
new file mode 100644
index 00000000..44cf1ed2
--- /dev/null
+++ b/ui/public/imgs/providers/cloudns.svg
@@ -0,0 +1,99 @@
+
diff --git a/ui/public/imgs/providers/gname.svg b/ui/public/imgs/providers/gname.svg
new file mode 100644
index 00000000..ec409ca5
--- /dev/null
+++ b/ui/public/imgs/providers/gname.svg
@@ -0,0 +1 @@
+
diff --git a/ui/public/imgs/providers/rainyun.svg b/ui/public/imgs/providers/rainyun.svg
new file mode 100644
index 00000000..1076cb76
--- /dev/null
+++ b/ui/public/imgs/providers/rainyun.svg
@@ -0,0 +1 @@
+
diff --git a/ui/public/imgs/providers/westcn.svg b/ui/public/imgs/providers/westcn.svg
new file mode 100644
index 00000000..9ea5a83c
--- /dev/null
+++ b/ui/public/imgs/providers/westcn.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index afe49ccd..c6c44d2d 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -10,28 +10,32 @@ import { ACCESS_PROVIDERS } from "@/domain/provider";
import { useAntdForm, useAntdFormName } from "@/hooks";
import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig";
-import AccessFormAWSConfig from "./AccessFormAWSConfig";
import AccessFormAliyunConfig from "./AccessFormAliyunConfig";
+import AccessFormAWSConfig from "./AccessFormAWSConfig";
import AccessFormAzureConfig from "./AccessFormAzureConfig";
import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig";
import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig";
import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig";
+import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig";
import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig";
import AccessFormEdgioConfig from "./AccessFormEdgioConfig";
+import AccessFormGnameConfig from "./AccessFormGnameConfig";
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
import AccessFormLocalConfig from "./AccessFormLocalConfig";
-import AccessFormNS1Config from "./AccessFormNS1Config";
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
+import AccessFormNS1Config from "./AccessFormNS1Config";
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
import AccessFormQiniuConfig from "./AccessFormQiniuConfig";
+import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
import AccessFormSSHConfig from "./AccessFormSSHConfig";
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
+import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
type AccessFormFieldValues = Partial>;
type AccessFormPresets = "add" | "edit";
@@ -99,8 +103,12 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.CLOUDFLARE:
return ;
+ case ACCESS_PROVIDERS.CLOUDNS:
+ return ;
case ACCESS_PROVIDERS.DOGECLOUD:
return ;
+ case ACCESS_PROVIDERS.GNAME:
+ return ;
case ACCESS_PROVIDERS.GODADDY:
return ;
case ACCESS_PROVIDERS.EDGIO:
@@ -121,6 +129,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.QINIU:
return ;
+ case ACCESS_PROVIDERS.RAINYUN:
+ return ;
case ACCESS_PROVIDERS.SSH:
return ;
case ACCESS_PROVIDERS.TENCENTCLOUD:
@@ -131,6 +141,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.WEBHOOK:
return ;
+ case ACCESS_PROVIDERS.WESTCN:
+ return ;
}
}, [disabled, initialValues?.config, fieldProvider, nestedFormInst, nestedFormName]);
diff --git a/ui/src/components/access/AccessFormClouDNSConfig.tsx b/ui/src/components/access/AccessFormClouDNSConfig.tsx
new file mode 100644
index 00000000..4472eeea
--- /dev/null
+++ b/ui/src/components/access/AccessFormClouDNSConfig.tsx
@@ -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 { type AccessConfigForClouDNS } from "@/domain/access";
+
+type AccessFormClouDNSConfigFieldValues = Nullish;
+
+export type AccessFormClouDNSConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormClouDNSConfigFieldValues;
+ onValuesChange?: (values: AccessFormClouDNSConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormClouDNSConfigFieldValues => {
+ return {
+ authId: "",
+ authPassword: "",
+ };
+};
+
+const AccessFormClouDNSConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormClouDNSConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ authId: z
+ .string()
+ .trim()
+ .min(1, t("access.form.cloudns_auth_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ authPassword: z
+ .string()
+ .min(1, t("access.form.cloudns_auth_password.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormClouDNSConfig;
diff --git a/ui/src/components/access/AccessFormGnameConfig.tsx b/ui/src/components/access/AccessFormGnameConfig.tsx
new file mode 100644
index 00000000..f0c8f072
--- /dev/null
+++ b/ui/src/components/access/AccessFormGnameConfig.tsx
@@ -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 { type AccessConfigForGname } from "@/domain/access";
+
+type AccessFormGnameConfigFieldValues = Nullish;
+
+export type AccessFormGnameConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormGnameConfigFieldValues;
+ onValuesChange?: (values: AccessFormGnameConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormGnameConfigFieldValues => {
+ return {
+ appId: "",
+ appKey: "",
+ };
+};
+
+const AccessFormGnameConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormGnameConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ appId: z
+ .string()
+ .min(1, t("access.form.gname_app_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ appKey: z
+ .string()
+ .min(1, t("access.form.gname_app_key.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormGnameConfig;
diff --git a/ui/src/components/access/AccessFormRainYunConfig.tsx b/ui/src/components/access/AccessFormRainYunConfig.tsx
new file mode 100644
index 00000000..a34cf683
--- /dev/null
+++ b/ui/src/components/access/AccessFormRainYunConfig.tsx
@@ -0,0 +1,61 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForRainYun } from "@/domain/access";
+
+type AccessFormRainYunConfigFieldValues = Nullish;
+
+export type AccessFormRainYunConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormRainYunConfigFieldValues;
+ onValuesChange?: (values: AccessFormRainYunConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormRainYunConfigFieldValues => {
+ return {
+ apiKey: "",
+ };
+};
+
+const AccessFormRainYunConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormRainYunConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiKey: z
+ .string()
+ .min(1, t("access.form.rainyun_api_key.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormRainYunConfig;
diff --git a/ui/src/components/access/AccessFormWestcnConfig.tsx b/ui/src/components/access/AccessFormWestcnConfig.tsx
new file mode 100644
index 00000000..0b0257ed
--- /dev/null
+++ b/ui/src/components/access/AccessFormWestcnConfig.tsx
@@ -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 { type AccessConfigForWestcn } from "@/domain/access";
+
+type AccessFormWestcnConfigFieldValues = Nullish;
+
+export type AccessFormWestcnConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormWestcnConfigFieldValues;
+ onValuesChange?: (values: AccessFormWestcnConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormWestcnConfigFieldValues => {
+ return {
+ username: "",
+ apiPassword: "",
+ };
+};
+
+const AccessFormWestcnConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormWestcnConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ username: z
+ .string()
+ .trim()
+ .min(1, t("access.form.westcn_username.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ apiPassword: z
+ .string()
+ .min(1, t("access.form.westcn_api_password.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormWestcnConfig;
diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx
index 2fa9b5b3..d818ee4c 100644
--- a/ui/src/components/notification/NotifyChannelEditForm.tsx
+++ b/ui/src/components/notification/NotifyChannelEditForm.tsx
@@ -10,8 +10,8 @@ import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
-import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields";
import NotifyChannelEditFormWebhookFields from "./NotifyChannelEditFormWebhookFields";
+import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields";
type NotifyChannelEditFormFieldValues = NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent];
diff --git a/ui/src/components/workflow/WorkflowElement.tsx b/ui/src/components/workflow/WorkflowElement.tsx
index d2272ef0..3aa70ff3 100644
--- a/ui/src/components/workflow/WorkflowElement.tsx
+++ b/ui/src/components/workflow/WorkflowElement.tsx
@@ -64,4 +64,3 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem
};
export default memo(WorkflowElement);
-
diff --git a/ui/src/components/workflow/node/ApplyNode.tsx b/ui/src/components/workflow/node/ApplyNode.tsx
index 556188d9..6e090616 100644
--- a/ui/src/components/workflow/node/ApplyNode.tsx
+++ b/ui/src/components/workflow/node/ApplyNode.tsx
@@ -8,8 +8,8 @@ import { useZustandShallowSelector } from "@/hooks";
import { useContactEmailsStore } from "@/stores/contact";
import { useWorkflowStore } from "@/stores/workflow";
-import ApplyNodeConfigForm, { type ApplyNodeConfigFormInstance } from "./ApplyNodeConfigForm";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import ApplyNodeConfigForm, { type ApplyNodeConfigFormInstance } from "./ApplyNodeConfigForm";
export type ApplyNodeProps = SharedNodeProps;
diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx
index 2280fbb8..1792163e 100644
--- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx
@@ -21,10 +21,10 @@ import {
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
-import ModalForm from "@/components/ModalForm";
-import MultipleInput from "@/components/MultipleInput";
import AccessEditModal from "@/components/access/AccessEditModal";
import AccessSelect from "@/components/access/AccessSelect";
+import ModalForm from "@/components/ModalForm";
+import MultipleInput from "@/components/MultipleInput";
import ApplyDNSProviderSelect from "@/components/provider/ApplyDNSProviderSelect";
import { ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyDNSProvidersMap } from "@/domain/provider";
import { type WorkflowNodeConfigForApply } from "@/domain/workflow";
diff --git a/ui/src/components/workflow/node/ConditionNode.tsx b/ui/src/components/workflow/node/ConditionNode.tsx
index d84e8550..56639692 100644
--- a/ui/src/components/workflow/node/ConditionNode.tsx
+++ b/ui/src/components/workflow/node/ConditionNode.tsx
@@ -2,8 +2,8 @@ import { memo } from "react";
import { MoreOutlined as MoreOutlinedIcon } from "@ant-design/icons";
import { Button, Card, Popover } from "antd";
-import AddNode from "./AddNode";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import AddNode from "./AddNode";
export type ConditionNodeProps = SharedNodeProps & {
branchId: string;
diff --git a/ui/src/components/workflow/node/DeployNode.tsx b/ui/src/components/workflow/node/DeployNode.tsx
index db5830da..9eca1248 100644
--- a/ui/src/components/workflow/node/DeployNode.tsx
+++ b/ui/src/components/workflow/node/DeployNode.tsx
@@ -8,8 +8,8 @@ import { type WorkflowNodeConfigForDeploy, WorkflowNodeType } from "@/domain/wor
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
-import DeployNodeConfigForm, { type DeployNodeConfigFormInstance } from "./DeployNodeConfigForm";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import DeployNodeConfigForm, { type DeployNodeConfigFormInstance } from "./DeployNodeConfigForm";
export type DeployNodeProps = SharedNodeProps;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index 5e11fb2a..c9b70570 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -5,11 +5,11 @@ import { Alert, Button, Divider, Flex, Form, type FormInstance, Select, Switch,
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
-import Show from "@/components/Show";
import AccessEditModal from "@/components/access/AccessEditModal";
import AccessSelect from "@/components/access/AccessSelect";
import DeployProviderPicker from "@/components/provider/DeployProviderPicker";
import DeployProviderSelect from "@/components/provider/DeployProviderSelect";
+import Show from "@/components/Show";
import { ACCESS_USAGES, DEPLOY_PROVIDERS, accessProvidersMap, deployProvidersMap } from "@/domain/provider";
import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/workflow";
import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks";
@@ -22,6 +22,8 @@ import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDC
import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig";
import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig";
import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig";
+import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig";
+import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig";
import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig";
import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig";
import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig";
@@ -31,6 +33,7 @@ import DeployNodeConfigFormHuaweiCloudELBConfig from "./DeployNodeConfigFormHuaw
import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig";
import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
+import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig";
import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx";
import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx";
import DeployNodeConfigFormTencentCloudCLBConfig from "./DeployNodeConfigFormTencentCloudCLBConfig.tsx";
@@ -132,6 +135,10 @@ const DeployNodeConfigForm = forwardRef;
case DEPLOY_PROVIDERS.ALIYUN_OSS:
return ;
+ case DEPLOY_PROVIDERS.ALIYUN_WAF:
+ return ;
+ case DEPLOY_PROVIDERS.AWS_CLOUDFRONT:
+ return ;
case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN:
return ;
case DEPLOY_PROVIDERS.BYTEPLUS_CDN:
@@ -150,6 +157,8 @@ const DeployNodeConfigForm = forwardRef;
case DEPLOY_PROVIDERS.QINIU_CDN:
return ;
+ case DEPLOY_PROVIDERS.QINIU_PILI:
+ return ;
case DEPLOY_PROVIDERS.SSH:
return ;
case DEPLOY_PROVIDERS.TENCENTCLOUD_CDN:
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx
new file mode 100644
index 00000000..f1689ced
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx
@@ -0,0 +1,79 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+type DeployNodeConfigFormAWSCloudFrontConfigFieldValues = Nullish<{
+ region: string;
+ distributionId: string;
+}>;
+
+export type DeployNodeConfigFormAWSCloudFrontConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormAWSCloudFrontConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormAWSCloudFrontConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormAWSCloudFrontConfigFieldValues => {
+ return {};
+};
+
+const DeployNodeConfigFormAWSCloudFrontConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormAWSCloudFrontConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ region: z
+ .string({ message: t("workflow_node.deploy.form.aws_cloudfront_region.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.aws_cloudfront_region.placeholder"))
+ .trim(),
+ distributionId: z
+ .string({ message: t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormAWSCloudFrontConfig;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx
new file mode 100644
index 00000000..b7206d04
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx
@@ -0,0 +1,79 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+type DeployNodeConfigFormAliyunWAFConfigFieldValues = Nullish<{
+ region: string;
+ instanceId: string;
+}>;
+
+export type DeployNodeConfigFormAliyunWAFConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormAliyunWAFConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormAliyunWAFConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormAliyunWAFConfigFieldValues => {
+ return {};
+};
+
+const DeployNodeConfigFormAliyunWAFConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormAliyunWAFConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ region: z
+ .string({ message: t("workflow_node.deploy.form.aliyun_waf_region.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder"))
+ .trim(),
+ instanceId: z
+ .string({ message: t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormAliyunWAFConfig;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx
new file mode 100644
index 00000000..dd6f1570
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx
@@ -0,0 +1,79 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { validDomainName } from "@/utils/validators";
+
+type DeployNodeConfigFormQiniuPiliConfigFieldValues = Nullish<{
+ hub: string;
+ domain: string;
+}>;
+
+export type DeployNodeConfigFormQiniuPiliConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormQiniuPiliConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormQiniuPiliConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormQiniuPiliConfigFieldValues => {
+ return {};
+};
+
+const DeployNodeConfigFormQiniuPiliConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormQiniuPiliConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ hub: z
+ .string({ message: t("workflow_node.deploy.form.qiniu_pili_hub.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.qiniu_pili_hub.placeholder"))
+ .trim(),
+ domain: z
+ .string({ message: t("workflow_node.deploy.form.qiniu_pili_domain.placeholder") })
+ .refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormQiniuPiliConfig;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx
index 976ca079..1e176d7b 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
-import { Button, Dropdown, Form, type FormInstance, Input, Select } from "antd";
+import { Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@@ -17,6 +17,7 @@ type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{
jksStorepass?: string | null;
preCommand?: string | null;
postCommand?: string | null;
+ useSCP?: boolean;
}>;
export type DeployNodeConfigFormSSHConfigProps = {
@@ -89,6 +90,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
.string()
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish(),
+ useSCP: z.boolean().nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
@@ -261,6 +263,15 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
+
+ }
+ >
+
+
);
};
diff --git a/ui/src/components/workflow/node/ExecuteResultNode.tsx b/ui/src/components/workflow/node/ExecuteResultNode.tsx
index b53b2a8c..69a0949c 100644
--- a/ui/src/components/workflow/node/ExecuteResultNode.tsx
+++ b/ui/src/components/workflow/node/ExecuteResultNode.tsx
@@ -8,8 +8,8 @@ import {
import { Button, Card, Popover, theme } from "antd";
import { WorkflowNodeType } from "@/domain/workflow";
-import AddNode from "./AddNode";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import AddNode from "./AddNode";
export type ConditionNodeProps = SharedNodeProps & {
branchId: string;
diff --git a/ui/src/components/workflow/node/NotifyNode.tsx b/ui/src/components/workflow/node/NotifyNode.tsx
index 1b739e8f..4ac35ab5 100644
--- a/ui/src/components/workflow/node/NotifyNode.tsx
+++ b/ui/src/components/workflow/node/NotifyNode.tsx
@@ -8,8 +8,8 @@ import { type WorkflowNodeConfigForNotify, WorkflowNodeType } from "@/domain/wor
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
-import NotifyNodeConfigForm, { type NotifyNodeConfigFormInstance } from "./NotifyNodeConfigForm";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import NotifyNodeConfigForm, { type NotifyNodeConfigFormInstance } from "./NotifyNodeConfigForm";
export type NotifyNodeProps = SharedNodeProps;
diff --git a/ui/src/components/workflow/node/StartNode.tsx b/ui/src/components/workflow/node/StartNode.tsx
index 1df65bc1..900793fa 100644
--- a/ui/src/components/workflow/node/StartNode.tsx
+++ b/ui/src/components/workflow/node/StartNode.tsx
@@ -7,8 +7,8 @@ import { WORKFLOW_TRIGGERS, type WorkflowNodeConfigForStart, WorkflowNodeType }
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
-import StartNodeConfigForm, { type StartNodeConfigFormInstance } from "./StartNodeConfigForm";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import StartNodeConfigForm, { type StartNodeConfigFormInstance } from "./StartNodeConfigForm";
export type StartNodeProps = SharedNodeProps;
diff --git a/ui/src/components/workflow/node/UploadNode.tsx b/ui/src/components/workflow/node/UploadNode.tsx
index 3d0e20c3..25850743 100644
--- a/ui/src/components/workflow/node/UploadNode.tsx
+++ b/ui/src/components/workflow/node/UploadNode.tsx
@@ -8,8 +8,8 @@ import { WorkflowNodeType } from "@/domain/workflow";
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
-import UploadNodeConfigForm, { type UploadNodeConfigFormInstance } from "./UploadNodeConfigForm";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+import UploadNodeConfigForm, { type UploadNodeConfigFormInstance } from "./UploadNodeConfigForm";
export type UploadNodeProps = SharedNodeProps;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 0c0d6986..1e229751 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -15,8 +15,10 @@ export interface AccessModel extends BaseModel {
| AccessConfigForBaiduCloud
| AccessConfigForBytePlus
| AccessConfigForCloudflare
+ | AccessConfigForClouDNS
| AccessConfigForDogeCloud
| AccessConfigForEdgio
+ | AccessConfigForGname
| AccessConfigForGoDaddy
| AccessConfigForHuaweiCloud
| AccessConfigForKubernetes
@@ -25,11 +27,13 @@ export interface AccessModel extends BaseModel {
| AccessConfigForNameSilo
| AccessConfigForPowerDNS
| AccessConfigForQiniu
+ | AccessConfigForRainYun
| AccessConfigForSSH
| AccessConfigForTencentCloud
| AccessConfigForUCloud
| AccessConfigForVolcEngine
| AccessConfigForWebhook
+ | AccessConfigForWestcn
);
usage: AccessUsageType;
}
@@ -73,6 +77,11 @@ export type AccessConfigForCloudflare = {
dnsApiToken: string;
};
+export type AccessConfigForClouDNS = {
+ authId: string;
+ authPassword: string;
+};
+
export type AccessConfigForDogeCloud = {
accessKey: string;
secretKey: string;
@@ -83,6 +92,11 @@ export type AccessConfigForEdgio = {
clientSecret: string;
};
+export type AccessConfigForGname = {
+ appId: string;
+ appKey: string;
+};
+
export type AccessConfigForGoDaddy = {
apiKey: string;
apiSecret: string;
@@ -122,6 +136,10 @@ export type AccessConfigForQiniu = {
secretKey: string;
};
+export type AccessConfigForRainYun = {
+ apiKey: string;
+};
+
export type AccessConfigForSSH = {
host: string;
port: number;
@@ -150,4 +168,9 @@ export type AccessConfigForVolcEngine = {
export type AccessConfigForWebhook = {
url: string;
};
+
+export type AccessConfigForWestcn = {
+ username: string;
+ apiPassword: string;
+};
// #endregion
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 574c022a..652f4332 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -11,7 +11,9 @@ export const ACCESS_PROVIDERS = Object.freeze({
BAIDUCLOUD: "baiducloud",
BYTEPLUS: "byteplus",
CLOUDFLARE: "cloudflare",
+ CLOUDNS: "cloudns",
DOGECLOUD: "dogecloud",
+ GNAME: "gname",
GODADDY: "godaddy",
EDGIO: "edgio",
HUAWEICLOUD: "huaweicloud",
@@ -22,11 +24,13 @@ export const ACCESS_PROVIDERS = Object.freeze({
NS1: "ns1",
POWERDNS: "powerdns",
QINIU: "qiniu",
+ RAINYUN: "rainyun",
SSH: "ssh",
TENCENTCLOUD: "tencentcloud",
UCLOUD: "ucloud",
VOLCENGINE: "volcengine",
WEBHOOK: "webhook",
+ WESTCN: "westcn",
} as const);
export type AccessProviderType = (typeof ACCESS_PROVIDERS)[keyof typeof ACCESS_PROVIDERS];
@@ -59,20 +63,24 @@ export const accessProvidersMap: Map [
@@ -100,6 +108,8 @@ export const APPLY_DNS_PROVIDERS = Object.freeze({
AWS_ROUTE53: `${ACCESS_PROVIDERS.AWS}-route53`,
AZURE_DNS: `${ACCESS_PROVIDERS.AZURE}-dns`,
CLOUDFLARE: `${ACCESS_PROVIDERS.CLOUDFLARE}`,
+ CLOUDNS: `${ACCESS_PROVIDERS.CLOUDNS}`,
+ GNAME: `${ACCESS_PROVIDERS.GNAME}`,
GODADDY: `${ACCESS_PROVIDERS.GODADDY}`,
HUAWEICLOUD: `${ACCESS_PROVIDERS.HUAWEICLOUD}`, // 兼容旧值,等同于 `HUAWEICLOUD_DNS`
HUAWEICLOUD_DNS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-dns`,
@@ -107,10 +117,12 @@ export const APPLY_DNS_PROVIDERS = Object.freeze({
NAMESILO: `${ACCESS_PROVIDERS.NAMESILO}`,
NS1: `${ACCESS_PROVIDERS.NS1}`,
POWERDNS: `${ACCESS_PROVIDERS.POWERDNS}`,
+ RAINYUN: `${ACCESS_PROVIDERS.RAINYUN}`,
TENCENTCLOUD: `${ACCESS_PROVIDERS.TENCENTCLOUD}`, // 兼容旧值,等同于 `TENCENTCLOUD_DNS`
TENCENTCLOUD_DNS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-dns`,
VOLCENGINE: `${ACCESS_PROVIDERS.VOLCENGINE}`, // 兼容旧值,等同于 `VOLCENGINE_DNS`
VOLCENGINE_DNS: `${ACCESS_PROVIDERS.VOLCENGINE}-dns`,
+ WESTCN: `${ACCESS_PROVIDERS.WESTCN}`,
} as const);
export type ApplyDNSProviderType = (typeof APPLY_DNS_PROVIDERS)[keyof typeof APPLY_DNS_PROVIDERS];
@@ -135,10 +147,14 @@ export const applyDNSProvidersMap: Map [
@@ -166,6 +182,8 @@ export const DEPLOY_PROVIDERS = Object.freeze({
ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`,
ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`,
ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`,
+ ALIYUN_WAF: `${ACCESS_PROVIDERS.ALIYUN}-waf`,
+ AWS_CLOUDFRONT: `${ACCESS_PROVIDERS.AWS}-cloudfront`,
BAIDUCLOUD_CDN: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cdn`,
BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`,
DOGECLOUD_CDN: `${ACCESS_PROVIDERS.DOGECLOUD}-cdn`,
@@ -175,6 +193,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`,
LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
+ QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`,
SSH: `${ACCESS_PROVIDERS.SSH}`,
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
TENCENTCLOUD_CLB: `${ACCESS_PROVIDERS.TENCENTCLOUD}-clb`,
@@ -217,6 +236,7 @@ export const deployProvidersMap: Map [
type,
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index 47f59120..4f0266ca 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -72,6 +72,12 @@
"access.form.cloudflare_dns_api_token.label": "Cloudflare API token",
"access.form.cloudflare_dns_api_token.placeholder": "Please enter Cloudflare API token",
"access.form.cloudflare_dns_api_token.tooltip": "For more information, see https://developers.cloudflare.com/fundamentals/api/get-started/create-token/",
+ "access.form.cloudns_auth_id.label": "ClouDNS API user ID",
+ "access.form.cloudns_auth_id.placeholder": "Please enter ClouDNS API user ID",
+ "access.form.cloudns_auth_id.tooltip": "For more information, see https://www.cloudns.net/wiki/article/42/",
+ "access.form.cloudns_auth_password.label": "ClouDNS API user password",
+ "access.form.cloudns_auth_password.placeholder": "Please enter ClouDNS API user password",
+ "access.form.cloudns_auth_password.tooltip": "For more information, see https://www.cloudns.net/wiki/article/42/",
"access.form.dogecloud_access_key.label": "Doge Cloud AccessKey",
"access.form.dogecloud_access_key.placeholder": "Please enter Doge Cloud AccessKey",
"access.form.dogecloud_access_key.tooltip": "For more information, see https://console.dogecloud.com/",
@@ -84,6 +90,12 @@
"access.form.edgio_client_secret.label": "Edgio ClientSecret",
"access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret",
"access.form.edgio_client_secret.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
+ "access.form.gname_app_id.label": "GNAME AppId",
+ "access.form.gname_app_id.placeholder": "Please enter GNAME AppId",
+ "access.form.gname_app_id.tooltip": "For more information, see https://www.gname.com/user#/dealer_api",
+ "access.form.gname_app_key.label": "GNAME AppKey",
+ "access.form.gname_app_key.placeholder": "Please enter GNAME AppKey",
+ "access.form.gname_app_key.tooltip": "For more information, see https://www.gname.com/user#/dealer_api",
"access.form.godaddy_api_key.label": "GoDaddy API key",
"access.form.godaddy_api_key.placeholder": "Please enter GoDaddy API key",
"access.form.godaddy_api_key.tooltip": "For more information, see https://developer.godaddy.com/",
@@ -124,6 +136,25 @@
"access.form.qiniu_secret_key.label": "Qiniu SecretKey",
"access.form.qiniu_secret_key.placeholder": "Please enter Qiniu SecretKey",
"access.form.qiniu_secret_key.tooltip": "For more information, see https://portal.qiniu.com/",
+ "access.form.rainyun_api_key.label": "Rain Yun API key",
+ "access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key",
+ "access.form.rainyun_api_key.tooltip": "For more information, see https://www.rainyun.com/docs/account/racc/setting",
+ "access.form.ssh_host.label": "Server host",
+ "access.form.ssh_host.placeholder": "Please enter server host",
+ "access.form.ssh_port.label": "Server port",
+ "access.form.ssh_port.placeholder": "Please enter server port",
+ "access.form.ssh_username.label": "Username",
+ "access.form.ssh_username.placeholder": "Please enter username",
+ "access.form.ssh_password.label": "Password",
+ "access.form.ssh_password.placeholder": "Please enter password",
+ "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.",
+ "access.form.ssh_key.label": "SSH key",
+ "access.form.ssh_key.placeholder": "Please enter SSH key",
+ "access.form.ssh_key.upload": "Choose file ...",
+ "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.",
+ "access.form.ssh_key_passphrase.label": "SSH key passphrase",
+ "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase",
+ "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.",
"access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId",
"access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId",
"access.form.tencentcloud_secret_id.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en",
@@ -147,20 +178,10 @@
"access.form.volcengine_secret_access_key.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571",
"access.form.webhook_url.label": "Webhook URL",
"access.form.webhook_url.placeholder": "Please enter Webhook URL",
- "access.form.ssh_host.label": "Server host",
- "access.form.ssh_host.placeholder": "Please enter server host",
- "access.form.ssh_port.label": "Server port",
- "access.form.ssh_port.placeholder": "Please enter server port",
- "access.form.ssh_username.label": "Username",
- "access.form.ssh_username.placeholder": "Please enter username",
- "access.form.ssh_password.label": "Password",
- "access.form.ssh_password.placeholder": "Please enter password",
- "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.",
- "access.form.ssh_key.label": "SSH key",
- "access.form.ssh_key.placeholder": "Please enter SSH key",
- "access.form.ssh_key.upload": "Choose file ...",
- "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.",
- "access.form.ssh_key_passphrase.label": "SSH key passphrase",
- "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase",
- "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH."
+ "access.form.westcn_username.label": "West.cn username",
+ "access.form.westcn_username.placeholder": "Please enter West.cn username",
+ "access.form.westcn_username.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html",
+ "access.form.westcn_api_password.label": "West.cn API password",
+ "access.form.westcn_api_password.placeholder": "Please enter West.cn API password",
+ "access.form.westcn_api_password.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html"
}
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index 53eeb515..cb5c2d41 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -37,32 +37,36 @@
"common.provider.acmehttpreq": "Http Request (ACME Proxy)",
"common.provider.aliyun": "Alibaba Cloud",
- "common.provider.aliyun.alb": "Alibaba Cloud - Application Load Balancer (ALB)",
- "common.provider.aliyun.cdn": "Alibaba Cloud - Content Delivery Network (CDN)",
- "common.provider.aliyun.clb": "Alibaba Cloud - Classic Load Balancer (CLB)",
- "common.provider.aliyun.dcdn": "Alibaba Cloud - Dynamic Route for Content Delivery Network (DCDN)",
- "common.provider.aliyun.dns": "Alibaba Cloud - Domain Name Service (DNS)",
+ "common.provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)",
+ "common.provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)",
+ "common.provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)",
+ "common.provider.aliyun.dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)",
+ "common.provider.aliyun.dns": "Alibaba Cloud - DNS (Domain Name Service)",
"common.provider.aliyun.live": "Alibaba Cloud - ApsaraVideo Live",
- "common.provider.aliyun.nlb": "Alibaba Cloud - Network Load Balancer (NLB)",
- "common.provider.aliyun.oss": "Alibaba Cloud - Object Storage Service (OSS)",
+ "common.provider.aliyun.nlb": "Alibaba Cloud - NLB (Network Load Balancer)",
+ "common.provider.aliyun.oss": "Alibaba Cloud - OSS (Object Storage Service)",
+ "common.provider.aliyun.waf": "Alibaba Cloud - WAF (Web Application Firewall)",
"common.provider.aws": "AWS",
+ "common.provider.aws.cloudfront": "AWS - CloudFront",
"common.provider.aws.route53": "AWS - Route53",
"common.provider.azure": "Azure",
"common.provider.azure.dns": "Azure - DNS",
"common.provider.baiducloud": "Baidu Cloud",
- "common.provider.baiducloud.cdn": "Baidu Cloud - Content Delivery Network (CDN)",
+ "common.provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)",
"common.provider.byteplus": "BytePlus",
- "common.provider.byteplus.cdn": "BytePlus - Content Delivery Network (CDN)",
+ "common.provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)",
"common.provider.cloudflare": "Cloudflare",
+ "common.provider.cloudns": "ClouDNS",
"common.provider.dogecloud": "Doge Cloud",
- "common.provider.dogecloud.cdn": "Doge Cloud - Content Delivery Network (CDN)",
+ "common.provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)",
"common.provider.edgio": "Edgio",
"common.provider.edgio.applications": "Edgio - Applications",
+ "common.provider.gname": "GNAME",
"common.provider.godaddy": "GoDaddy",
"common.provider.huaweicloud": "Huawei Cloud",
- "common.provider.huaweicloud.cdn": "Huawei Cloud - Content Delivery Network (CDN)",
- "common.provider.huaweicloud.dns": "Huawei Cloud - Domain Name Service (DNS)",
- "common.provider.huaweicloud.elb": "Huawei Cloud - Elastic Load Balance (ELB)",
+ "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
+ "common.provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
+ "common.provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)",
"common.provider.kubernetes": "Kubernetes",
"common.provider.kubernetes.secret": "Kubernetes - Secret",
"common.provider.local": "Local deployment",
@@ -71,27 +75,30 @@
"common.provider.ns1": "NS1 (IBM NS1 Connect)",
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "Qiniu",
- "common.provider.qiniu.cdn": "Qiniu - Content Delivery Network (CDN)",
+ "common.provider.qiniu.cdn": "Qiniu - CDN (Content Delivery Network)",
+ "common.provider.qiniu.pili": "Qiniu - Pili",
+ "common.provider.rainyun": "Rain Yun",
"common.provider.ssh": "SSH deployment",
"common.provider.tencentcloud": "Tencent Cloud",
- "common.provider.tencentcloud.cdn": "Tencent Cloud - Content Delivery Network (CDN)",
- "common.provider.tencentcloud.clb": "Tencent Cloud - Cloud Load Balancer (CLB)",
- "common.provider.tencentcloud.cos": "Tencent Cloud - Cloud Object Storage (COS)",
- "common.provider.tencentcloud.css": "Tencent Cloud - Cloud Streaming Service (CSS)",
- "common.provider.tencentcloud.dns": "Tencent Cloud - Domain Name Service (DNS)",
- "common.provider.tencentcloud.ecdn": "Tencent Cloud - Enterprise Content Delivery Network (ECDN)",
+ "common.provider.tencentcloud.cdn": "Tencent Cloud - CDN (Content Delivery Network)",
+ "common.provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)",
+ "common.provider.tencentcloud.cos": "Tencent Cloud - COS (Cloud Object Storage)",
+ "common.provider.tencentcloud.css": "Tencent Cloud - CSS (Cloud Streaming Service)",
+ "common.provider.tencentcloud.dns": "Tencent Cloud - DNS (Domain Name Service)",
+ "common.provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)",
"common.provider.tencentcloud.eo": "Tencent Cloud - EdgeOne",
"common.provider.ucloud": "UCloud",
- "common.provider.ucloud.ucdn": "UCloud - UCloud Content Delivery Network (UCDN)",
- "common.provider.ucloud.us3": "UCloud - UCloud Object-based Storage (US3)",
+ "common.provider.ucloud.ucdn": "UCloud - UCDN (UCloud Content Delivery Network)",
+ "common.provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)",
"common.provider.volcengine": "Volcengine",
- "common.provider.volcengine.cdn": "Volcengine - Content Delivery Network (CDN)",
- "common.provider.volcengine.clb": "Volcengine - Cloud Load Balancer (CLB)",
- "common.provider.volcengine.dcdn": "Volcengine - Dynamic Content Delivery Network (DCDN)",
- "common.provider.volcengine.dns": "Volcengine - Domain Name Service (DNS)",
+ "common.provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)",
+ "common.provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)",
+ "common.provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)",
+ "common.provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)",
"common.provider.volcengine.live": "Volcengine - Live",
- "common.provider.volcengine.tos": "Volcengine - Tinder Object Storage (TOS)",
+ "common.provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
"common.provider.webhook": "Webhook",
+ "common.provider.westcn": "West.cn",
"common.notifier.bark": "Bark",
"common.notifier.dingtalk": "DingTalk",
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index edd26b74..964633e0 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -37,8 +37,8 @@
"workflow_node.apply.form.provider_access.placeholder": "Please select an authorization of DNS provider",
"workflow_node.apply.form.provider_access.tooltip": "Used to manage DNS records during ACME DNS-01 authentication.",
"workflow_node.apply.form.provider_access.button": "Create",
- "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 Region",
- "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS Route53 region (e.g. us-east-1)",
+ "workflow_node.apply.form.aws_route53_region.label": "AWS Region",
+ "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS region (e.g. us-east-1)",
"workflow_node.apply.form.aws_route53_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints",
"workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 hosted zone ID",
"workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "Please enter AWS Route53 hosted zone ID",
@@ -153,6 +153,18 @@
"workflow_node.deploy.form.aliyun_oss_domain.label": "Alibaba Cloud OSS domain",
"workflow_node.deploy.form.aliyun_oss_domain.placeholder": "Please enter Alibaba Cloud OSS domain name",
"workflow_node.deploy.form.aliyun_oss_domain.tooltip": "For more information, see https://oss.console.aliyun.com",
+ "workflow_node.deploy.form.aliyun_waf_region.label": "Alibaba Cloud region",
+ "workflow_node.deploy.form.aliyun_waf_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)",
+ "workflow_node.deploy.form.aliyun_waf_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com",
+ "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS Region",
+ "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS region (e.g. us-east-1)",
+ "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront distribution ID",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "Please enter AWS CloudFront distribution ID",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html",
"workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain",
"workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name",
"workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see https://console.bce.baidu.com/cdn",
@@ -241,7 +253,13 @@
"workflow_node.deploy.form.local_preset_scripts.option.binding_netsh.label": "PowerShell - Binding netsh",
"workflow_node.deploy.form.qiniu_cdn_domain.label": "Qiniu CDN domain",
"workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "Please enter Qiniu CDN domain name",
- "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "For more information, see https://portal.qiniu.com/",
+ "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "For more information, see https://portal.qiniu.com/cdn",
+ "workflow_node.deploy.form.qiniu_pili_hub.label": "Qiniu Pili hub",
+ "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "Please enter Qiniu Pili hub name",
+ "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "For more information, see https://portal.qiniu.com/hub",
+ "workflow_node.deploy.form.qiniu_pili_domain.label": "Qiniu Pili streaming domain",
+ "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "Please enter Qiniu Pili streaming domain name",
+ "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "For more information, see https://portal.qiniu.com/hub",
"workflow_node.deploy.form.ssh_format.label": "File format",
"workflow_node.deploy.form.ssh_format.placeholder": "Please select file format",
"workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)",
@@ -273,6 +291,8 @@
"workflow_node.deploy.form.ssh_post_command.placeholder": "Please enter command to be executed after uploading files",
"workflow_node.deploy.form.ssh_preset_scripts.button": "Use preset scripts",
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - Reload nginx",
+ "workflow_node.deploy.form.ssh_use_scp.label": "Fallback to use SCP",
+ "workflow_node.deploy.form.ssh_use_scp.tooltip": "If the remote server does not support SFTP, please enable this option to fallback to SCP.",
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "Tencent Cloud CDN domain",
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "Please enter Tencent Cloud CDN domain name",
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "For more information, see https://console.tencentcloud.com/cdn",
@@ -345,8 +365,8 @@
"workflow_node.deploy.form.volcengine_dcdn_domain.label": "VolcEngine DCDN domain",
"workflow_node.deploy.form.volcengine_dcdn_domain.placeholder": "Please enter VolcEngine DCDN domain name",
"workflow_node.deploy.form.volcengine_dcdn_domain.tooltip": "For more information, see https://console.volcengine.com/dcdn/dashboard",
- "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine live streaming domain",
- "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine live streaming domain name",
+ "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine Live streaming domain",
+ "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine Live streaming domain name",
"workflow_node.deploy.form.volcengine_live_domain.tooltip": "For more information, see https://console.volcengine.com/live",
"workflow_node.deploy.form.volcengine_tos_region.label": "VolcEngine region",
"workflow_node.deploy.form.volcengine_tos_region.placeholder": "Please enter VolcEngine region (e.g. cn-beijing)",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index 35a10eda..d0c9037d 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -72,6 +72,12 @@
"access.form.cloudflare_dns_api_token.label": "Cloudflare API Token",
"access.form.cloudflare_dns_api_token.placeholder": "请输入 Cloudflare API Token",
"access.form.cloudflare_dns_api_token.tooltip": "这是什么?请参阅 https://developers.cloudflare.com/fundamentals/api/get-started/create-token/",
+ "access.form.cloudns_auth_id.label": "ClouDNS API 用户 ID",
+ "access.form.cloudns_auth_id.placeholder": "请输入 ClouDNS API 用户 ID",
+ "access.form.cloudns_auth_id.tooltip": "这是什么?请参阅 https://www.cloudns.net/wiki/article/42/",
+ "access.form.cloudns_auth_password.label": "ClouDNS API 用户密码",
+ "access.form.cloudns_auth_password.placeholder": "请输入 ClouDNS API 用户密码",
+ "access.form.cloudns_auth_password.tooltip": "这是什么?请参阅 https://www.cloudns.net/wiki/article/42/",
"access.form.dogecloud_access_key.label": "多吉云 AccessKey",
"access.form.dogecloud_access_key.placeholder": "请输入多吉云 AccessKey",
"access.form.dogecloud_access_key.tooltip": "这是什么?请参阅 https://console.dogecloud.com/",
@@ -84,6 +90,12 @@
"access.form.edgio_client_secret.label": "Edgio 客户端密码",
"access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码",
"access.form.edgio_client_secret.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
+ "access.form.gname_app_id.label": "GNAME AppId",
+ "access.form.gname_app_id.placeholder": "请输入 GNAME AppId",
+ "access.form.gname_app_id.tooltip": "这是什么?请参阅 https://www.gname.com/user#/dealer_api",
+ "access.form.gname_app_key.label": "GNAME AppKey",
+ "access.form.gname_app_key.placeholder": "请输入 GNAME AppKey",
+ "access.form.gname_app_key.tooltip": "这是什么?请参阅 https://www.gname.com/user#/dealer_api",
"access.form.godaddy_api_key.label": "GoDaddy API Key",
"access.form.godaddy_api_key.placeholder": "请输入 GoDaddy API Key",
"access.form.godaddy_api_key.tooltip": "这是什么?请参阅 https://developer.godaddy.com/",
@@ -124,6 +136,25 @@
"access.form.qiniu_secret_key.label": "七牛云 SecretKey",
"access.form.qiniu_secret_key.placeholder": "请输入七牛云 SecretKey",
"access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 https://portal.qiniu.com/",
+ "access.form.rainyun_api_key.label": "雨云 API 密钥",
+ "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥",
+ "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://www.rainyun.com/docs/account/racc/setting",
+ "access.form.ssh_host.label": "服务器地址",
+ "access.form.ssh_host.placeholder": "请输入服务器地址",
+ "access.form.ssh_port.label": "服务器端口",
+ "access.form.ssh_port.placeholder": "请输入服务器端口",
+ "access.form.ssh_username.label": "用户名",
+ "access.form.ssh_username.placeholder": "请输入用户名",
+ "access.form.ssh_password.label": "密码",
+ "access.form.ssh_password.placeholder": "请输入密码",
+ "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一。",
+ "access.form.ssh_key.label": "SSH 密钥",
+ "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件",
+ "access.form.ssh_key.upload": "选择文件",
+ "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一。",
+ "access.form.ssh_key_passphrase.label": "SSH 密钥口令",
+ "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
+ "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。",
"access.form.tencentcloud_secret_id.label": "腾讯云 SecretId",
"access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId",
"access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488",
@@ -147,20 +178,10 @@
"access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571",
"access.form.webhook_url.label": "Webhook 回调地址",
"access.form.webhook_url.placeholder": "请输入 Webhook 回调地址",
- "access.form.ssh_host.label": "服务器地址",
- "access.form.ssh_host.placeholder": "请输入服务器地址",
- "access.form.ssh_port.label": "服务器端口",
- "access.form.ssh_port.placeholder": "请输入服务器端口",
- "access.form.ssh_username.label": "用户名",
- "access.form.ssh_username.placeholder": "请输入用户名",
- "access.form.ssh_password.label": "密码",
- "access.form.ssh_password.placeholder": "请输入密码",
- "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一。",
- "access.form.ssh_key.label": "SSH 密钥",
- "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件",
- "access.form.ssh_key.upload": "选择文件",
- "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一。",
- "access.form.ssh_key_passphrase.label": "SSH 密钥口令",
- "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
- "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。"
+ "access.form.westcn_username.label": "西部数码用户名",
+ "access.form.westcn_username.placeholder": "请输入西部数码用户名",
+ "access.form.westcn_username.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html",
+ "access.form.westcn_api_password.label": "西部数码 API 密码",
+ "access.form.westcn_api_password.placeholder": "请输入西部数码 API 密码",
+ "access.form.westcn_api_password.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html"
}
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index 87fce337..b8b97842 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -45,7 +45,9 @@
"common.provider.aliyun.live": "阿里云 - 视频直播 Live",
"common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB",
"common.provider.aliyun.oss": "阿里云 - 对象存储 OSS",
+ "common.provider.aliyun.waf": "阿里云 - Web 应用防火墙 WAF",
"common.provider.aws": "AWS",
+ "common.provider.aws.cloudfront": "AWS - CloudFront",
"common.provider.aws.route53": "AWS - Route53",
"common.provider.azure": "Azure",
"common.provider.azure.dns": "Azure - DNS",
@@ -54,10 +56,12 @@
"common.provider.byteplus": "BytePlus",
"common.provider.byteplus.cdn": "BytePlus - 内容分发网络 CDN",
"common.provider.cloudflare": "Cloudflare",
+ "common.provider.cloudns": "ClouDNS",
"common.provider.dogecloud": "多吉云",
"common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
"common.provider.edgio": "Edgio",
"common.provider.edgio.applications": "Edgio - Applications",
+ "common.provider.gname": "GNAME",
"common.provider.godaddy": "GoDaddy",
"common.provider.huaweicloud": "华为云",
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
@@ -72,6 +76,8 @@
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "七牛云",
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
+ "common.provider.qiniu.pili": "七牛云 - 视频直播 Pili",
+ "common.provider.rainyun": "雨云",
"common.provider.ssh": "SSH 部署",
"common.provider.tencentcloud": "腾讯云",
"common.provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN",
@@ -92,6 +98,7 @@
"common.provider.volcengine.live": "火山引擎 - 视频直播 Live",
"common.provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
"common.provider.webhook": "Webhook",
+ "common.provider.westcn": "西部数码",
"common.notifier.bark": "Bark",
"common.notifier.dingtalk": "钉钉",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 4eddbd92..d132f935 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -38,8 +38,8 @@
"workflow_node.apply.form.provider_access.tooltip": "用于 ACME DNS-01 认证时操作域名解析记录,注意与部署阶段所需的主机提供商相区分。",
"workflow_node.apply.form.provider_access.button": "新建",
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。",
- "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 区域",
- "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS Route53 区域(例如:us-east-1)",
+ "workflow_node.apply.form.aws_route53_region.label": "AWS 区域",
+ "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS 区域(例如:us-east-1)",
"workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints",
"workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 托管区域 ID",
"workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "请输入 AWS Route53 托管区域 ID",
@@ -153,6 +153,18 @@
"workflow_node.deploy.form.aliyun_oss_domain.label": "阿里云 OSS 自定义域名",
"workflow_node.deploy.form.aliyun_oss_domain.placeholder": "请输入阿里云 OSS 自定义域名",
"workflow_node.deploy.form.aliyun_oss_domain.tooltip": "这是什么?请参阅 see https://oss.console.aliyun.com",
+ "workflow_node.deploy.form.aliyun_waf_region.label": "阿里云地域",
+ "workflow_node.deploy.form.aliyun_waf_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)",
+ "workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID",
+ "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com",
+ "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS 区域",
+ "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS 区域(例如:us-east-1)",
+ "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID",
+ "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html",
"workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名",
"workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn
泛域名表示形式为:*.example.com",
@@ -241,7 +253,13 @@
"workflow_node.deploy.form.local_preset_scripts.option.binding_netsh.label": "PowerShell - 导入并绑定到 netsh(需管理员权限)",
"workflow_node.deploy.form.qiniu_cdn_domain.label": "七牛云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "请输入七牛云 CDN 加速域名",
- "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/
泛域名表示形式为:*.example.com",
+ "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/cdn
泛域名表示形式为:*.example.com",
+ "workflow_node.deploy.form.qiniu_pili_hub.label": "七牛云视频直播空间名",
+ "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "请输入七牛云视频直播空间名",
+ "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "这是什么?请参阅 https://portal.qiniu.com/hub",
+ "workflow_node.deploy.form.qiniu_pili_domain.label": "七牛云视频直播流域名",
+ "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "请输入七牛云视频直播流域名",
+ "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/hub",
"workflow_node.deploy.form.ssh_format.label": "文件格式",
"workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式",
"workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)",
@@ -273,6 +291,8 @@
"workflow_node.deploy.form.ssh_post_command.placeholder": "请输入保存文件后执行的命令",
"workflow_node.deploy.form.ssh_preset_scripts.button": "使用预设脚本",
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - 重启 nginx 进程",
+ "workflow_node.deploy.form.ssh_use_scp.label": "回退使用 SCP",
+ "workflow_node.deploy.form.ssh_use_scp.tooltip": "如果你的远程服务器不支持 SFTP,请开启此选项回退为 SCP。",
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名",
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn
泛域名表示形式为:*.example.com",
diff --git a/ui/src/router.tsx b/ui/src/router.tsx
index 31265f6b..0bfa8b41 100644
--- a/ui/src/router.tsx
+++ b/ui/src/router.tsx
@@ -1,9 +1,9 @@
import { createHashRouter } from "react-router-dom";
-import AuthLayout from "./pages/AuthLayout";
-import ConsoleLayout from "./pages/ConsoleLayout";
import AccessList from "./pages/accesses/AccessList";
+import AuthLayout from "./pages/AuthLayout";
import CertificateList from "./pages/certificates/CertificateList";
+import ConsoleLayout from "./pages/ConsoleLayout";
import Dashboard from "./pages/dashboard/Dashboard";
import Login from "./pages/login/Login";
import Settings from "./pages/settings/Settings";