diff --git a/.goreleaser.yml b/.goreleaser.yml index 8b4b64fe..9315c6a7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -30,6 +30,9 @@ builds: - goos: darwin goarch: arm +upx: + enable: true + release: draft: true diff --git a/Dockerfile b/Dockerfile index e1d01abd..997cda44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,8 @@ RUN apk add --no-cache tzdata COPY ../. /app/ RUN rm -rf /app/ui/dist COPY --from=webui-builder /app/ui/dist /app/ui/dist -RUN go build -o certimate +ENV CGO_ENABLED=0 +RUN go build -ldflags="-s -w" -o certimate diff --git a/Makefile b/Makefile index 7bf8cabd..eb05c306 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,8 @@ $(OS_ARCH): @mkdir -p $(BUILD_DIR) GOOS=$(word 1,$(subst /, ,$@)) \ GOARCH=$(word 2,$(subst /, ,$@)) \ - go build -o $(BUILD_DIR)/$(BINARY_NAME)_$(word 1,$(subst /, ,$@))_$(word 2,$(subst /, ,$@)) -ldflags="-X main.version=$(VERSION)" . + CGO_ENABLED=0 \ + go build -o $(BUILD_DIR)/$(BINARY_NAME)_$(word 1,$(subst /, ,$@))_$(word 2,$(subst /, ,$@)) -ldflags="-X main.version=$(VERSION) -s -w" . # 清理构建文件 clean: diff --git a/README.md b/README.md index 82f81275..988089d9 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决 - 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法; - 支持 PEM、PFX、JKS 等多种格式输出证书; - 支持 30+ 域名托管商(如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)); -- 支持 80+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers)); +- 支持 80+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-hosting-providers)); - 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道; - 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构; - 更多特性等待探索。 diff --git a/README_EN.md b/README_EN.md index 472efd66..f94020a2 100644 --- a/README_EN.md +++ b/README_EN.md @@ -39,7 +39,7 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate - Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC. - Supports various certificate formats such as PEM, PFX, JKS. - Supports more than 30+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers)); -- Supports more than 80+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers)); +- Supports more than 80+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-hosting-providers)); - Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more; - Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust Services,SSL.com, ZeroSSL, and more; - More features waiting to be discovered. diff --git a/go.mod b/go.mod index 7adb56bf..37686d8c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/alibabacloud-go/esa-20240910/v2 v2.32.0 github.com/alibabacloud-go/fc-20230330/v4 v4.3.5 github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 + github.com/alibabacloud-go/ga-20191120/v3 v3.1.8 github.com/alibabacloud-go/live-20161101 v1.1.1 github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 github.com/alibabacloud-go/slb-20140515/v4 v4.0.10 @@ -30,8 +31,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/acm v1.32.0 github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1 github.com/baidubce/bce-sdk-go v0.9.226 + github.com/blinkbean/dingtalk v1.1.3 github.com/byteplus-sdk/byteplus-sdk-golang v1.0.46 github.com/go-acme/lego/v4 v4.23.1 + github.com/go-lark/lark v1.16.0 github.com/go-resty/resty/v2 v2.16.5 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.148 @@ -39,7 +42,6 @@ require ( github.com/libdns/dynv6 v1.0.0 github.com/libdns/libdns v0.2.3 github.com/luthermonson/go-proxmox v0.2.2 - github.com/nikoksr/notify v1.3.0 github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 github.com/pkg/sftp v1.13.9 github.com/pocketbase/dbx v1.11.0 @@ -84,13 +86,11 @@ require ( github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // indirect - github.com/blinkbean/dingtalk v1.1.3 // indirect github.com/buger/goterm v1.0.4 // indirect github.com/diskfs/go-diskfs v1.5.0 // indirect github.com/djherbis/times v1.6.0 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-lark/lark v1.15.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -99,7 +99,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect @@ -126,7 +125,6 @@ require ( github.com/qiniu/x v1.10.5 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect - github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect @@ -195,12 +193,9 @@ require ( github.com/nrdcg/namesilo v0.2.1 // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/cast v1.8.0 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.10.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect golang.org/x/image v0.27.0 // indirect diff --git a/go.sum b/go.sum index ae7a9d22..5a64f77a 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,7 @@ github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+M github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 h1:ASXSBga98QrGMxbIThCD6jAti09gedLfvry6yJtsoBE= @@ -137,6 +138,8 @@ github.com/alibabacloud-go/fc-20230330/v4 v4.3.5 h1:nDNjVzGwkQPbQnAuxAmxvS9x8QGL github.com/alibabacloud-go/fc-20230330/v4 v4.3.5/go.mod h1:vEJimQ6E/e+m2z0/oXdeQWlFw/Pi/Ar6NKcMrSvcILE= github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 h1:A3D8Mp6qf8DfR6Dt5MpS8aDVaWfS4N85T5CvGUvgrjM= github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12/go.mod h1:F5c0E5UB3k8v6neTtw3FBcJ1YCNFzVoL1JPRHTe33u4= +github.com/alibabacloud-go/ga-20191120/v3 v3.1.8 h1:5GF0PXijDhxRQ3gTg9Ee/CVPtglkxuVdz4yIQgYLPgw= +github.com/alibabacloud-go/ga-20191120/v3 v3.1.8/go.mod h1:RVpR9VL4YECKoZCQijTYfPk8k52O61v6hSRekjxF0kw= github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8= github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4= github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko= @@ -360,8 +363,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-lark/lark v1.15.1 h1:fo6PQKBJht/71N9Zn3/xjknOYx0TmdVuP+VP8NrUCsI= -github.com/go-lark/lark v1.15.1/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM= +github.com/go-lark/lark v1.16.0 h1:U6BwkLM9wrZedSM7cIiMofganr8PCvJN+M75w2lf2Gg= +github.com/go-lark/lark v1.16.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -400,8 +403,6 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= @@ -569,8 +570,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= -github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -679,8 +678,6 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs= -github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM= github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI= github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= @@ -830,8 +827,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1155 h1:ildxJtjnqiKZxWDVKHT/ncIknGDijtg60MuFELON8bY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1155/go.mod h1:iLASpooTdyXtx642E5Ws7cfWENsp4/uZ/78TFoln7OI= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1161 h1:yGFg9/6j3NP10r9PfSWHfekuq4SwPyqblWnfISfKANo= diff --git a/internal/applicant/acme_ca.go b/internal/applicant/acme_ca.go index 67b7693e..36c0a0a4 100644 --- a/internal/applicant/acme_ca.go +++ b/internal/applicant/acme_ca.go @@ -3,25 +3,26 @@ package applicant import "github.com/usual2970/certimate/internal/domain" const ( - sslProviderLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt) - sslProviderLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging) - sslProviderBuypass = string(domain.CAProviderTypeBuypass) - sslProviderGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices) - sslProviderSSLCom = string(domain.CAProviderTypeSSLCom) - sslProviderZeroSSL = string(domain.CAProviderTypeZeroSSL) + caLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt) + caLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging) + caBuypass = string(domain.CAProviderTypeBuypass) + caGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices) + caSSLCom = string(domain.CAProviderTypeSSLCom) + caZeroSSL = string(domain.CAProviderTypeZeroSSL) + caCustom = string(domain.CAProviderTypeACMECA) - sslProviderDefault = sslProviderLetsEncrypt + caDefault = caLetsEncrypt ) -var sslProviderUrls = map[string]string{ - sslProviderLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory", - sslProviderLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory", - sslProviderBuypass: "https://api.buypass.com/acme/directory", - sslProviderGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory", - sslProviderSSLCom: "https://acme.ssl.com/sslcom-dv-rsa", - sslProviderSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa", - sslProviderSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc", - sslProviderZeroSSL: "https://acme.zerossl.com/v2/DV90", +var caDirUrls = map[string]string{ + caLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory", + caLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory", + caBuypass: "https://api.buypass.com/acme/directory", + caGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory", + caSSLCom: "https://acme.ssl.com/sslcom-dv-rsa", + caSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa", + caSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc", + caZeroSSL: "https://acme.zerossl.com/v2/DV90", } type acmeSSLProviderConfig struct { diff --git a/internal/applicant/acme_user.go b/internal/applicant/acme_user.go index 29ac80cd..e6e13cb7 100644 --- a/internal/applicant/acme_user.go +++ b/internal/applicant/acme_user.go @@ -7,6 +7,7 @@ import ( "crypto/elliptic" "crypto/rand" "fmt" + "strings" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/registration" @@ -19,22 +20,31 @@ import ( ) type acmeUser struct { - CA string - Email string + // 证书颁发机构标识。 + // 通常等同于 [CAProviderType] 的值。 + // 对于自定义 ACME CA,值为 "custom#{access_id}"。 + CA string + // 邮箱。 + Email string + // 注册信息。 Registration *registration.Resource + // CSR 私钥。 privkey string } -func newAcmeUser(ca, email string) (*acmeUser, error) { +func newAcmeUser(ca, caAccessId, email string) (*acmeUser, error) { repo := repository.NewAcmeAccountRepository() applyUser := &acmeUser{ CA: ca, Email: email, } + if ca == caCustom { + applyUser.CA = fmt.Sprintf("%s#%s", ca, caAccessId) + } - acmeAccount, err := repo.GetByCAAndEmail(ca, email) + acmeAccount, err := repo.GetByCAAndEmail(applyUser.CA, applyUser.Email) if err != nil { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { @@ -73,6 +83,10 @@ func (u *acmeUser) hasRegistration() bool { return u.Registration != nil } +func (u *acmeUser) getCAProvider() string { + return strings.Split(u.CA, "#")[0] +} + func (u *acmeUser) getPrivateKeyPEM() string { return u.privkey } @@ -94,16 +108,16 @@ func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userR func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) { var reg *registration.Resource var err error - switch user.CA { - case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging: + switch user.getCAProvider() { + case caLetsEncrypt, caLetsEncryptStaging: reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - case sslProviderBuypass: + case caBuypass: { reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) } - case sslProviderGoogleTrustServices: + case caGoogleTrustServices: { access := domain.AccessConfigForGoogleTrustServices{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -117,7 +131,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } - case sslProviderSSLCom: + case caSSLCom: { access := domain.AccessConfigForSSLCom{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -131,7 +145,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } - case sslProviderZeroSSL: + case caZeroSSL: { access := domain.AccessConfigForZeroSSL{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -145,6 +159,26 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } + case caCustom: + { + access := domain.AccessConfigForACMECA{} + if err := maputil.Populate(userRegisterOptions, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + if access.EabKid == "" && access.EabHmacKey == "" { + reg, err = client.Registration.Register(registration.RegisterOptions{ + TermsOfServiceAgreed: true, + }) + } else { + reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ + TermsOfServiceAgreed: true, + Kid: access.EabKid, + HmacEncoded: access.EabHmacKey, + }) + } + } + default: err = fmt.Errorf("unsupported ca provider '%s'", user.CA) } diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index e9ed4cb1..e6a04bcd 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -20,12 +20,13 @@ import ( "golang.org/x/time/rate" "github.com/usual2970/certimate/internal/domain" + maputil "github.com/usual2970/certimate/internal/pkg/utils/map" sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" "github.com/usual2970/certimate/internal/repository" ) type ApplyResult struct { - CertificateFullChain string + FullChainCertificate string IssuerCertificate string PrivateKey string ACMEAccountUrl string @@ -53,20 +54,20 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err nodeConfig := config.Node.GetConfigForApply() options := &applicantProviderOptions{ - Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), - ContactEmail: nodeConfig.ContactEmail, - Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, - CAProvider: domain.CAProviderType(nodeConfig.CAProvider), - CAProviderAccessConfig: make(map[string]any), - CAProviderExtendedConfig: nodeConfig.CAProviderConfig, - KeyAlgorithm: nodeConfig.KeyAlgorithm, - Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }), - DnsPropagationWait: nodeConfig.DnsPropagationWait, - DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout, - DnsTTL: nodeConfig.DnsTTL, - DisableFollowCNAME: nodeConfig.DisableFollowCNAME, + Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), + ContactEmail: nodeConfig.ContactEmail, + Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, + CAProvider: domain.CAProviderType(nodeConfig.CAProvider), + CAProviderAccessConfig: make(map[string]any), + CAProviderServiceConfig: nodeConfig.CAProviderConfig, + KeyAlgorithm: nodeConfig.KeyAlgorithm, + Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }), + DnsPropagationWait: nodeConfig.DnsPropagationWait, + DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout, + DnsTTL: nodeConfig.DnsTTL, + DisableFollowCNAME: nodeConfig.DisableFollowCNAME, } accessRepo := repository.NewAccessRepository() @@ -81,6 +82,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err if access, err := accessRepo.GetById(context.Background(), nodeConfig.CAProviderAccessId); err != nil { return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.CAProviderAccessId, err) } else { + options.CAProviderAccessId = access.Id options.CAProviderAccessConfig = access.Config } } @@ -91,13 +93,13 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err sslProviderConfig := &acmeSSLProviderConfig{ Config: make(map[domain.CAProviderType]map[string]any), - Provider: sslProviderDefault, + Provider: caDefault, } if settings != nil { if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil { return nil, err } else if sslProviderConfig.Provider == "" { - sslProviderConfig.Provider = sslProviderDefault + sslProviderConfig.Provider = caDefault } } @@ -163,7 +165,7 @@ func getLimiter(key string) *rate.Limiter { } func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) { - user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail) + user, err := newAcmeUser(string(options.CAProvider), options.CAProviderAccessId, options.ContactEmail) if err != nil { return nil, err } @@ -175,13 +177,26 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt // Create an ACME client config config := lego.NewConfig(user) config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) - config.CADirURL = sslProviderUrls[user.CA] - if user.CA == sslProviderSSLCom { + switch user.getCAProvider() { + case caSSLCom: if strings.HasPrefix(options.KeyAlgorithm, "RSA") { - config.CADirURL = sslProviderUrls[sslProviderSSLCom+"RSA"] + config.CADirURL = caDirUrls[caSSLCom+"RSA"] } else if strings.HasPrefix(options.KeyAlgorithm, "EC") { - config.CADirURL = sslProviderUrls[sslProviderSSLCom+"ECC"] + config.CADirURL = caDirUrls[caSSLCom+"ECC"] + } else { + config.CADirURL = caDirUrls[caSSLCom] } + + case caCustom: + caDirURL := maputil.GetString(options.CAProviderAccessConfig, "endpoint") + if caDirURL != "" { + config.CADirURL = caDirURL + } else { + return nil, fmt.Errorf("invalid ca provider endpoint") + } + + default: + config.CADirURL = caDirUrls[user.CA] } // Create an ACME client @@ -229,7 +244,7 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt } return &ApplyResult{ - CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)), + FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), ACMEAccountUrl: user.Registration.URI, diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 90a3cf72..de47ae18 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -42,22 +42,23 @@ import ( ) type applicantProviderOptions struct { - Domains []string - ContactEmail string - Provider domain.ACMEDns01ProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any - CAProvider domain.CAProviderType - CAProviderAccessConfig map[string]any - CAProviderExtendedConfig map[string]any - KeyAlgorithm string - Nameservers []string - DnsPropagationWait int32 - DnsPropagationTimeout int32 - DnsTTL int32 - DisableFollowCNAME bool - ReplacedARIAcct string - ReplacedARICert string + Domains []string + ContactEmail string + Provider domain.ACMEDns01ProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any + CAProvider domain.CAProviderType + CAProviderAccessId string + CAProviderAccessConfig map[string]any + CAProviderServiceConfig map[string]any + KeyAlgorithm string + Nameservers []string + DnsPropagationWait int32 + DnsPropagationTimeout int32 + DnsTTL int32 + DisableFollowCNAME bool + ReplacedARIAcct string + ReplacedARICert string } func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { @@ -104,7 +105,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pAliyunESA.NewChallengeProvider(&pAliyunESA.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -125,8 +126,8 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pAWSRoute53.NewChallengeProvider(&pAWSRoute53.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - HostedZoneId: maputil.GetString(options.ProviderExtendedConfig, "hostedZoneId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + HostedZoneId: maputil.GetString(options.ProviderServiceConfig, "hostedZoneId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -333,7 +334,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -350,7 +351,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), + RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -520,7 +521,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), + ZoneId: maputil.GetString(options.ProviderServiceConfig, "zoneId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index bdacf08e..e4a28746 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error nodeConfig := config.Node.GetConfigForDeploy() options := &deployerProviderOptions{ - Provider: domain.DeploymentProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, + Provider: domain.DeploymentProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index d69b05b7..d1d72408 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -19,6 +19,7 @@ import ( pAliyunDDoS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ddos" pAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa" pAliyunFC "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-fc" + pAliyunGA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ga" pAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live" pAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" @@ -34,12 +35,15 @@ import ( pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn" pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console" pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" + pBaotaWAFConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-console" + pBaotaWAFSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site" pBunnyCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn" pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly" pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly" pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" pEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications" + pFlexCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" pGcoreCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/gcore-cdn" pGoEdge "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/goedge" pHuaweiCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn" @@ -51,12 +55,15 @@ import ( pJDCloudLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-live" pJDCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-vod" pK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" + pLeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/lecdn" pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" pNetlifySite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/netlify-site" pProxmoxVE "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/proxmoxve" pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili" pRainYunRCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn" + pRatPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-console" + pRatPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-site" pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline" pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" @@ -81,7 +88,9 @@ import ( pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex" pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" + pWangsuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdn" pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro" + pWangsuCertificate "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate" pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" @@ -89,9 +98,9 @@ import ( ) type deployerProviderOptions struct { - Provider domain.DeploymentProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any + Provider domain.DeploymentProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any } func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer, error) { @@ -111,20 +120,22 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderType1PanelConsole: deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{ ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err case domain.DeploymentProviderType1PanelSite: deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{ ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), - WebsiteId: maputil.GetInt64(options.ProviderExtendedConfig, "websiteId"), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), + WebsiteId: maputil.GetInt64(options.ProviderServiceConfig, "websiteId"), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -133,7 +144,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeAliyunALB, domain.DeploymentProviderTypeAliyunAPIGW, domain.DeploymentProviderTypeAliyunCAS, domain.DeploymentProviderTypeAliyunCASDeploy, domain.DeploymentProviderTypeAliyunCDN, domain.DeploymentProviderTypeAliyunCLB, domain.DeploymentProviderTypeAliyunDCDN, domain.DeploymentProviderTypeAliyunDDoS, domain.DeploymentProviderTypeAliyunESA, domain.DeploymentProviderTypeAliyunFC, domain.DeploymentProviderTypeAliyunLive, domain.DeploymentProviderTypeAliyunNLB, domain.DeploymentProviderTypeAliyunOSS, domain.DeploymentProviderTypeAliyunVOD, domain.DeploymentProviderTypeAliyunWAF: + case domain.DeploymentProviderTypeAliyunALB, domain.DeploymentProviderTypeAliyunAPIGW, domain.DeploymentProviderTypeAliyunCAS, domain.DeploymentProviderTypeAliyunCASDeploy, domain.DeploymentProviderTypeAliyunCDN, domain.DeploymentProviderTypeAliyunCLB, domain.DeploymentProviderTypeAliyunDCDN, domain.DeploymentProviderTypeAliyunDDoS, domain.DeploymentProviderTypeAliyunESA, domain.DeploymentProviderTypeAliyunFC, domain.DeploymentProviderTypeAliyunGA, domain.DeploymentProviderTypeAliyunLive, domain.DeploymentProviderTypeAliyunNLB, domain.DeploymentProviderTypeAliyunOSS, domain.DeploymentProviderTypeAliyunVOD, domain.DeploymentProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -145,11 +156,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunALB.NewDeployer(&pAliyunALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -157,11 +168,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderExtendedConfig, "serviceType")), - GatewayId: maputil.GetString(options.ProviderExtendedConfig, "gatewayId"), - GroupId: maputil.GetString(options.ProviderExtendedConfig, "groupId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderServiceConfig, "serviceType")), + GatewayId: maputil.GetString(options.ProviderServiceConfig, "gatewayId"), + GroupId: maputil.GetString(options.ProviderServiceConfig, "groupId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -169,7 +180,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), }) return deployer, err @@ -177,9 +188,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), - ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -187,7 +198,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCDN.NewDeployer(&pAliyunCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -195,11 +206,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCLB.NewDeployer(&pAliyunCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetOrDefaultInt32(options.ProviderExtendedConfig, "listenerPort", 443), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetOrDefaultInt32(options.ProviderServiceConfig, "listenerPort", 443), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -207,7 +218,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunDCDN.NewDeployer(&pAliyunDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -215,8 +226,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunDDoS.NewDeployer(&pAliyunDDoS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -224,8 +235,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunESA.NewDeployer(&pAliyunESA.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - SiteId: maputil.GetInt64(options.ProviderExtendedConfig, "siteId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + SiteId: maputil.GetInt64(options.ProviderServiceConfig, "siteId"), }) return deployer, err @@ -233,9 +244,20 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderServiceConfig, "serviceVersion", "3.0"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + }) + return deployer, err + + case domain.DeploymentProviderTypeAliyunGA: + deployer, err := pAliyunGA.NewDeployer(&pAliyunGA.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + ResourceType: pAliyunGA.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + AcceleratorId: maputil.GetString(options.ProviderServiceConfig, "acceleratorId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -243,8 +265,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -252,10 +274,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunNLB.NewDeployer(&pAliyunNLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -263,9 +285,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunOSS.NewDeployer(&pAliyunOSS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -273,8 +295,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunVOD.NewDeployer(&pAliyunVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -282,10 +304,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunWAF.NewDeployer(&pAliyunWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderServiceConfig, "serviceVersion", "3.0"), + InstanceId: maputil.GetString(options.ProviderServiceConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -306,8 +328,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSACM.NewDeployer(&pAWSACM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - CertificateArn: maputil.GetString(options.ProviderExtendedConfig, "certificateArn"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + CertificateArn: maputil.GetString(options.ProviderServiceConfig, "certificateArn"), }) return deployer, err @@ -315,8 +337,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - DistributionId: maputil.GetString(options.ProviderExtendedConfig, "distributionId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + DistributionId: maputil.GetString(options.ProviderServiceConfig, "distributionId"), }) return deployer, err @@ -339,8 +361,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ClientId: access.ClientId, ClientSecret: access.ClientSecret, CloudName: access.CloudName, - KeyVaultName: maputil.GetString(options.ProviderExtendedConfig, "keyvaultName"), - CertificateName: maputil.GetString(options.ProviderExtendedConfig, "certificateName"), + KeyVaultName: maputil.GetString(options.ProviderServiceConfig, "keyvaultName"), + CertificateName: maputil.GetString(options.ProviderServiceConfig, "certificateName"), }) return deployer, err @@ -361,11 +383,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudAppBLB.NewDeployer(&pBaiduCloudAppBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderServiceConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -373,11 +395,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudBLB.NewDeployer(&pBaiduCloudBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderServiceConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -385,7 +407,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudCDN.NewDeployer(&pBaiduCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -412,8 +434,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeBaishanCDN: deployer, err := pBaishanCDN.NewDeployer(&pBaishanCDN.DeployerConfig{ ApiToken: access.ApiToken, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -435,7 +457,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err @@ -444,9 +466,40 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - SiteType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "siteType", "other"), - SiteName: maputil.GetString(options.ProviderExtendedConfig, "siteName"), - SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + SiteType: maputil.GetOrDefaultString(options.ProviderServiceConfig, "siteType", "other"), + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), + SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + }) + return deployer, err + + default: + break + } + } + + case domain.DeploymentProviderTypeBaotaWAFConsole, domain.DeploymentProviderTypeBaotaWAFSite: + { + access := domain.AccessConfigForBaotaWAF{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeploymentProviderTypeBaotaWAFConsole: + deployer, err := pBaotaWAFConsole.NewDeployer(&pBaotaWAFConsole.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + }) + return deployer, err + + case domain.DeploymentProviderTypeBaotaWAFSite: + deployer, err := pBaotaWAFSite.NewDeployer(&pBaotaWAFSite.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), + SitePort: maputil.GetOrDefaultInt32(options.ProviderServiceConfig, "sitePort", 443), }) return deployer, err @@ -464,8 +517,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBunnyCDN.NewDeployer(&pBunnyCDN.DeployerConfig{ ApiKey: access.ApiKey, - PullZoneId: maputil.GetString(options.ProviderExtendedConfig, "pullZoneId"), - Hostname: maputil.GetString(options.ProviderExtendedConfig, "hostname"), + PullZoneId: maputil.GetString(options.ProviderServiceConfig, "pullZoneId"), + Hostname: maputil.GetString(options.ProviderServiceConfig, "hostname"), }) return deployer, err } @@ -482,7 +535,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBytePlusCDN.NewDeployer(&pBytePlusCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -516,9 +569,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), - SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), + SiteId: maputil.GetString(options.ProviderServiceConfig, "siteId"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -533,7 +586,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pDogeCDN.NewDeployer(&pDogeCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err } @@ -548,7 +601,26 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pEdgioApplications.NewDeployer(&pEdgioApplications.DeployerConfig{ ClientId: access.ClientId, ClientSecret: access.ClientSecret, - EnvironmentId: maputil.GetString(options.ProviderExtendedConfig, "environmentId"), + EnvironmentId: maputil.GetString(options.ProviderServiceConfig, "environmentId"), + }) + return deployer, err + } + + case domain.DeploymentProviderTypeFlexCDN: + { + access := domain.AccessConfigForFlexCDN{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pFlexCDN.NewDeployer(&pFlexCDN.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiRole: access.ApiRole, + AccessKeyId: access.AccessKeyId, + AccessKey: access.AccessKey, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pFlexCDN.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -564,8 +636,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeGcoreCDN: deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{ ApiToken: access.ApiToken, - ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceId: maputil.GetInt64(options.ProviderServiceConfig, "resourceId"), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -587,8 +659,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessKeyId: access.AccessKeyId, AccessKey: access.AccessKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pGoEdge.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pGoEdge.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -605,8 +677,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudCDN.NewDeployer(&pHuaweiCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -614,11 +686,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudELB.NewDeployer(&pHuaweiCloudELB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -633,10 +705,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudWAF.NewDeployer(&pHuaweiCloudWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -657,10 +729,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudALB.NewDeployer(&pJDCloudALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), - ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"), + ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -668,7 +740,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudCDN.NewDeployer(&pJDCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -676,7 +748,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudLive.NewDeployer(&pJDCloudLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -684,7 +756,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudVOD.NewDeployer(&pJDCloudVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -693,21 +765,42 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeLeCDN: + { + access := domain.AccessConfigForLeCDN{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pLeCDN.NewDeployer(&pLeCDN.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, + ApiRole: access.ApiRole, + Username: access.Username, + Password: access.Password, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pLeCDN.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), + ClientId: maputil.GetInt64(options.ProviderServiceConfig, "clientId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ - ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderServiceConfig, "shellEnv")), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } @@ -721,11 +814,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pK8sSecret.NewDeployer(&pK8sSecret.DeployerConfig{ KubeConfig: access.KubeConfig, - Namespace: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), - SecretName: maputil.GetString(options.ProviderExtendedConfig, "secretName"), - SecretType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), - SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), - SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), + Namespace: maputil.GetOrDefaultString(options.ProviderServiceConfig, "namespace", "default"), + SecretName: maputil.GetString(options.ProviderServiceConfig, "secretName"), + SecretType: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretType", "kubernetes.io/tls"), + SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretDataKeyForKey", "tls.key"), }) return deployer, err } @@ -739,7 +832,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pNetlifySite.NewDeployer(&pNetlifySite.DeployerConfig{ ApiToken: access.ApiToken, - SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), + SiteId: maputil.GetString(options.ProviderServiceConfig, "siteId"), }) return deployer, err } @@ -756,8 +849,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiToken: access.ApiToken, ApiTokenSecret: access.ApiTokenSecret, AllowInsecureConnections: access.AllowInsecureConnections, - NodeName: maputil.GetString(options.ProviderExtendedConfig, "nodeName"), - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + NodeName: maputil.GetString(options.ProviderServiceConfig, "nodeName"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err } @@ -774,7 +867,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -782,8 +875,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuPili.NewDeployer(&pQiniuPili.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Hub: maputil.GetString(options.ProviderExtendedConfig, "hub"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Hub: maputil.GetString(options.ProviderServiceConfig, "hub"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -803,8 +896,40 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeTencentCloudCDN: deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{ ApiKey: access.ApiKey, - InstanceId: maputil.GetInt32(options.ProviderExtendedConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + InstanceId: maputil.GetInt32(options.ProviderServiceConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + }) + return deployer, err + + default: + break + } + } + + case domain.DeploymentProviderTypeRatPanelConsole, domain.DeploymentProviderTypeRatPanelSite: + { + access := domain.AccessConfigForRatPanel{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeploymentProviderTypeRatPanelConsole: + deployer, err := pRatPanelConsole.NewDeployer(&pRatPanelConsole.DeployerConfig{ + ApiUrl: access.ApiUrl, + AccessTokenId: access.AccessTokenId, + AccessToken: access.AccessToken, + AllowInsecureConnections: access.AllowInsecureConnections, + }) + return deployer, err + + case domain.DeploymentProviderTypeRatPanelSite: + deployer, err := pRatPanelSite.NewDeployer(&pRatPanelSite.DeployerConfig{ + ApiUrl: access.ApiUrl, + AccessTokenId: access.AccessTokenId, + AccessToken: access.AccessToken, + AllowInsecureConnections: access.AllowInsecureConnections, + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), }) return deployer, err @@ -824,8 +949,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiToken: access.ApiToken, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetInt32(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt32(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -837,6 +962,18 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServers)) + for i, jumpServer := range access.JumpServers { + jumpServers[i] = pSSH.JumpServerConfig{ + SshHost: jumpServer.Host, + SshPort: jumpServer.Port, + SshUsername: jumpServer.Username, + SshPassword: jumpServer.Password, + SshKey: jumpServer.Key, + SshKeyPassphrase: jumpServer.KeyPassphrase, + } + } + deployer, err := pSSH.NewDeployer(&pSSH.DeployerConfig{ SshHost: access.Host, SshPort: access.Port, @@ -844,18 +981,19 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + JumpServers: jumpServers, + UseSCP: maputil.GetBool(options.ProviderServiceConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } @@ -872,7 +1010,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCDN.NewDeployer(&pTencentCloudCDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -880,11 +1018,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCLB.NewDeployer(&pTencentCloudCLB.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -892,9 +1030,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCOS.NewDeployer(&pTencentCloudCOS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -902,7 +1040,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCSS.NewDeployer(&pTencentCloudCSS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -910,7 +1048,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudECDN.NewDeployer(&pTencentCloudECDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -918,8 +1056,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudEO.NewDeployer(&pTencentCloudEO.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + ZoneId: maputil.GetString(options.ProviderServiceConfig, "zoneId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -927,8 +1065,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -943,9 +1081,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: maputil.GetString(options.ProviderExtendedConfig, "resourceType"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: maputil.GetString(options.ProviderServiceConfig, "resourceType"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -953,8 +1091,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudVOD.NewDeployer(&pTencentCloudVOD.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - SubAppId: maputil.GetInt64(options.ProviderExtendedConfig, "subAppId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + SubAppId: maputil.GetInt64(options.ProviderServiceConfig, "subAppId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -962,9 +1100,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudWAF.NewDeployer(&pTencentCloudWAF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), - InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + DomainId: maputil.GetString(options.ProviderServiceConfig, "domainId"), + InstanceId: maputil.GetString(options.ProviderServiceConfig, "instanceId"), }) return deployer, err @@ -986,7 +1124,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), + DomainId: maputil.GetString(options.ProviderServiceConfig, "domainId"), }) return deployer, err @@ -995,9 +1133,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1018,7 +1156,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ Username: access.Username, Password: access.Password, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1039,11 +1177,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineALB.NewDeployer(&pVolcEngineALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1051,7 +1189,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCDN.NewDeployer(&pVolcEngineCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1059,7 +1197,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCertCenter.NewDeployer(&pVolcEngineCertCenter.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), }) return deployer, err @@ -1067,10 +1205,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCLB.NewDeployer(&pVolcEngineCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -1078,7 +1216,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineDCDN.NewDeployer(&pVolcEngineDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1086,9 +1224,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineImageX.NewDeployer(&pVolcEngineImageX.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceId: maputil.GetString(options.ProviderExtendedConfig, "serviceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceId: maputil.GetString(options.ProviderServiceConfig, "serviceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1096,7 +1234,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineLive.NewDeployer(&pVolcEngineLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1104,9 +1242,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineTOS.NewDeployer(&pVolcEngineTOS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1115,7 +1253,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeWangsuCDNPro: + case domain.DeploymentProviderTypeWangsuCDN, domain.DeploymentProviderTypeWangsuCDNPro, domain.DeploymentProviderTypeWangsuCertificate: { access := domain.AccessConfigForWangsu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -1123,15 +1261,31 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } switch options.Provider { + case domain.DeploymentProviderTypeWangsuCDN: + deployer, err := pWangsuCDN.NewDeployer(&pWangsuCDN.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Domains: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "domains"), ";"), func(s string) bool { return s != "" }), + }) + return deployer, err + case domain.DeploymentProviderTypeWangsuCDNPro: deployer, err := pWangsuCDNPro.NewDeployer(&pWangsuCDNPro.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, ApiKey: access.ApiKey, - Environment: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - WebhookId: maputil.GetString(options.ProviderExtendedConfig, "webhookId"), + Environment: maputil.GetOrDefaultString(options.ProviderServiceConfig, "environment", "production"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + WebhookId: maputil.GetString(options.ProviderServiceConfig, "webhookId"), + }) + return deployer, err + + case domain.DeploymentProviderTypeWangsuCertificate: + deployer, err := pWangsuCertificate.NewDeployer(&pWangsuCertificate.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -1157,7 +1311,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) } } - if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + if extendedHeadersString := maputil.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" { h, err := httputil.ParseHeaders(extendedHeadersString) if err != nil { return nil, fmt.Errorf("failed to parse webhook headers: %w", err) @@ -1169,7 +1323,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForDeployment), + WebhookData: maputil.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForDeployment), Method: access.Method, Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, diff --git a/internal/domain/access.go b/internal/domain/access.go index 35dd9b0a..13975392 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -17,10 +17,17 @@ type Access struct { type AccessConfigFor1Panel struct { ApiUrl string `json:"apiUrl"` + ApiVersion string `json:"apiVersion"` ApiKey string `json:"apiKey"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForACMECA struct { + Endpoint string `json:"endpoint"` + EabKid string `json:"eabKid,omitempty"` + EabHmacKey string `json:"eabHmacKey,omitempty"` +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode,omitempty"` @@ -60,6 +67,12 @@ type AccessConfigForBaotaPanel struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForBaotaWAF struct { + ApiUrl string `json:"apiUrl"` + ApiKey string `json:"apiKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForBytePlus struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` @@ -133,6 +146,14 @@ type AccessConfigForEmail struct { DefaultReceiverAddress string `json:"defaultReceiverAddress,omitempty"` } +type AccessConfigForFlexCDN struct { + ApiUrl string `json:"apiUrl"` + ApiRole string `json:"apiRole"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForGcore struct { ApiToken string `json:"apiToken"` } @@ -178,6 +199,15 @@ type AccessConfigForLarkBot struct { WebhookUrl string `json:"webhookUrl"` } +type AccessConfigForLeCDN struct { + ApiUrl string `json:"apiUrl"` + ApiVersion string `json:"apiVersion"` + ApiRole string `json:"apiRole"` + Username string `json:"username"` + Password string `json:"password"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForMattermost struct { ServerUrl string `json:"serverUrl"` Username string `json:"username"` @@ -240,6 +270,13 @@ type AccessConfigForRainYun struct { ApiKey string `json:"apiKey"` } +type AccessConfigForRatPanel struct { + ApiUrl string `json:"apiUrl"` + AccessTokenId int32 `json:"accessTokenId"` + AccessToken string `json:"accessToken"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForSafeLine struct { ApiUrl string `json:"apiUrl"` ApiToken string `json:"apiToken"` @@ -253,6 +290,14 @@ type AccessConfigForSSH struct { Password string `json:"password,omitempty"` Key string `json:"key,omitempty"` KeyPassphrase string `json:"keyPassphrase,omitempty"` + JumpServers []struct { + Host string `json:"host"` + Port int32 `json:"port"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Key string `json:"key,omitempty"` + KeyPassphrase string `json:"keyPassphrase,omitempty"` + } `json:"jumpServers,omitempty"` } type AccessConfigForSSLCom struct { @@ -260,7 +305,7 @@ type AccessConfigForSSLCom struct { EabHmacKey string `json:"eabHmacKey"` } -type AccessConfigForTelegram struct { +type AccessConfigForTelegramBot struct { BotToken string `json:"botToken"` DefaultChatId int64 `json:"defaultChatId,omitempty"` } diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go index d2a998da..710ce268 100644 --- a/internal/domain/certificate.go +++ b/internal/domain/certificate.go @@ -20,7 +20,7 @@ type Certificate struct { SerialNumber string `json:"serialNumber" db:"serialNumber"` Certificate string `json:"certificate" db:"certificate"` PrivateKey string `json:"privateKey" db:"privateKey"` - Issuer string `json:"issuer" db:"issuer"` + IssuerOrg string `json:"issuerOrg" db:"issuerOrg"` IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"` KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"` EffectAt time.Time `json:"effectAt" db:"effectAt"` @@ -38,7 +38,7 @@ type Certificate struct { func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate { c.SubjectAltNames = strings.Join(certX509.DNSNames, ";") c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16)) - c.Issuer = strings.Join(certX509.Issuer.Organization, ";") + c.IssuerOrg = strings.Join(certX509.Issuer.Organization, ";") c.EffectAt = certX509.NotBefore c.ExpireAt = certX509.NotAfter diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c635aad1..db0de59d 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -10,7 +10,7 @@ type AccessProviderType string */ const ( AccessProviderType1Panel = AccessProviderType("1panel") - AccessProviderTypeACMECA = AccessProviderType("acmeca") // ACME CA(预留) + AccessProviderTypeACMECA = AccessProviderType("acmeca") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") @@ -19,6 +19,7 @@ const ( AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") AccessProviderTypeBaishan = AccessProviderType("baishan") AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") + AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf") AccessProviderTypeBytePlus = AccessProviderType("byteplus") AccessProviderTypeBunny = AccessProviderType("bunny") AccessProviderTypeBuypass = AccessProviderType("buypass") @@ -36,8 +37,8 @@ const ( AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeEmail = AccessProviderType("email") - AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) - AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") // FlexCDN(预留) + AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) + AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGcore = AccessProviderType("gcore") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") @@ -49,7 +50,7 @@ const ( AccessProviderTypeLarkBot = AccessProviderType("larkbot") AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt") AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging") - AccessProviderTypeLeCDN = AccessProviderType("lecdn") // LeCDN(预留) + AccessProviderTypeLeCDN = AccessProviderType("lecdn") AccessProviderTypeLocal = AccessProviderType("local") AccessProviderTypeMattermost = AccessProviderType("mattermost") AccessProviderTypeNamecheap = AccessProviderType("namecheap") @@ -64,10 +65,11 @@ const ( AccessProviderTypeQiniu = AccessProviderType("qiniu") AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留) AccessProviderTypeRainYun = AccessProviderType("rainyun") + AccessProviderTypeRatPanel = AccessProviderType("ratpanel") AccessProviderTypeSafeLine = AccessProviderType("safeline") AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeSSLCOM = AccessProviderType("sslcom") - AccessProviderTypeTelegram = AccessProviderType("telegram") + AccessProviderTypeTelegramBot = AccessProviderType("telegrambot") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") AccessProviderTypeUpyun = AccessProviderType("upyun") @@ -90,6 +92,7 @@ type CAProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( + CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA) CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass) CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices) CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt) @@ -172,7 +175,7 @@ const ( DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos") DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa") DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc") - DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") // 阿里云全球加速(预留) + DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live") DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb") DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss") @@ -188,13 +191,15 @@ const ( DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn") DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console") DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site") + DeploymentProviderTypeBaotaWAFConsole = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-console") + DeploymentProviderTypeBaotaWAFSite = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-site") DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn") DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn") DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly) DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly) DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications") - DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) // FlexCDN(预留) + DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn") DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge) DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn") @@ -206,7 +211,7 @@ const ( DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live") DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod") DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret") - DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) // LeCDN(预留) + DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal) DeploymentProviderTypeNetlifySite = DeploymentProviderType(AccessProviderTypeNetlify + "-site") DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE) @@ -214,6 +219,8 @@ const ( DeploymentProviderTypeQiniuKodo = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo") DeploymentProviderTypeQiniuPili = DeploymentProviderType(AccessProviderTypeQiniu + "-pili") DeploymentProviderTypeRainYunRCDN = DeploymentProviderType(AccessProviderTypeRainYun + "-rcdn") + DeploymentProviderTypeRatPanelConsole = DeploymentProviderType(AccessProviderTypeRatPanel + "-console") + DeploymentProviderTypeRatPanelSite = DeploymentProviderType(AccessProviderTypeRatPanel + "-site") DeploymentProviderTypeSafeLine = DeploymentProviderType(AccessProviderTypeSafeLine) DeploymentProviderTypeSSH = DeploymentProviderType(AccessProviderTypeSSH) DeploymentProviderTypeTencentCloudCDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn") @@ -239,9 +246,9 @@ const ( DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex") DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live") DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos") - DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") // 网宿 CDN(预留) + DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro") - DeploymentProviderTypeWangsuCert = DeploymentProviderType(AccessProviderTypeWangsu + "-cert") // 网宿证书管理(预留) + DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate") DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook) ) @@ -259,7 +266,7 @@ const ( NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail) NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot) NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost) - NotificationProviderTypeTelegram = NotificationProviderType(AccessProviderTypeTelegram) + NotificationProviderTypeTelegramBot = NotificationProviderType(AccessProviderTypeTelegramBot) NotificationProviderTypeWebhook = NotificationProviderType(AccessProviderTypeWebhook) NotificationProviderTypeWeComBot = NotificationProviderType(AccessProviderTypeWeComBot) ) diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 6f3cccea..6a96dd81 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -105,11 +105,6 @@ type WorkflowNodeConfigForNotify struct { } func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { - skipBeforeExpiryDays := maputil.GetInt32(n.Config, "skipBeforeExpiryDays") - if skipBeforeExpiryDays == 0 { - skipBeforeExpiryDays = 30 - } - return WorkflowNodeConfigForApply{ Domains: maputil.GetString(n.Config, "domains"), ContactEmail: maputil.GetString(n.Config, "contactEmail"), @@ -126,7 +121,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"), DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"), DisableARI: maputil.GetBool(n.Config, "disableARI"), - SkipBeforeExpiryDays: skipBeforeExpiryDays, + SkipBeforeExpiryDays: maputil.GetOrDefaultInt32(n.Config, "skipBeforeExpiryDays", 30), } } diff --git a/internal/notify/notifier.go b/internal/notify/notifier.go index 955e88c3..ee3fbd2f 100644 --- a/internal/notify/notifier.go +++ b/internal/notify/notifier.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error nodeConfig := config.Node.GetConfigForNotify() options := ¬ifierProviderOptions{ - Provider: domain.NotificationProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, + Provider: domain.NotificationProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/notify/providers.go b/internal/notify/providers.go index c57b9c82..3a8c575d 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -6,21 +6,21 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" - pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + pDingTalkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot" pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" - pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + pLarkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot" pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" - pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + pTelegramBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot" pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" - pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + pWeComBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot" httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) type notifierProviderOptions struct { - Provider domain.NotificationProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any + Provider domain.NotificationProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any } func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) { @@ -36,7 +36,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ + return pDingTalkBot.NewNotifier(&pDingTalkBot.NotifierConfig{ WebhookUrl: access.WebhookUrl, Secret: access.Secret, }) @@ -55,8 +55,8 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier SmtpTls: access.SmtpTls, Username: access.Username, Password: access.Password, - SenderAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "senderAddress", access.DefaultSenderAddress), - ReceiverAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", access.DefaultReceiverAddress), + SenderAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "senderAddress", access.DefaultSenderAddress), + ReceiverAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "receiverAddress", access.DefaultReceiverAddress), }) } @@ -67,7 +67,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - return pLark.NewNotifier(&pLark.NotifierConfig{ + return pLarkBot.NewNotifier(&pLarkBot.NotifierConfig{ WebhookUrl: access.WebhookUrl, }) } @@ -83,20 +83,20 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier ServerUrl: access.ServerUrl, Username: access.Username, Password: access.Password, - ChannelId: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "channelId", access.DefaultChannelId), + ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId), }) } - case domain.NotificationProviderTypeTelegram: + case domain.NotificationProviderTypeTelegramBot: { - access := domain.AccessConfigForTelegram{} + access := domain.AccessConfigForTelegramBot{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ + return pTelegramBot.NewNotifier(&pTelegramBot.NotifierConfig{ BotToken: access.BotToken, - ChatId: maputil.GetOrDefaultInt64(options.ProviderExtendedConfig, "chatId", access.DefaultChatId), + ChatId: maputil.GetOrDefaultInt64(options.ProviderServiceConfig, "chatId", access.DefaultChatId), }) } @@ -117,7 +117,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) } } - if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + if extendedHeadersString := maputil.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" { h, err := httputil.ParseHeaders(extendedHeadersString) if err != nil { return nil, fmt.Errorf("failed to parse webhook headers: %w", err) @@ -129,7 +129,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForNotification), + WebhookData: maputil.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForNotification), Method: access.Method, Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, @@ -143,7 +143,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ + return pWeComBot.NewNotifier(&pWeComBot.NotifierConfig{ WebhookUrl: access.WebhookUrl, }) } diff --git a/internal/notify/providers_deprecated.go b/internal/notify/providers_deprecated.go index 1e862866..d2d926a6 100644 --- a/internal/notify/providers_deprecated.go +++ b/internal/notify/providers_deprecated.go @@ -6,17 +6,17 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" - pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot" pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" - pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot" pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" - pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot" pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" - pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) @@ -52,9 +52,9 @@ func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, c case domain.NotifyChannelTypeGotify: return pGotify.NewNotifier(&pGotify.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - Token: maputil.GetString(channelConfig, "token"), - Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), + ServerUrl: maputil.GetString(channelConfig, "url"), + Token: maputil.GetString(channelConfig, "token"), + Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), }) case domain.NotifyChannelTypeLark: @@ -83,7 +83,7 @@ func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, c case domain.NotifyChannelTypeServerChan: return pServerChan.NewNotifier(&pServerChan.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), + ServerUrl: maputil.GetString(channelConfig, "url"), }) case domain.NotifyChannelTypeTelegram: diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go index bf7026da..56deaa2d 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/aliyun_esa.go @@ -24,6 +24,7 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, providerConfig := internal.NewDefaultConfig() providerConfig.SecretID = config.AccessKeyId providerConfig.SecretKey = config.AccessKeySecret + providerConfig.RegionID = config.Region if config.DnsPropagationTimeout != 0 { providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second } diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go index 79df4083..51d4e7c4 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go @@ -89,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return &DNSProvider{ client: client, config: config, + + siteIDs: make(map[string]int64), }, nil } @@ -100,7 +102,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err) } - siteId, err := d.getSiteId(authZone) + siteId, err := d.getSiteId(strings.TrimRight(authZone, ".")) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", authZone, err) } @@ -120,7 +122,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err) } - siteId, err := d.getSiteId(authZone) + siteId, err := d.getSiteId(strings.TrimRight(authZone, ".")) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", authZone, err) } @@ -148,10 +150,11 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) { pageSize := 500 for { request := &aliesa.ListSitesRequest{ - SiteName: tea.String(siteName), - PageNumber: tea.Int32(int32(pageNumber)), - PageSize: tea.Int32(int32(pageNumber)), - AccessType: tea.String("NS"), + SiteName: tea.String(siteName), + SiteSearchType: tea.String("exact"), + PageNumber: tea.Int32(int32(pageNumber)), + PageSize: tea.Int32(int32(pageSize)), + AccessType: tea.String("NS"), } response, err := d.client.ListSites(request) if err != nil { @@ -178,7 +181,7 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) { } } - return 0, errors.New("failed to get site id") + return 0, errors.New("site not found") } func (d *DNSProvider) findDNSRecord(siteId int64, effectiveFQDN string) (*aliesa.ListRecordsResponseBodyRecords, error) { @@ -186,11 +189,12 @@ func (d *DNSProvider) findDNSRecord(siteId int64, effectiveFQDN string) (*aliesa pageSize := 500 for { request := &aliesa.ListRecordsRequest{ - SiteId: tea.Int64(siteId), - Type: tea.String("TXT"), - RecordName: tea.String(effectiveFQDN), - PageNumber: tea.Int32(int32(pageNumber)), - PageSize: tea.Int32(int32(pageNumber)), + SiteId: tea.Int64(siteId), + Type: tea.String("TXT"), + RecordName: tea.String(effectiveFQDN), + RecordMatchType: tea.String("exact"), + PageNumber: tea.Int32(int32(pageNumber)), + PageSize: tea.Int32(int32(pageSize)), } response, err := d.client.ListRecords(request) if err != nil { diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go index 069e01f9..e81b264f 100644 --- a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go @@ -9,12 +9,15 @@ import ( "net/url" "github.com/usual2970/certimate/internal/pkg/core/deployer" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + // 可取值 "v1"、"v2"。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 @@ -26,7 +29,7 @@ type DeployerConfig struct { type DeployerProvider struct { config *DeployerConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client } var _ deployer.Deployer = (*DeployerProvider)(nil) @@ -36,7 +39,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } @@ -59,7 +62,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { // 设置面板 SSL 证书 - updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{ + updateSystemSSLReq := &onepanelsdk.UpdateSystemSSLRequest{ Cert: certPEM, Key: privkeyPEM, SSL: "enable", @@ -79,16 +82,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return &deployer.DeployResult{}, nil } -func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) if skipTlsVerify { client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go index abec586c..88bf961a 100644 --- a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go @@ -15,6 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string ) @@ -24,6 +25,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") } @@ -34,6 +36,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIVERSION="v1" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" */ func TestDeploy(t *testing.T) { @@ -45,11 +48,13 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiVersion: fApiVersion, ApiKey: fApiKey, AllowInsecureConnections: true, AutoRestart: true, diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go index 7d360c77..690e5242 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -12,12 +12,15 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + // 可取值 "v1"、"v2"。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 @@ -35,7 +38,7 @@ type DeployerConfig struct { type DeployerProvider struct { config *DeployerConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client sslUploader uploader.Uploader } @@ -46,14 +49,15 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ - ApiUrl: config.ApiUrl, - ApiKey: config.ApiKey, + ApiUrl: config.ApiUrl, + ApiVersion: config.ApiVersion, + ApiKey: config.ApiKey, }) if err != nil { return nil, fmt.Errorf("failed to create ssl uploader: %w", err) @@ -103,7 +107,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string, } // 获取网站 HTTPS 配置 - getHttpsConfReq := &opsdk.GetHttpsConfRequest{ + getHttpsConfReq := &onepanelsdk.GetHttpsConfRequest{ WebsiteID: d.config.WebsiteId, } getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq) @@ -122,7 +126,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string, // 修改网站 HTTPS 配置 certId, _ := strconv.ParseInt(upres.CertId, 10, 64) - updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{ + updateHttpsConfReq := &onepanelsdk.UpdateHttpsConfRequest{ WebsiteID: d.config.WebsiteId, Type: "existed", WebsiteSSLID: certId, @@ -147,7 +151,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri } // 获取证书详情 - getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{ + getWebsiteSSLReq := &onepanelsdk.GetWebsiteSSLRequest{ SSLID: d.config.CertificateId, } getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq) @@ -157,7 +161,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri } // 更新证书 - uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{ Type: "paste", SSLID: d.config.CertificateId, Description: getWebsiteSSLResp.Data.Description, @@ -173,16 +177,20 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri return nil } -func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) if skipTlsVerify { client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go index 702584e3..1d5bafef 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go @@ -15,6 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string fWebsiteId int64 ) @@ -25,6 +26,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") flag.Int64Var(&fWebsiteId, argsPrefix+"WEBSITEID", 0, "") } @@ -36,6 +38,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELSITE_APIVERSION="v1" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \ --CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id" */ @@ -48,12 +51,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), fmt.Sprintf("WEBSITEID: %v", fWebsiteId), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiVersion: fApiVersion, ApiKey: fApiKey, AllowInsecureConnections: true, ResourceType: provider.RESOURCE_TYPE_WEBSITE, 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 3dca4a9d..35b4997c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -157,7 +157,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -192,7 +192,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -211,8 +211,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId d.logger.Info("found https/quic listeners to deploy", slog.Any("listenerIds", listenerIds)) for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go index c75119e9..11d5b565 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -96,6 +96,7 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ 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 d443514e..34c3a49e 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -171,7 +171,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId select { case <-ctx.Done(): return ctx.Err() - default: if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { errs = append(errs, err) diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go index 3b8ce12d..dfa46173 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -18,7 +18,7 @@ var ( fAccessKeySecret string fRegion string fLoadbalancerId string - fListenerPort int + fListenerPort int64 fDomain string ) @@ -31,7 +31,7 @@ func init() { flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") - flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") + flag.Int64Var(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } diff --git a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go index 8557068c..426aa3a6 100644 --- a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go +++ b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go @@ -22,6 +22,7 @@ type DeployerConfig struct { // 阿里云地域。 Region string `json:"region"` // 服务版本。 + // 可取值 "2.0"、"3.0"。 ServiceVersion string `json:"serviceVersion"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go new file mode 100644 index 00000000..f69660a8 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go @@ -0,0 +1,322 @@ +package aliyunga + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + + aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliga "github.com/alibabacloud-go/ga-20191120/v3/client" + "github.com/alibabacloud-go/tea/tea" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" +) + +type DeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 全球加速实例 ID。 + AcceleratorId string `json:"acceleratorId"` + // 全球加速监听 ID。 + // 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` + // SNI 域名(不支持泛域名)。 + // 部署资源类型为 [RESOURCE_TYPE_ACCELERATOR]、[RESOURCE_TYPE_LISTENER] 时选填。 + Domain string `json:"domain,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *aliga.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_ACCELERATOR: + if err := d.deployToAccelerator(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { + return nil, err + } + + case RESOURCE_TYPE_LISTENER: + if err := d.deployToListener(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToAccelerator(ctx context.Context, cloudCertId string) error { + if d.config.AcceleratorId == "" { + return errors.New("config `acceleratorId` is required") + } + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlisteners + listenerIds := make([]string, 0) + listListenersPageNumber := int32(1) + listListenersPageSize := int32(50) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + listListenersReq := &aliga.ListListenersRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(d.config.AcceleratorId), + PageNumber: tea.Int32(listListenersPageNumber), + PageSize: tea.Int32(listListenersPageSize), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + d.logger.Debug("sdk request 'ga.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.ListListeners': %w", err) + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) + } + } + } + + if len(listListenersResp.Body.Listeners) < int(listListenersPageSize) { + break + } else { + listListenersPageNumber++ + } + } + + // 遍历更新监听证书 + if len(listenerIds) == 0 { + d.logger.Info("no ga listeners to deploy") + } else { + var errs []error + d.logger.Info("found https listeners to deploy", slog.Any("listenerIds", listenerIds)) + + for _, listenerId := range listenerIds { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.AcceleratorId == "" { + return errors.New("config `acceleratorId` is required") + } + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudAcceleratorId string, cloudListenerId string, cloudCertId string) error { + // 查询监听绑定的证书列表 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlistenercertificates + var listenerDefaultCertificate *aliga.ListListenerCertificatesResponseBodyCertificates + var listenerAdditionalCertificates []*aliga.ListListenerCertificatesResponseBodyCertificates = make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) + var listListenerCertificatesNextToken *string + for { + listListenerCertificatesReq := &aliga.ListListenerCertificatesRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(d.config.AcceleratorId), + NextToken: listListenerCertificatesNextToken, + MaxResults: tea.Int32(20), + } + listListenerCertificatesResp, err := d.sdkClient.ListListenerCertificates(listListenerCertificatesReq) + d.logger.Debug("sdk request 'ga.ListListenerCertificates'", slog.Any("request", listListenerCertificatesReq), slog.Any("response", listListenerCertificatesResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.ListListenerCertificates': %w", err) + } + + if listListenerCertificatesResp.Body.Certificates != nil { + for _, certificate := range listListenerCertificatesResp.Body.Certificates { + if tea.BoolValue(certificate.IsDefault) { + listenerDefaultCertificate = certificate + } else { + listenerAdditionalCertificates = append(listenerAdditionalCertificates, certificate) + } + } + } + + if listListenerCertificatesResp.Body.NextToken == nil { + break + } else { + listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken + } + } + + if d.config.Domain == "" { + // 未指定 SNI,只需部署到监听器 + if listenerDefaultCertificate != nil && tea.StringValue(listenerDefaultCertificate.CertificateId) == cloudCertId { + d.logger.Info("no need to update ga listener default certificate") + return nil + } + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updatelistener + updateListenerReq := &aliga.UpdateListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliga.UpdateListenerRequestCertificates{{ + Id: tea.String(cloudCertId), + }}, + } + updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq) + d.logger.Debug("sdk request 'ga.UpdateListener'", slog.Any("request", updateListenerReq), slog.Any("response", updateListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.UpdateListener': %w", err) + } + } else { + // 指定 SNI,需部署到扩展域名 + if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool { + return tea.StringValue(item.CertificateId) == cloudCertId + }) { + d.logger.Info("no need to update ga listener additional certificate") + return nil + } + + if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool { + return tea.StringValue(item.Domain) == d.config.Domain + }) { + // 为监听替换扩展证书 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updateadditionalcertificatewithlistener + updateAdditionalCertificateWithListenerReq := &aliga.UpdateAdditionalCertificateWithListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(cloudAcceleratorId), + ListenerId: tea.String(cloudListenerId), + CertificateId: tea.String(cloudCertId), + Domain: tea.String(d.config.Domain), + } + updateAdditionalCertificateWithListenerResp, err := d.sdkClient.UpdateAdditionalCertificateWithListener(updateAdditionalCertificateWithListenerReq) + d.logger.Debug("sdk request 'ga.UpdateAdditionalCertificateWithListener'", slog.Any("request", updateAdditionalCertificateWithListenerReq), slog.Any("response", updateAdditionalCertificateWithListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.UpdateAdditionalCertificateWithListener': %w", err) + } + } else { + // 为监听绑定扩展证书 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-associateadditionalcertificateswithlistener + associateAdditionalCertificatesWithListenerReq := &aliga.AssociateAdditionalCertificatesWithListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(cloudAcceleratorId), + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliga.AssociateAdditionalCertificatesWithListenerRequestCertificates{{ + Id: tea.String(cloudCertId), + Domain: tea.String(d.config.Domain), + }}, + } + associateAdditionalCertificatesWithListenerResp, err := d.sdkClient.AssociateAdditionalCertificatesWithListener(associateAdditionalCertificatesWithListenerReq) + d.logger.Debug("sdk request 'ga.AssociateAdditionalCertificatesWithListener'", slog.Any("request", associateAdditionalCertificatesWithListenerReq), slog.Any("response", associateAdditionalCertificatesWithListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.AssociateAdditionalCertificatesWithListener': %w", err) + } + } + } + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliga.Client, error) { + // 接入点一览 https://api.aliyun.com/product/Ga + config := &aliopen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("ga.cn-hangzhou.aliyuncs.com"), + } + + client, err := aliga.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} + +func createSslUploader(accessKeyId, accessKeySecret string) (uploader.Uploader, error) { + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: "cn-hangzhou", + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go new file mode 100644 index 00000000..611ddc41 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go @@ -0,0 +1,118 @@ +package aliyunga_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ga" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fAcceleratorId string + fListenerId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNGA_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fAcceleratorId, argsPrefix+"ACCELERATORID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_ga_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNGA_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCELERATORID="your-ga-accelerator-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_LISTENERID="your-ga-listener-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_DOMAIN="your-ga-sni-domain" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToAccelerator", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("ACCELERATORID: %v", fAcceleratorId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + ResourceType: provider.RESOURCE_TYPE_ACCELERATOR, + AcceleratorId: fAcceleratorId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + ResourceType: provider.RESOURCE_TYPE_LISTENER, + ListenerId: fListenerId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/consts.go b/internal/pkg/core/deployer/providers/aliyun-ga/consts.go new file mode 100644 index 00000000..f96d98d5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/consts.go @@ -0,0 +1,10 @@ +package aliyunga + +type ResourceType string + +const ( + // 资源类型:部署到指定全球加速器。 + RESOURCE_TYPE_ACCELERATOR = ResourceType("accelerator") + // 资源类型:部署到指定监听器。 + RESOURCE_TYPE_LISTENER = ResourceType("listener") +) 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 b8391144..58015f3d 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -145,7 +145,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -167,7 +167,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId select { case <-ctx.Done(): return ctx.Err() - default: if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { errs = append(errs, err) diff --git a/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go new file mode 100644 index 00000000..811b350b --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go @@ -0,0 +1,88 @@ +package baotapanelconsole + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf" +) + +type DeployerConfig struct { + // 堡塔云 WAF 地址。 + ApiUrl string `json:"apiUrl"` + // 堡塔云 WAF 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *btsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 设置面板 SSL + configSetSSLReq := &btsdk.ConfigSetSSLRequest{ + CertContent: certPEM, + KeyContent: privkeyPEM, + } + configSetSSLResp, err := d.sdkClient.ConfigSetSSL(configSetSSLReq) + d.logger.Debug("sdk request 'bt.ConfigSetSSL'", slog.Any("request", configSetSSLReq), slog.Any("response", configSetSSLResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.ConfigSetSSL': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid baota api url") + } + + if apiKey == "" { + return nil, errors.New("invalid baota api key") + } + + client := btsdk.NewClient(apiUrl, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go new file mode 100644 index 00000000..ba6ddd26 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go @@ -0,0 +1,73 @@ +package baotapanelconsole_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-console" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fSiteName string + fSitePort int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./baotawaf_console_test.go -args \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_APIKEY="your-api-key" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go new file mode 100644 index 00000000..ed05937a --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go @@ -0,0 +1,151 @@ +package baotapanelwaf + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // 堡塔云 WAF 地址。 + ApiUrl string `json:"apiUrl"` + // 堡塔云 WAF 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 网站名称。 + SiteName string `json:"siteName"` + // 网站 SSL 端口。 + // 零值时默认为 443。 + SitePort int32 `json:"sitePort,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *btsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + if d.config.SiteName == "" { + return nil, errors.New("config `siteName` is required") + } + if d.config.SitePort == 0 { + d.config.SitePort = 443 + } + + // 遍历获取网站列表,获取网站 ID + // REF: https://support.huaweicloud.com/api-waf/ListHost.html + siteId := "" + getSitListPage := int32(1) + getSitListPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getSiteListReq := &btsdk.GetSiteListRequest{ + SiteName: typeutil.ToPtr(d.config.SiteName), + Page: typeutil.ToPtr(getSitListPage), + PageSize: typeutil.ToPtr(getSitListPageSize), + } + getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) + d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) + } + + if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil { + for _, siteItem := range getSiteListResp.Result.List { + if siteItem.SiteName == d.config.SiteName { + siteId = siteItem.SiteId + break + } + } + } + + if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) { + break + } else { + getSitListPage++ + } + } + if siteId == "" { + return nil, errors.New("site not found") + } + + // 修改站点配置 + modifySiteReq := &btsdk.ModifySiteRequest{ + SiteId: siteId, + Type: typeutil.ToPtr("openCert"), + Server: &btsdk.SiteServerInfo{ + ListenSSLPort: typeutil.ToPtr(d.config.SitePort), + SSL: &btsdk.SiteServerSSLInfo{ + IsSSL: typeutil.ToPtr(int32(1)), + FullChain: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + }, + }, + } + modifySiteResp, err := d.sdkClient.ModifySite(modifySiteReq) + d.logger.Debug("sdk request 'bt.ModifySite'", slog.Any("request", modifySiteReq), slog.Any("response", modifySiteResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.ModifySite': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid baota api url") + } + + if apiKey == "" { + return nil, errors.New("invalid baota api key") + } + + client := btsdk.NewClient(apiUrl, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go new file mode 100644 index 00000000..4e1ffe34 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go @@ -0,0 +1,81 @@ +package baotapanelwaf_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fSiteName string + fSitePort int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFSITE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") + flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "") + flag.Int64Var(&fSitePort, argsPrefix+"SITEPORT", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./baotawaf_site_test.go -args \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIKEY="your-api-key" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITENAME="your-site-name"\ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITEPORT=443 +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + fmt.Sprintf("SITENAME: %v", fSiteName), + fmt.Sprintf("SITEPORT: %v", fSitePort), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + SiteName: fSiteName, + SitePort: int32(fSitePort), + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/flexcdn/consts.go b/internal/pkg/core/deployer/providers/flexcdn/consts.go new file mode 100644 index 00000000..be55a475 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/consts.go @@ -0,0 +1,8 @@ +package flexcdn + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go new file mode 100644 index 00000000..a12ed164 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go @@ -0,0 +1,145 @@ +package flexcdn + +import ( + "context" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + flexcdnsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/flexcdn" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" +) + +type DeployerConfig struct { + // FlexCDN URL。 + ApiUrl string `json:"apiUrl"` + // FlexCDN 用户角色。 + // 可取值 "user"、"admin"。 + ApiRole string `json:"apiRole"` + // FlexCDN AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // FlexCDN AccessKey。 + AccessKey string `json:"accessKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *flexcdnsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + // 修改证书 + // REF: https://flexcdn.cloud/dev/api/service/SSLCertService?role=user#updateSSLCert + updateSSLCertReq := &flexcdnsdk.UpdateSSLCertRequest{ + SSLCertId: d.config.CertificateId, + IsOn: true, + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + ServerName: certX509.Subject.CommonName, + IsCA: false, + CertData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + KeyData: base64.StdEncoding.EncodeToString([]byte(privkeyPEM)), + TimeBeginAt: certX509.NotBefore.Unix(), + TimeEndAt: certX509.NotAfter.Unix(), + DNSNames: certX509.DNSNames, + CommonNames: []string{certX509.Subject.CommonName}, + } + updateSSLCertResp, err := d.sdkClient.UpdateSSLCert(updateSSLCertReq) + d.logger.Debug("sdk request 'flexcdn.UpdateSSLCert'", slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'flexcdn.UpdateSSLCert': %w", err) + } + + return nil +} + +func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*flexcdnsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid flexcdn api url") + } + + if apiRole != "user" && apiRole != "admin" { + return nil, errors.New("invalid flexcdn api role") + } + + if accessKeyId == "" { + return nil, errors.New("invalid flexcdn access key id") + } + + if accessKey == "" { + return nil, errors.New("invalid flexcdn access key") + } + + client := flexcdnsdk.NewClient(apiUrl, apiRole, accessKeyId, accessKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go new file mode 100644 index 00000000..b9b8de07 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go @@ -0,0 +1,83 @@ +package flexcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessKeyId string + fAccessKey string + fCertificateId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_FLEXCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./flexcdn_test.go -args \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_APIURL="http://127.0.0.1:7788" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_FLEXCDN_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToCertificate", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiRole: "user", + AccessKeyId: fAccessKeyId, + AccessKey: fAccessKey, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/goedge/goedge.go b/internal/pkg/core/deployer/providers/goedge/goedge.go index 73eade64..ecae774e 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge.go @@ -19,6 +19,7 @@ type DeployerConfig struct { // GoEdge URL。 ApiUrl string `json:"apiUrl"` // GoEdge 用户角色。 + // 可取值 "user"、"admin"。 ApiRole string `json:"apiRole"` // GoEdge AccessKeyId。 AccessKeyId string `json:"accessKeyId"` diff --git a/internal/pkg/core/deployer/providers/goedge/goedge_test.go b/internal/pkg/core/deployer/providers/goedge/goedge_test.go index c8c32b37..d10f931c 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge_test.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge_test.go @@ -17,7 +17,7 @@ var ( fApiUrl string fAccessKeyId string fAccessKey string - fCertificateId int + fCertificateId int64 ) func init() { @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") - flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") } /* @@ -45,7 +45,7 @@ Shell command to run this test: func TestDeploy(t *testing.T) { flag.Parse() - t.Run("Deploy", func(t *testing.T) { + t.Run("Deploy_ToCertificate", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), @@ -58,11 +58,12 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiRole: "user", AccessKeyId: fAccessKeyId, AccessKey: fAccessKey, AllowInsecureConnections: true, ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, - CertificateId: int64(fCertificateId), + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) 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 748111dd..23ec4a92 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -210,7 +210,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str select { case <-ctx.Done(): return ctx.Err() - default: if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { errs = append(errs, err) diff --git a/internal/pkg/core/deployer/providers/lecdn/consts.go b/internal/pkg/core/deployer/providers/lecdn/consts.go new file mode 100644 index 00000000..f5b7c0c9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/consts.go @@ -0,0 +1,8 @@ +package lecdn + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/lecdn/lecdn.go b/internal/pkg/core/deployer/providers/lecdn/lecdn.go new file mode 100644 index 00000000..1ad88dcf --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/lecdn.go @@ -0,0 +1,176 @@ +package lecdn + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + leclientsdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/client" + lemastersdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/master" +) + +type DeployerConfig struct { + // LeCDN URL。 + ApiUrl string `json:"apiUrl"` + // LeCDN 版本。 + // 可取值 "v3"。 + ApiVersion string `json:"apiVersion"` + // LeCDN 用户角色。 + // 可取值 "client"、"master"。 + ApiRole string `json:"apiRole"` + // LeCDN 用户名。 + Username string `json:"accessKeyId"` + // LeCDN 用户密码。 + Password string `json:"accessKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` + // 客户 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时选填。 + ClientId int64 `json:"clientId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient interface{} +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +const ( + apiVersionV3 = "v3" + + apiRoleClient = "client" + apiRoleMaster = "master" +) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiRole, config.Username, config.Password, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 修改证书 + // REF: https://wdk0pwf8ul.feishu.cn/wiki/YE1XwCRIHiLYeKkPupgcXrlgnDd + switch sdkClient := d.sdkClient.(type) { + case *leclientsdkv3.Client: + updateSSLCertReq := &leclientsdkv3.UpdateCertificateRequest{ + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + Type: "upload", + SSLPEM: certPEM, + SSLKey: privkeyPEM, + AutoRenewal: false, + } + updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq) + d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err) + } + + case *lemastersdkv3.Client: + updateSSLCertReq := &lemastersdkv3.UpdateCertificateRequest{ + ClientId: d.config.ClientId, + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + Type: "upload", + SSLPEM: certPEM, + SSLKey: privkeyPEM, + AutoRenewal: false, + } + updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq) + d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err) + } + + default: + panic("sdk client is not implemented") + } + + return nil +} + +func createSdkClient(apiUrl, apiVersion, apiRole, username, password string, skipTlsVerify bool) (interface{}, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid lecdn api url") + } + + if username == "" { + return nil, errors.New("invalid lecdn username") + } + + if password == "" { + return nil, errors.New("invalid lecdn password") + } + + if apiVersion == apiVersionV3 && apiRole == apiRoleClient { + // v3 版客户端 + client := leclientsdkv3.NewClient(apiUrl, username, password) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil + } else if apiVersion == apiVersionV3 && apiRole == apiRoleMaster { + // v3 版主控端 + client := lemastersdkv3.NewClient(apiUrl, username, password) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil + } + + return nil, fmt.Errorf("invalid lecdn api version or user role") +} diff --git a/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go b/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go new file mode 100644 index 00000000..cbaa4523 --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go @@ -0,0 +1,87 @@ +package lecdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/lecdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiVersion string + fUsername string + fPassword string + fCertificateId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_LECDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v3", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./lecdn_test.go -args \ + --CERTIMATE_DEPLOYER_LECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_LECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_LECDN_APIURL="http://127.0.0.1:5090" \ + --CERTIMATE_DEPLOYER_LECDN_USERNAME="your-username" \ + --CERTIMATE_DEPLOYER_LECDN_PASSWORD="your-password" \ + --CERTIMATE_DEPLOYER_LECDN_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToCertificate", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiVersion: fApiVersion, + ApiRole: "user", + Username: fUsername, + Password: fPassword, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console.go b/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console.go new file mode 100644 index 00000000..51faf4f2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console.go @@ -0,0 +1,94 @@ +package ratpanelconsole + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel" +) + +type DeployerConfig struct { + // 耗子面板地址。 + ApiUrl string `json:"apiUrl"` + // 耗子面板访问令牌 ID。 + AccessTokenId int32 `json:"accessTokenId"` + // 耗子面板访问令牌。 + AccessToken string `json:"accessToken"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *rpsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 设置面板 SSL 证书 + settingCertReq := &rpsdk.SettingCertRequest{ + Certificate: certPEM, + PrivateKey: privkeyPEM, + } + settingCertResp, err := d.sdkClient.SettingCert(settingCertReq) + d.logger.Debug("sdk request 'ratpanel.SettingCert'", slog.Any("request", settingCertReq), slog.Any("response", settingCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.SettingCert': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl string, accessTokenId int32, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid ratpanel api url") + } + + if accessTokenId == 0 { + return nil, errors.New("invalid ratpanel access token id") + } + + if accessToken == "" { + return nil, errors.New("invalid ratpanel access token") + } + + client := rpsdk.NewClient(apiUrl, accessTokenId, accessToken) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console_test.go b/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console_test.go new file mode 100644 index 00000000..3f3193b3 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ratpanel-console/ratpanel_console_test.go @@ -0,0 +1,76 @@ +package ratpanelconsole_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-console" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessTokenId int64 + fAccessToken string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_RATPANELCONSOLE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.Int64Var(&fAccessTokenId, argsPrefix+"ACCESSTOKENID", 0, "") + flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ratpanel_console_test.go -args \ + --CERTIMATE_DEPLOYER_RATPANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_RATPANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_RATPANELCONSOLE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_RATPANELCONSOLE_ACCESSTOKENID="your-access-token-id" \ + --CERTIMATE_DEPLOYER_RATPANELCONSOLE_ACCESSTOKEN="your-access-token" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("ACCESSTOKENID: %v", fAccessTokenId), + fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + AccessTokenId: int32(fAccessTokenId), + AccessToken: fAccessToken, + AllowInsecureConnections: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site.go b/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site.go new file mode 100644 index 00000000..b4e283be --- /dev/null +++ b/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site.go @@ -0,0 +1,101 @@ +package ratpanelsite + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel" +) + +type DeployerConfig struct { + // 耗子面板地址。 + ApiUrl string `json:"apiUrl"` + // 耗子面板访问令牌 ID。 + AccessTokenId int32 `json:"accessTokenId"` + // 耗子面板访问令牌。 + AccessToken string `json:"accessToken"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 网站名称。 + SiteName string `json:"siteName"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *rpsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + if d.config.SiteName == "" { + return nil, errors.New("config `siteName` is required") + } + + // 设置站点 SSL 证书 + websiteCertReq := &rpsdk.WebsiteCertRequest{ + SiteName: d.config.SiteName, + Certificate: certPEM, + PrivateKey: privkeyPEM, + } + websiteCertResp, err := d.sdkClient.WebsiteCert(websiteCertReq) + d.logger.Debug("sdk request 'ratpanel.WebsiteCert'", slog.Any("request", websiteCertReq), slog.Any("response", websiteCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.WebsiteCert': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl string, accessTokenId int32, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid ratpanel api url") + } + + if accessTokenId == 0 { + return nil, errors.New("invalid ratpanel access token id") + } + + if accessToken == "" { + return nil, errors.New("invalid ratpanel access token") + } + + client := rpsdk.NewClient(apiUrl, accessTokenId, accessToken) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site_test.go b/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site_test.go new file mode 100644 index 00000000..658175fb --- /dev/null +++ b/internal/pkg/core/deployer/providers/ratpanel-site/ratpanel_site_test.go @@ -0,0 +1,81 @@ +package ratpanelsite_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-site" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessTokenId int64 + fAccessToken string + fSiteName string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_RATPANELSITE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.Int64Var(&fAccessTokenId, argsPrefix+"ACCESSTOKENID", 0, "") + flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "") + flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ratpanel_site_test.go -args \ + --CERTIMATE_DEPLOYER_RATPANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_RATPANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_RATPANELSITE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_RATPANELSITE_ACCESSTOKENID="your-access-token-id" \ + --CERTIMATE_DEPLOYER_RATPANELSITE_ACCESSTOKEN="your-access-token" \ + --CERTIMATE_DEPLOYER_RATPANELSITE_SITENAME="your-site-name" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("ACCESSTOKENID: %v", fAccessTokenId), + fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken), + fmt.Sprintf("SITENAME: %v", fSiteName), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + AccessTokenId: int32(fAccessTokenId), + AccessToken: fAccessToken, + AllowInsecureConnections: true, + SiteName: fSiteName, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/safeline/safeline_test.go b/internal/pkg/core/deployer/providers/safeline/safeline_test.go index 294086c8..67fe6755 100644 --- a/internal/pkg/core/deployer/providers/safeline/safeline_test.go +++ b/internal/pkg/core/deployer/providers/safeline/safeline_test.go @@ -16,7 +16,7 @@ var ( fInputKeyPath string fApiUrl string fApiToken string - fCertificateId int + fCertificateId int64 ) func init() { @@ -26,7 +26,7 @@ func init() { flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") - flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") } /* diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index cf09214b..ae6e459f 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "log/slog" + "net" "os" "path/filepath" @@ -16,6 +17,23 @@ import ( certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" ) +type JumpServerConfig struct { + // SSH 主机。 + // 零值时默认为 "localhost"。 + SshHost string `json:"sshHost,omitempty"` + // SSH 端口。 + // 零值时默认为 22。 + SshPort int32 `json:"sshPort,omitempty"` + // SSH 登录用户名。 + SshUsername string `json:"sshUsername,omitempty"` + // SSH 登录密码。 + SshPassword string `json:"sshPassword,omitempty"` + // SSH 登录私钥。 + SshKey string `json:"sshKey,omitempty"` + // SSH 登录私钥口令。 + SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` +} + type DeployerConfig struct { // SSH 主机。 // 零值时默认为 "localhost"。 @@ -31,6 +49,8 @@ type DeployerConfig struct { SshKey string `json:"sshKey,omitempty"` // SSH 登录私钥口令。 SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` + // 跳板机配置数组。 + JumpServers []JumpServerConfig `json:"jumpServers,omitempty"` // 是否回退使用 SCP。 UseSCP bool `json:"useSCP,omitempty"` // 前置命令。 @@ -97,8 +117,61 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, fmt.Errorf("failed to extract certs: %w", err) } - // 连接 + var targetConn net.Conn + + // 连接到跳板机 + if len(d.config.JumpServers) > 0 { + var jumpClient *ssh.Client + for i, jumpServerConf := range d.config.JumpServers { + d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) + + var jumpConn net.Conn + // 第一个连接是主机发起,后续通过跳板机发起 + if jumpClient == nil { + jumpConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort)) + } else { + jumpConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort)) + } + if err != nil { + return nil, fmt.Errorf("failed to connect to jump server [%d]: %w", i+1, err) + } + defer jumpConn.Close() + + newClient, err := createSshClient( + jumpConn, + jumpServerConf.SshHost, + jumpServerConf.SshPort, + jumpServerConf.SshUsername, + jumpServerConf.SshPassword, + jumpServerConf.SshKey, + jumpServerConf.SshKeyPassphrase, + ) + if err != nil { + return nil, fmt.Errorf("failed to create jump server ssh client[%d]: %w", i+1, err) + } + defer newClient.Close() + + jumpClient = newClient + d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) + } + + // 通过跳板机发起 TCP 连接到目标服务器 + targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) + if err != nil { + return nil, fmt.Errorf("failed to connect to target server: %w", err) + } + } else { + // 直接发起 TCP 连接到目标服务器 + targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) + if err != nil { + return nil, fmt.Errorf("failed to connect to target server: %w", err) + } + } + defer targetConn.Close() + + // 通过已有的连接创建目标服务器 SSH 客户端 client, err := createSshClient( + targetConn, d.config.SshHost, d.config.SshPort, d.config.SshUsername, @@ -189,7 +262,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return &deployer.DeployResult{}, nil } -func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) { +func createSshClient(conn net.Conn, host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) { if host == "" { host = "localhost" } @@ -217,11 +290,16 @@ func createSshClient(host string, port int32, username string, password string, authMethod = ssh.Password(password) } - return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{ + sshConn, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{authMethod}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), }) + if err != nil { + return nil, err + } + + return ssh.NewClient(sshConn, chans, reqs), nil } func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) { diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index b63471d1..ae908185 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -15,7 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fSshHost string - fSshPort int + fSshPort int64 fSshUsername string fSshPassword string fOutputCertPath string @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fSshHost, argsPrefix+"SSHHOST", "", "") - flag.IntVar(&fSshPort, argsPrefix+"SSHPORT", 0, "") + flag.Int64Var(&fSshPort, argsPrefix+"SSHPORT", 0, "") flag.StringVar(&fSshUsername, argsPrefix+"SSHUSERNAME", "", "") flag.StringVar(&fSshPassword, argsPrefix+"SSHPASSWORD", "", "") flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "") 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 01f71d9e..a92e4eb1 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -2,9 +2,11 @@ package tencentcloudcdn import ( "context" + "errors" "fmt" "log/slog" "strings" + "time" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -132,6 +134,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } } return &deployer.DeployResult{}, nil 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 0c2f8902..2f7c0f22 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "time" tcclb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -151,6 +152,49 @@ func (d *DeployerProvider) deployViaSslService(ctx context.Context, cloudCertId return fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } + return nil } 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 0949c5a3..99ee9b2f 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "time" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -102,6 +103,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } + return &deployer.DeployResult{}, nil } 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 ebe87025..88840f4a 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -2,9 +2,11 @@ package tencentcloudecdn import ( "context" + "errors" "fmt" "log/slog" "strings" + "time" tccdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -103,7 +105,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } else { d.logger.Info("found ecdn instances to deploy", slog.Any("instanceIds", instanceIds)) - // 证书部署到 ECDN 实例 + // 证书部署到 CDN 实例 // REF: https://cloud.tencent.com/document/product/400/91667 deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest() deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) @@ -115,6 +117,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DeployCertificateInstance': %w", err) } + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailResp, err := d.sdkClients.SSL.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) + } + + var runningCount, succeededCount, failedCount, totalCount int64 + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("unexpected deployment job status") + } else { + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount + } + + if succeededCount+failedCount == totalCount { + break + } + } + + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) + time.Sleep(time.Second * 5) + } } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index 5f13660d..585000d9 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -116,30 +116,35 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) - describeHostDeployRecordDetailReq.Limit = common.Uint64Ptr(100) describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) d.logger.Debug("sdk request 'ssl.DescribeHostDeployRecordDetail'", slog.Any("request", describeHostDeployRecordDetailReq), slog.Any("response", describeHostDeployRecordDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail': %w", err) } + var runningCount, succeededCount, failedCount, totalCount int64 if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected deployment job status") } else { - acc := int64(0) + if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { + runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount + } if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - acc += *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount } if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - acc += *describeHostDeployRecordDetailResp.Response.FailedTotalCount + failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + if describeHostDeployRecordDetailResp.Response.TotalCount != nil { + totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount } - if acc == *describeHostDeployRecordDetailResp.Response.TotalCount { + if succeededCount+failedCount == totalCount { break } } - d.logger.Info("waiting for deployment job completion ...") + d.logger.Info(fmt.Sprintf("waiting for deployment job completion (running: %d, succeeded: %d, failed: %d, total: %d) ...", runningCount, succeededCount, failedCount, totalCount)) time.Sleep(time.Second * 5) } diff --git a/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go new file mode 100644 index 00000000..43c65de2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go @@ -0,0 +1,104 @@ +package wangsucdn + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strconv" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn" +) + +type DeployerConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名数组。 + Domains []string `json:"domains"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *wangsusdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到证书管理 + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 批量修改域名证书配置 + // REF: https://www.wangsu.com/document/api-doc/37447 + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + batchUpdateCertificateConfigReq := &wangsusdk.BatchUpdateCertificateConfigRequest{ + CertificateId: certId, + DomainNames: d.config.Domains, + } + batchUpdateCertificateConfigResp, err := d.sdkClient.BatchUpdateCertificateConfig(batchUpdateCertificateConfigReq) + d.logger.Debug("sdk request 'cdn.BatchUpdateCertificateConfig'", slog.Any("request", batchUpdateCertificateConfigReq), slog.Any("response", batchUpdateCertificateConfigResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.BatchUpdateCertificateConfig': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go new file mode 100644 index 00000000..99859b85 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go @@ -0,0 +1,75 @@ +package wangsucdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_WANGSUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_DOMAIN="example.com" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domains: []string{fDomain}, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index ee16b08a..4d5f2e10 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -17,7 +17,7 @@ import ( "time" "github.com/usual2970/certimate/internal/pkg/core/deployer" - wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn" + wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdnpro" certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" ) @@ -88,9 +88,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 查询已部署加速域名的详情 getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain) - d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp)) + d.logger.Debug("sdk request 'cdnpro.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetHostnameDetail': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetHostnameDetail': %w", err) } // 生成网宿云证书参数 @@ -126,9 +126,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE NewVersion: certificateNewVersionInfo, } createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateCertificate': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateCertificate': %w", err) } wangsuCertUrl = createCertificateResp.CertificateUrl @@ -149,9 +149,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE NewVersion: certificateNewVersionInfo, } updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateCertificate': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.UpdateCertificate': %w", err) } wangsuCertUrl = updateCertificateResp.CertificateUrl @@ -186,9 +186,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE createDeploymentTaskReq.Webhook = typeutil.ToPtr(d.config.WebhookId) } createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateDeploymentTask': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateDeploymentTask': %w", err) } // 循环获取部署任务详细信息,等待任务状态变更 @@ -206,9 +206,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId) - d.logger.Info("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp)) + d.logger.Info("sdk request 'cdnpro.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDeploymentTaskDetail': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetDeploymentTaskDetail': %w", err) } if getDeploymentTaskDetailResp.Status == "failed" { diff --git a/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go new file mode 100644 index 00000000..3f691489 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go @@ -0,0 +1,109 @@ +package wangsucertificate + +import ( + "context" + "errors" + "fmt" + "log/slog" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/certificate" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 证书 ID。 + // 选填。零值时表示新建证书;否则表示更新证书。 + CertificateId string `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *wangsusdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + if d.config.CertificateId == "" { + // 上传证书到证书管理 + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + } else { + // 修改证书 + // REF: https://www.wangsu.com/document/api-doc/25568?productCode=certificatemanagement + updateCertificateReq := &wangsusdk.UpdateCertificateRequest{ + Name: typeutil.ToPtr(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), + Certificate: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + Comment: typeutil.ToPtr("upload from certimate"), + } + updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq) + d.logger.Debug("sdk request 'certificatemanagement.UpdateCertificate'", slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err) + } + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go new file mode 100644 index 00000000..a6805ec9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go @@ -0,0 +1,75 @@ +package wangsucertificate_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fCertificateId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_certificate_test.go -args \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_CERTIFICATEID="your-certificate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 418b2c1a..49b07b47 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -159,9 +159,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 生成请求 // 其中 GET 请求需转换为查询参数 - req := d.httpClient.R(). - SetContext(ctx). - SetHeaderMultiValues(webhookHeaders) + req := d.httpClient.R().SetHeaderMultiValues(webhookHeaders) req.URL = webhookUrl.String() req.Method = webhookMethod if webhookMethod == http.MethodGet { @@ -178,7 +176,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } // 发送请求 - resp, err := req.SetDebug(true).Send() + resp, err := req.Send() if err != nil { return nil, fmt.Errorf("failed to send webhook request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/bark/bark.go b/internal/pkg/core/notifier/providers/bark/bark.go index ccdd5736..97ece0be 100644 --- a/internal/pkg/core/notifier/providers/bark/bark.go +++ b/internal/pkg/core/notifier/providers/bark/bark.go @@ -2,10 +2,10 @@ package bark import ( "context" + "fmt" "log/slog" - "github.com/nikoksr/notify" - "github.com/nikoksr/notify/service/bark" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -19,8 +19,9 @@ type NotifierConfig struct { } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -30,9 +31,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ - config: config, - logger: slog.Default(), + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -46,16 +50,25 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - var srv notify.Notifier - if n.config.ServerUrl == "" { - srv = bark.New(n.config.DeviceKey) - } else { - srv = bark.NewWithServers(n.config.DeviceKey, n.config.ServerUrl) + const defaultServerURL = "https://api.day.app/" + serverUrl := defaultServerURL + if n.config.ServerUrl != "" { + serverUrl = n.config.ServerUrl } - err = srv.Send(ctx, subject, message) + // REF: https://bark.day.app/#/tutorial + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "title": subject, + "body": message, + "device_key": n.config.DeviceKey, + }) + resp, err := req.Post(serverUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("bark api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("bark api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go b/internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go similarity index 67% rename from internal/pkg/core/notifier/providers/dingtalk/dingtalk.go rename to internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go index 9eb94dcf..d6d8b096 100644 --- a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go +++ b/internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go @@ -1,4 +1,4 @@ -package dingtalk +package dingtalkbot import ( "context" @@ -6,7 +6,7 @@ import ( "log/slog" "net/url" - "github.com/nikoksr/notify/service/dingding" + "github.com/blinkbean/dingtalk" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -48,17 +48,18 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { webhookUrl, err := url.Parse(n.config.WebhookUrl) if err != nil { - return nil, fmt.Errorf("invalid webhook url: %w", err) + return nil, fmt.Errorf("dingtalk api error: invalid webhook url: %w", err) } - srv := dingding.New(&dingding.Config{ - Token: webhookUrl.Query().Get("access_token"), - Secret: n.config.Secret, - }) + var bot *dingtalk.DingTalk + if n.config.Secret == "" { + bot = dingtalk.InitDingTalk([]string{webhookUrl.Query().Get("access_token")}, "") + } else { + bot = dingtalk.InitDingTalkWithSecret(webhookUrl.Query().Get("access_token"), n.config.Secret) + } - err = srv.Send(ctx, subject, message) - if err != nil { - return nil, err + if err := bot.SendTextMessage(subject + "\n" + message); err != nil { + return nil, fmt.Errorf("dingtalk api error: %w", err) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go b/internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot_test.go similarity index 62% rename from internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go rename to internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot_test.go index 086e3a94..de3b6ba0 100644 --- a/internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go +++ b/internal/pkg/core/notifier/providers/dingtalkbot/dingtalkbot_test.go @@ -1,4 +1,4 @@ -package dingtalk_test +package dingtalkbot_test import ( "context" @@ -7,7 +7,7 @@ import ( "strings" "testing" - provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot" ) const ( @@ -16,22 +16,23 @@ const ( ) var ( - fAccessToken string - fSecret string + fWebhookUrl string + fSecret string ) func init() { - argsPrefix := "CERTIMATE_NOTIFIER_DINGTALK_" + argsPrefix := "CERTIMATE_NOTIFIER_DINGTALKBOT_" - flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "") + flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "") flag.StringVar(&fSecret, argsPrefix+"SECRET", "", "") } /* Shell command to run this test: - go test -v ./dingtalk_test.go -args \ - --CERTIMATE_NOTIFIER_DINGTALK_URL="https://example.com/your-webhook-url" + go test -v ./dingtalkbot_test.go -args \ + --CERTIMATE_NOTIFIER_DINGTALKBOT_WEBHOOKURL="https://example.com/your-webhook-url" \ + --CERTIMATE_NOTIFIER_DINGTALKBOT_SECRET="your-secret" */ func TestNotify(t *testing.T) { flag.Parse() @@ -39,13 +40,13 @@ func TestNotify(t *testing.T) { t.Run("Notify", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", - fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken), + fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl), fmt.Sprintf("SECRET: %v", fSecret), }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - AccessToken: fAccessToken, - Secret: fSecret, + WebhookUrl: fWebhookUrl, + Secret: fSecret, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 30bfba07..cf0669ca 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -17,7 +17,7 @@ const ( var ( fSmtpHost string - fSmtpPort int + fSmtpPort int64 fSmtpTLS bool fUsername string fPassword string @@ -29,7 +29,7 @@ func init() { argsPrefix := "CERTIMATE_NOTIFIER_EMAIL_" flag.StringVar(&fSmtpHost, argsPrefix+"SMTPHOST", "", "") - flag.IntVar(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "") + flag.Int64Var(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "") flag.BoolVar(&fSmtpTLS, argsPrefix+"SMTPTLS", false, "") flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") diff --git a/internal/pkg/core/notifier/providers/gotify/gotify.go b/internal/pkg/core/notifier/providers/gotify/gotify.go index aed6e7c8..81dcb8ad 100644 --- a/internal/pkg/core/notifier/providers/gotify/gotify.go +++ b/internal/pkg/core/notifier/providers/gotify/gotify.go @@ -1,20 +1,19 @@ package gotify import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "log/slog" - "net/http" + "strings" + + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { // Gotify 服务地址。 - Url string `json:"url"` + ServerUrl string `json:"serverUrl"` // Gotify Token。 Token string `json:"token"` // Gotify 消息优先级。 @@ -24,7 +23,7 @@ type NotifierConfig struct { type NotifierProvider struct { config *NotifierConfig logger *slog.Logger - httpClient *http.Client + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -34,10 +33,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ config: config, logger: slog.Default(), - httpClient: http.DefaultClient, + httpClient: client, }, nil } @@ -51,45 +52,22 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - reqBody := &struct { - Title string `json:"title"` - Message string `json:"message"` - Priority int64 `json:"priority"` - }{ - Title: subject, - Message: message, - Priority: n.config.Priority, - } + serverUrl := strings.TrimRight(n.config.ServerUrl, "/") - body, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("gotify api error: failed to encode message body: %w", err) - } - - req, err := http.NewRequestWithContext( - ctx, - http.MethodPost, - fmt.Sprintf("%s/message", n.config.Url), - bytes.NewReader(body), - ) - if err != nil { - return nil, fmt.Errorf("gotify api error: failed to create new request: %w", err) - } - - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.config.Token)) - req.Header.Set("Content-Type", "application/json; charset=utf-8") - - resp, err := n.httpClient.Do(req) + // REF: https://gotify.net/api-docs#/message/createMessage + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bearer "+n.config.Token). + SetBody(map[string]any{ + "title": subject, + "message": message, + "priority": n.config.Priority, + }) + resp, err := req.Post(fmt.Sprintf("%s/message", serverUrl)) if err != nil { return nil, fmt.Errorf("gotify api error: failed to send request: %w", err) - } - defer resp.Body.Close() - - result, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("gotify api error: failed to read response: %w", err) - } else if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("gotify api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) + } else if resp.IsError() { + return nil, fmt.Errorf("gotify api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/gotify/gotify_test.go b/internal/pkg/core/notifier/providers/gotify/gotify_test.go index 31ad64af..eb0ffd6b 100644 --- a/internal/pkg/core/notifier/providers/gotify/gotify_test.go +++ b/internal/pkg/core/notifier/providers/gotify/gotify_test.go @@ -48,9 +48,9 @@ func TestNotify(t *testing.T) { }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - Url: fUrl, - Token: fToken, - Priority: fPriority, + ServerUrl: fUrl, + Token: fToken, + Priority: fPriority, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/notifier/providers/lark/lark.go b/internal/pkg/core/notifier/providers/larkbot/larkbot.go similarity index 65% rename from internal/pkg/core/notifier/providers/lark/lark.go rename to internal/pkg/core/notifier/providers/larkbot/larkbot.go index e8ad7816..7d3e8a55 100644 --- a/internal/pkg/core/notifier/providers/lark/lark.go +++ b/internal/pkg/core/notifier/providers/larkbot/larkbot.go @@ -1,10 +1,11 @@ -package lark +package larkbot import ( "context" + "fmt" "log/slog" - "github.com/nikoksr/notify/service/lark" + "github.com/go-lark/lark" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -42,11 +43,17 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := lark.NewWebhookService(n.config.WebhookUrl) - - err = srv.Send(ctx, subject, message) + bot := lark.NewNotificationBot(n.config.WebhookUrl) + content := lark.NewPostBuilder(). + Title(subject). + TextTag(message, 1, false). + Render() + msg := lark.NewMsgBuffer(lark.MsgPost).Post(content) + resp, err := bot.PostNotificationV2(msg.Build()) if err != nil { - return nil, err + return nil, fmt.Errorf("lark api error: %w", err) + } else if resp.Code != 0 { + return nil, fmt.Errorf("lark api error: code='%d', message='%s'", resp.Code, resp.Msg) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/wecom/wecom_test.go b/internal/pkg/core/notifier/providers/larkbot/larkbot_test.go similarity index 80% rename from internal/pkg/core/notifier/providers/wecom/wecom_test.go rename to internal/pkg/core/notifier/providers/larkbot/larkbot_test.go index 01646121..2deba768 100644 --- a/internal/pkg/core/notifier/providers/wecom/wecom_test.go +++ b/internal/pkg/core/notifier/providers/larkbot/larkbot_test.go @@ -1,4 +1,4 @@ -package serverchan_test +package larkbot_test import ( "context" @@ -7,7 +7,7 @@ import ( "strings" "testing" - provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot" ) const ( @@ -18,7 +18,7 @@ const ( var fWebhookUrl string func init() { - argsPrefix := "CERTIMATE_NOTIFIER_WECOM_" + argsPrefix := "CERTIMATE_NOTIFIER_LARKBOT_" flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "") } @@ -26,8 +26,8 @@ func init() { /* Shell command to run this test: - go test -v ./wecom_test.go -args \ - --CERTIMATE_NOTIFIER_WECOM_WEBHOOKURL="https://example.com/your-webhook-url" \ + go test -v ./larkbot_test.go -args \ + --CERTIMATE_NOTIFIER_LARKBOT_WEBHOOKURL="https://example.com/your-webhook-url" */ func TestNotify(t *testing.T) { flag.Parse() diff --git a/internal/pkg/core/notifier/providers/mattermost/mattermost.go b/internal/pkg/core/notifier/providers/mattermost/mattermost.go index ed3a507a..a9b2f4d6 100644 --- a/internal/pkg/core/notifier/providers/mattermost/mattermost.go +++ b/internal/pkg/core/notifier/providers/mattermost/mattermost.go @@ -1,15 +1,13 @@ package mattermost import ( - "bytes" "context" - "encoding/json" - "io" + "fmt" "log/slog" - "net/http" "strings" - "github.com/nikoksr/notify/service/mattermost" + "github.com/go-resty/resty/v2" + "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -25,8 +23,9 @@ type NotifierConfig struct { } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -36,9 +35,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ - config: config, - logger: slog.Default(), + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -52,17 +54,29 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := mattermost.New(strings.TrimRight(n.config.ServerUrl, "/")) + serverUrl := strings.TrimRight(n.config.ServerUrl, "/") - if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil { - return nil, err + // REF: https://developers.mattermost.com/api-documentation/#/operations/Login + loginReq := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "login_id": n.config.Username, + "password": n.config.Password, + }) + loginResp, err := loginReq.Post(fmt.Sprintf("%s/api/v4/users/login", serverUrl)) + if err != nil { + return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err) + } else if loginResp.IsError() { + return nil, fmt.Errorf("mattermost api error: unexpected status code: %d, resp: %s", loginResp.StatusCode(), loginResp.String()) + } else if loginResp.Header().Get("Token") == "" { + return nil, fmt.Errorf("mattermost api error: received empty login token") } - srv.AddReceivers(n.config.ChannelId) - - // 复写消息样式 - srv.PreSend(func(req *http.Request) error { - m := map[string]interface{}{ + // REF: https://developers.mattermost.com/api-documentation/#/operations/CreatePost + postReq := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bearer "+loginResp.Header().Get("Token")). + SetBody(map[string]any{ "channel_id": n.config.ChannelId, "props": map[string]interface{}{ "attachments": []map[string]interface{}{ @@ -72,20 +86,12 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s }, }, }, - } - - if body, err := json.Marshal(m); err != nil { - return err - } else { - req.ContentLength = int64(len(body)) - req.Body = io.NopCloser(bytes.NewReader(body)) - } - - return nil - }) - - if err = srv.Send(ctx, subject, message); err != nil { - return nil, err + }) + postResp, err := postReq.Post(fmt.Sprintf("%s/api/v4/posts", serverUrl)) + if err != nil { + return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err) + } else if postResp.IsError() { + return nil, fmt.Errorf("mattermost api error: unexpected status code: %d, resp: %s", postResp.StatusCode(), postResp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/pushover/pushover.go b/internal/pkg/core/notifier/providers/pushover/pushover.go index f306df1f..b7f74bba 100644 --- a/internal/pkg/core/notifier/providers/pushover/pushover.go +++ b/internal/pkg/core/notifier/providers/pushover/pushover.go @@ -1,13 +1,11 @@ package pushover import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "log/slog" - "net/http" + + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -22,7 +20,7 @@ type NotifierConfig struct { type NotifierProvider struct { config *NotifierConfig logger *slog.Logger - httpClient *http.Client + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -32,10 +30,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ config: config, logger: slog.Default(), - httpClient: http.DefaultClient, + httpClient: client, }, nil } @@ -50,46 +50,19 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { // REF: https://pushover.net/api - reqBody := &struct { - Token string `json:"token"` - User string `json:"user"` - Title string `json:"title"` - Message string `json:"message"` - }{ - Token: n.config.Token, - User: n.config.User, - Title: subject, - Message: message, - } - - body, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("pushover api error: failed to encode message body: %w", err) - } - - req, err := http.NewRequestWithContext( - ctx, - http.MethodPost, - "https://api.pushover.net/1/messages.json", - bytes.NewReader(body), - ) - if err != nil { - return nil, fmt.Errorf("pushover api error: failed to create new request: %w", err) - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - - resp, err := n.httpClient.Do(req) + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "title": subject, + "message": message, + "token": n.config.Token, + "user": n.config.User, + }) + resp, err := req.Post("https://api.pushover.net/1/messages.json") if err != nil { return nil, fmt.Errorf("pushover api error: failed to send request: %w", err) - } - defer resp.Body.Close() - - result, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("pushover api error: failed to read response: %w", err) - } else if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("pushover api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) + } else if resp.IsError() { + return nil, fmt.Errorf("pushover api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/pushplus/pushplus.go b/internal/pkg/core/notifier/providers/pushplus/pushplus.go index a0ef4c7f..834f9683 100644 --- a/internal/pkg/core/notifier/providers/pushplus/pushplus.go +++ b/internal/pkg/core/notifier/providers/pushplus/pushplus.go @@ -1,13 +1,12 @@ package pushplus import ( - "bytes" "context" "encoding/json" "fmt" - "io" "log/slog" - "net/http" + + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -20,7 +19,7 @@ type NotifierConfig struct { type NotifierProvider struct { config *NotifierConfig logger *slog.Logger - httpClient *http.Client + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -30,10 +29,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ config: config, logger: slog.Default(), - httpClient: http.DefaultClient, + httpClient: client, }, nil } @@ -47,55 +48,29 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - // REF: https://pushplus.plus/doc/guide/api.html - reqBody := &struct { - Token string `json:"token"` - Title string `json:"title"` - Content string `json:"content"` - }{ - Token: n.config.Token, - Title: subject, - Content: message, - } - - body, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("pushplus api error: failed to encode message body: %w", err) - } - - req, err := http.NewRequestWithContext( - ctx, - http.MethodPost, - "https://www.pushplus.plus/send", - bytes.NewReader(body), - ) - if err != nil { - return nil, fmt.Errorf("pushplus api error: failed to create new request: %w", err) - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - - resp, err := n.httpClient.Do(req) + // REF: https://pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3 + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "title": subject, + "content": message, + "token": n.config.Token, + }) + resp, err := req.Post("https://www.pushplus.plus/send") if err != nil { return nil, fmt.Errorf("pushplus api error: failed to send request: %w", err) - } - defer resp.Body.Close() - - result, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("pushplus api error: failed to read response: %w", err) - } else if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("pushplus api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) + } else if resp.IsError() { + return nil, fmt.Errorf("pushplus api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } var errorResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` + Code int `json:"code"` + Message string `json:"msg"` } - if err := json.Unmarshal(result, &errorResponse); err != nil { - return nil, fmt.Errorf("pushplus api error: failed to decode response: %w", err) + if err := json.Unmarshal(resp.Body(), &errorResponse); err != nil { + return nil, fmt.Errorf("pushplus api error: failed to unmarshal response: %w", err) } else if errorResponse.Code != 200 { - return nil, fmt.Errorf("pushplus api error: unexpected response code: %d, msg: %s", errorResponse.Code, errorResponse.Msg) + return nil, fmt.Errorf("pushplus api error: code='%d', message='%s'", errorResponse.Code, errorResponse.Message) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan.go b/internal/pkg/core/notifier/providers/serverchan/serverchan.go index 89724b08..d74b2fcc 100644 --- a/internal/pkg/core/notifier/providers/serverchan/serverchan.go +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan.go @@ -2,22 +2,23 @@ package serverchan import ( "context" + "fmt" "log/slog" - "net/http" - notifyHttp "github.com/nikoksr/notify/service/http" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { // ServerChan 服务地址。 - Url string `json:"url"` + ServerUrl string `json:"serverUrl"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -27,9 +28,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ - config: config, - logger: slog.Default(), + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -43,24 +47,18 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := notifyHttp.New() - - srv.AddReceivers(¬ifyHttp.Webhook{ - URL: n.config.Url, - Header: http.Header{}, - ContentType: "application/json", - Method: http.MethodPost, - BuildPayload: func(subject, message string) (payload any) { - return map[string]string{ - "text": subject, - "desp": message, - } - }, - }) - - err = srv.Send(ctx, subject, message) + // REF: https://sct.ftqq.com/ + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "text": subject, + "desp": message, + }) + resp, err := req.Post(n.config.ServerUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("serverchan api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("serverchan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go b/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go index 991b4050..5684a593 100644 --- a/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go @@ -39,7 +39,7 @@ func TestNotify(t *testing.T) { }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - Url: fUrl, + ServerUrl: fUrl, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/notifier/providers/telegram/telegram.go b/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go similarity index 51% rename from internal/pkg/core/notifier/providers/telegram/telegram.go rename to internal/pkg/core/notifier/providers/telegrambot/telegrambot.go index 218f7ee3..99b86a38 100644 --- a/internal/pkg/core/notifier/providers/telegram/telegram.go +++ b/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go @@ -1,10 +1,11 @@ -package telegram +package telegrambot import ( "context" + "fmt" "log/slog" - "github.com/nikoksr/notify/service/telegram" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -17,8 +18,9 @@ type NotifierConfig struct { } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -28,9 +30,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ - config: config, - logger: slog.Default(), + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -44,16 +49,18 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv, err := telegram.New(n.config.BotToken) + // REF: https://core.telegram.org/bots/api#sendmessage + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "chat_id": n.config.ChatId, + "text": subject + "\n" + message, + }) + resp, err := req.Post(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", n.config.BotToken)) if err != nil { - return nil, err - } - - srv.AddReceivers(n.config.ChatId) - - err = srv.Send(ctx, subject, message) - if err != nil { - return nil, err + return nil, fmt.Errorf("telegram api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("telegram api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/telegram/telegram_test.go b/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go similarity index 79% rename from internal/pkg/core/notifier/providers/telegram/telegram_test.go rename to internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go index e9a7d10b..3a207384 100644 --- a/internal/pkg/core/notifier/providers/telegram/telegram_test.go +++ b/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go @@ -1,4 +1,4 @@ -package telegram_test +package telegrambot_test import ( "context" @@ -7,7 +7,7 @@ import ( "strings" "testing" - provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot" ) const ( @@ -21,7 +21,7 @@ var ( ) func init() { - argsPrefix := "CERTIMATE_NOTIFIER_TELEGRAM_" + argsPrefix := "CERTIMATE_NOTIFIER_TELEGRAMBOT_" flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") flag.Int64Var(&fChartId, argsPrefix+"CHATID", 0, "") @@ -30,9 +30,9 @@ func init() { /* Shell command to run this test: - go test -v ./telegram_test.go -args \ - --CERTIMATE_NOTIFIER_TELEGRAM_APITOKEN="your-api-token" \ - --CERTIMATE_NOTIFIER_TELEGRAM_CHATID=123456 + go test -v ./telegrambot_test.go -args \ + --CERTIMATE_NOTIFIER_TELEGRAMBOT_APITOKEN="your-api-token" \ + --CERTIMATE_NOTIFIER_TELEGRAMBOT_CHATID=123456 */ func TestNotify(t *testing.T) { flag.Parse() diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go index 0e7caaa5..5f62f170 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -139,9 +139,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s // 生成请求 // 其中 GET 请求需转换为查询参数 - req := n.httpClient.R(). - SetContext(ctx). - SetHeaderMultiValues(webhookHeaders) + req := n.httpClient.R().SetHeaderMultiValues(webhookHeaders) req.URL = webhookUrl.String() req.Method = webhookMethod if webhookMethod == http.MethodGet { @@ -160,12 +158,12 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s // 发送请求 resp, err := req.Send() if err != nil { - return nil, fmt.Errorf("failed to send webhook request: %w", err) + return nil, fmt.Errorf("webhook error: failed to send request: %w", err) } else if resp.IsError() { - return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode()) + return nil, fmt.Errorf("webhook error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } - n.logger.Debug("webhook responded", slog.Any("response", resp.String())) + n.logger.Debug("webhook responded", slog.String("response", resp.String())) return ¬ifier.NotifyResult{}, nil } diff --git a/internal/pkg/core/notifier/providers/wecom/wecom.go b/internal/pkg/core/notifier/providers/wecombot/wecombot.go similarity index 51% rename from internal/pkg/core/notifier/providers/wecom/wecom.go rename to internal/pkg/core/notifier/providers/wecombot/wecombot.go index 413f0d8d..36c179d4 100644 --- a/internal/pkg/core/notifier/providers/wecom/wecom.go +++ b/internal/pkg/core/notifier/providers/wecombot/wecombot.go @@ -1,11 +1,11 @@ -package serverchan +package wecombot import ( "context" + "fmt" "log/slog" - "net/http" - notifyHttp "github.com/nikoksr/notify/service/http" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -16,8 +16,9 @@ type NotifierConfig struct { } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -27,8 +28,12 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New() + return &NotifierProvider{ - config: config, + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -42,26 +47,20 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := notifyHttp.New() - - srv.AddReceivers(¬ifyHttp.Webhook{ - URL: n.config.WebhookUrl, - Header: http.Header{}, - ContentType: "application/json", - Method: http.MethodPost, - BuildPayload: func(subject, message string) (payload any) { - return map[string]any{ - "msgtype": "text", - "text": map[string]string{ - "content": subject + "\n\n" + message, - }, - } - }, - }) - - err = srv.Send(ctx, subject, message) + // REF: https://developer.work.weixin.qq.com/document/path/91770 + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]any{ + "msgtype": "text", + "text": map[string]string{ + "content": subject + "\n\n" + message, + }, + }) + resp, err := req.Post(n.config.WebhookUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("wecom api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("wecom api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/lark/lark_test.go b/internal/pkg/core/notifier/providers/wecombot/wecombot_test.go similarity index 80% rename from internal/pkg/core/notifier/providers/lark/lark_test.go rename to internal/pkg/core/notifier/providers/wecombot/wecombot_test.go index f72ca443..261f2158 100644 --- a/internal/pkg/core/notifier/providers/lark/lark_test.go +++ b/internal/pkg/core/notifier/providers/wecombot/wecombot_test.go @@ -1,4 +1,4 @@ -package lark_test +package wecombot_test import ( "context" @@ -7,7 +7,7 @@ import ( "strings" "testing" - provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot" ) const ( @@ -18,7 +18,7 @@ const ( var fWebhookUrl string func init() { - argsPrefix := "CERTIMATE_NOTIFIER_LARK_" + argsPrefix := "CERTIMATE_NOTIFIER_WECOMBOT_" flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "") } @@ -26,8 +26,8 @@ func init() { /* Shell command to run this test: - go test -v ./lark_test.go -args \ - --CERTIMATE_NOTIFIER_LARK_WEBHOOKURL="https://example.com/your-webhook-url" + go test -v ./wecombot_test.go -args \ + --CERTIMATE_NOTIFIER_WECOMBOT_WEBHOOKURL="https://example.com/your-webhook-url" \ */ func TestNotify(t *testing.T) { flag.Parse() diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go index e5a0b0ba..63900125 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -10,12 +10,14 @@ import ( "time" "github.com/usual2970/certimate/internal/pkg/core/uploader" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type UploaderConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` } @@ -23,7 +25,7 @@ type UploaderConfig struct { type UploaderProvider struct { config *UploaderConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client } var _ uploader.Uploader = (*UploaderProvider)(nil) @@ -33,7 +35,7 @@ func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } @@ -67,7 +69,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) // 上传证书 - uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{ Type: "paste", Description: certName, Certificate: certPEM, @@ -99,7 +101,7 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, default: } - searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{ + searchWebsiteSSLReq := &onepanelsdk.SearchWebsiteSSLRequest{ Page: searchWebsiteSSLPageNumber, PageSize: searchWebsiteSSLPageSize, } @@ -130,15 +132,19 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, return nil, nil } -func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) return client, nil } diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go index 257030f5..cfb250be 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go @@ -16,6 +16,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string ) @@ -25,6 +26,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") } @@ -35,6 +37,7 @@ Shell command to run this test: --CERTIMATE_UPLOADER_1PANELSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_UPLOADER_1PANELSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_UPLOADER_1PANELSSL_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_UPLOADER_1PANELSSL_APIVERSION="v1" \ --CERTIMATE_UPLOADER_1PANELSSL_APIKEY="your-api-key" */ func TestDeploy(t *testing.T) { @@ -46,12 +49,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), }, "\n")) uploader, err := provider.NewUploader(&provider.UploaderConfig{ - ApiUrl: fApiUrl, - ApiKey: fApiKey, + ApiUrl: fApiUrl, + ApiVersion: fApiVersion, + ApiKey: fApiKey, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go new file mode 100644 index 00000000..b512be09 --- /dev/null +++ b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go @@ -0,0 +1,143 @@ +package jdcloudssl + +import ( + "context" + "errors" + "fmt" + "log/slog" + "regexp" + "strings" + "time" + + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/certificate" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type UploaderConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *wangsusdk.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &UploaderProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader { + if logger == nil { + u.logger = slog.Default() + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (res *uploader.UploadResult, err error) { + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + // 查询证书列表,避免重复上传 + // REF: https://www.wangsu.com/document/api-doc/26426 + listCertificatesResp, err := u.sdkClient.ListCertificates() + u.logger.Debug("sdk request 'certificatemanagement.ListCertificates'", slog.Any("response", listCertificatesResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.ListCertificates': %w", err) + } + + if listCertificatesResp.Certificates != nil { + for _, certificate := range listCertificatesResp.Certificates { + // 对比证书序列号 + if !strings.EqualFold(certX509.SerialNumber.Text(16), certificate.Serial) { + continue + } + + // 再对比证书有效期 + cstzone := time.FixedZone("CST", 8*60*60) + oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certificate.ValidityFrom, cstzone) + oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certificate.ValidityTo, cstzone) + if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + u.logger.Info("ssl certificate already exists") + return &uploader.UploadResult{ + CertId: certificate.CertificateId, + CertName: certificate.Name, + }, nil + } + } + + // 生成新证书名(需符合网宿云命名规则) + var certId string + certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli()) + + // 新增证书 + // REF: https://www.wangsu.com/document/api-doc/25199?productCode=certificatemanagement + createCertificateReq := &wangsusdk.CreateCertificateRequest{ + Name: typeutil.ToPtr(certName), + Certificate: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + Comment: typeutil.ToPtr("upload from certimate"), + } + createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq) + u.logger.Debug("sdk request 'certificatemanagement.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err) + } + + // 网宿云证书 URL 中包含证书 ID + // 格式: + // https://open.chinanetcenter.com/api/certificate/100001 + wangsuCertIdMatches := regexp.MustCompile(`/certificate/([0-9]+)`).FindStringSubmatch(createCertificateResp.CertificateUrl) + if len(wangsuCertIdMatches) > 1 { + certId = wangsuCertIdMatches[1] + } else { + return nil, fmt.Errorf("received empty certificate id") + } + + return &uploader.UploadResult{ + CertId: certId, + CertName: certName, + }, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go new file mode 100644 index 00000000..bdec8cfe --- /dev/null +++ b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go @@ -0,0 +1,72 @@ +package jdcloudssl_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_JDCLOUDSSL_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_certificate_test.go -args \ + --CERTIMATE_UPLOADER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYSECRET="your-access-key-secret" +*/ +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), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + sres, _ := json.Marshal(res) + t.Logf("ok: %s", string(sres)) + }) +} diff --git a/internal/pkg/sdk3rd/1panel/client.go b/internal/pkg/sdk3rd/1panel/client.go index 0afbe2a5..003203d3 100644 --- a/internal/pkg/sdk3rd/1panel/client.go +++ b/internal/pkg/sdk3rd/1panel/client.go @@ -14,19 +14,30 @@ import ( ) type Client struct { - apiHost string - apiKey string + apiKey string client *resty.Client } -func NewClient(apiHost, apiKey string) *Client { - client := resty.New() +func NewClient(apiHost, apiVersion, apiKey string) *Client { + if apiVersion == "" { + apiVersion = "v1" + } + + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/api/" + apiVersion). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + tokenMd5 := md5.Sum([]byte("1panel" + apiKey + timestamp)) + tokenMd5Hex := hex.EncodeToString(tokenMd5[:]) + req.Header.Set("1Panel-Timestamp", timestamp) + req.Header.Set("1Panel-Token", tokenMd5Hex) + + return nil + }) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - client: client, + client: client, } } @@ -40,16 +51,8 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { return c } -func (c *Client) generateToken(timestamp string) string { - tokenMd5 := md5.Sum([]byte("1panel" + c.apiKey + timestamp)) - tokenMd5Hex := hex.EncodeToString(tokenMd5[:]) - return tokenMd5Hex -} - func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = c.apiHost + "/api/v1" + path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -65,21 +68,14 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - timestamp := fmt.Sprintf("%d", time.Now().Unix()) - token := c.generateToken(timestamp) - req.SetHeader("1Panel-Timestamp", timestamp) - req.SetHeader("1Panel-Token", token) - - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("1panel api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("1panel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("1panel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -95,7 +91,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("1panel api error: failed to parse response: %w", err) + return fmt.Errorf("1panel api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode/100 != 2 { return fmt.Errorf("1panel api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/baishan/client.go b/internal/pkg/sdk3rd/baishan/client.go index 408bcbc9..b3e428ee 100644 --- a/internal/pkg/sdk3rd/baishan/client.go +++ b/internal/pkg/sdk3rd/baishan/client.go @@ -13,17 +13,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://cdn.api.baishan.com"). + SetHeader("token", apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -34,8 +33,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://cdn.api.baishan.com" + path if strings.EqualFold(method, http.MethodGet) { qs := url.Values{} if params != nil { @@ -61,21 +58,16 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetQueryParam("token", c.apiToken). - SetQueryParamsFromValues(qs) + req = req.SetQueryParamsFromValues(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetQueryParam("token", c.apiToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("baishan api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("baishan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("baishan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -91,7 +83,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("baishan api error: failed to parse response: %w", err) + return fmt.Errorf("baishan api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode != 0 { return fmt.Errorf("baishan api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/btpanel/client.go b/internal/pkg/sdk3rd/btpanel/client.go index 781e9a75..1da625da 100644 --- a/internal/pkg/sdk3rd/btpanel/client.go +++ b/internal/pkg/sdk3rd/btpanel/client.go @@ -14,19 +14,18 @@ import ( ) type Client struct { - apiHost string - apiKey string + apiKey string client *resty.Client } func NewClient(apiHost, apiKey string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - client: client, + apiKey: apiKey, + client: client, } } @@ -78,15 +77,14 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, data["request_time"] = fmt.Sprintf("%d", timestamp) data["request_token"] = c.generateSignature(fmt.Sprintf("%d", timestamp)) - url := c.apiHost + path req := c.client.R(). SetHeader("Content-Type", "application/x-www-form-urlencoded"). SetFormData(data) - resp, err := req.Post(url) + resp, err := req.Post(path) if err != nil { return resp, fmt.Errorf("baota api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -99,7 +97,7 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("baota api error: failed to parse response: %w", err) + return fmt.Errorf("baota api error: failed to unmarshal response: %w", err) } else if errstatus := result.GetStatus(); errstatus != nil && !*errstatus { if result.GetMessage() == nil { return fmt.Errorf("baota api error: unknown error") diff --git a/internal/pkg/sdk3rd/btwaf/api.go b/internal/pkg/sdk3rd/btwaf/api.go new file mode 100644 index 00000000..bc35dee5 --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/api.go @@ -0,0 +1,19 @@ +package btwaf + +func (c *Client) GetSiteList(req *GetSiteListRequest) (*GetSiteListResponse, error) { + resp := &GetSiteListResponse{} + err := c.sendRequestWithResult("/wafmastersite/get_site_list", req, resp) + return resp, err +} + +func (c *Client) ModifySite(req *ModifySiteRequest) (*ModifySiteResponse, error) { + resp := &ModifySiteResponse{} + err := c.sendRequestWithResult("/wafmastersite/modify_site", req, resp) + return resp, err +} + +func (c *Client) ConfigSetSSL(req *ConfigSetSSLRequest) (*ConfigSetSSLResponse, error) { + resp := &ConfigSetSSLResponse{} + err := c.sendRequestWithResult("/config/set_cert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/btwaf/client.go b/internal/pkg/sdk3rd/btwaf/client.go new file mode 100644 index 00000000..5ae545cc --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/client.go @@ -0,0 +1,77 @@ +package btwaf + +import ( + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + client *resty.Client +} + +func NewClient(apiHost, apiKey string) *Client { + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + keyMd5 := md5.Sum([]byte(apiKey)) + keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:])) + signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex)) + signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:])) + req.Header.Set("waf_request_time", timestamp) + req.Header.Set("waf_request_token", signMd5Hex) + + return nil + }) + + return &Client{ + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) { + req := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(params) + resp, err := req.Post(path) + if err != nil { + return resp, fmt.Errorf("baota api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(path, params) + if err != nil { + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("baota api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 0 { + return fmt.Errorf("baota api error: code='%d'", errcode) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/btwaf/models.go b/internal/pkg/sdk3rd/btwaf/models.go new file mode 100644 index 00000000..16290e88 --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/models.go @@ -0,0 +1,67 @@ +package btwaf + +type BaseResponse interface { + GetCode() int32 +} + +type baseResponse struct { + Code *int32 `json:"code,omitempty"` +} + +func (r *baseResponse) GetCode() int32 { + if r.Code != nil { + return *r.Code + } + return 0 +} + +type GetSiteListRequest struct { + Page *int32 `json:"p,omitempty"` + PageSize *int32 `json:"p_size,omitempty"` + SiteName *string `json:"site_name,omitempty"` +} + +type GetSiteListResponse struct { + baseResponse + Result *struct { + List []*struct { + SiteId string `json:"site_id"` + SiteName string `json:"site_name"` + Type string `json:"types"` + Status int32 `json:"status"` + CreateTime int64 `json:"create_time"` + UpdateTime int64 `json:"update_time"` + } `json:"list"` + Total int32 `json:"total"` + } `json:"res,omitempty"` +} + +type SiteServerInfo struct { + ListenSSLPort *int32 `json:"listen_ssl_port,omitempty"` + SSL *SiteServerSSLInfo `json:"ssl,omitempty"` +} + +type SiteServerSSLInfo struct { + IsSSL *int32 `json:"is_ssl,omitempty"` + FullChain *string `json:"full_chain,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` +} + +type ModifySiteRequest struct { + SiteId string `json:"site_id"` + Type *string `json:"types,omitempty"` + Server *SiteServerInfo `json:"server,omitempty"` +} + +type ModifySiteResponse struct { + baseResponse +} + +type ConfigSetSSLRequest struct { + CertContent string `json:"certContent"` + KeyContent string `json:"keyContent"` +} + +type ConfigSetSSLResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/bunny/client.go b/internal/pkg/sdk3rd/bunny/client.go index 2b1ed4d1..8d50e1fc 100644 --- a/internal/pkg/sdk3rd/bunny/client.go +++ b/internal/pkg/sdk3rd/bunny/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.bunny.net"). + SetHeader("AccessKey", apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -32,9 +31,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://api.bunny.net" + path - req = req.SetHeader("AccessKey", c.apiToken) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -50,16 +46,14 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("bunny api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("bunny api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("bunny api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil diff --git a/internal/pkg/sdk3rd/cachefly/client.go b/internal/pkg/sdk3rd/cachefly/client.go index b1777ea9..342e329d 100644 --- a/internal/pkg/sdk3rd/cachefly/client.go +++ b/internal/pkg/sdk3rd/cachefly/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.cachefly.com/api/2.5"). + SetHeader("x-cf-authorization", "Bearer "+apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -32,9 +31,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://api.cachefly.com/api/2.5" + path - req = req.SetHeader("x-cf-authorization", "Bearer "+c.apiToken) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -50,16 +46,14 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -75,7 +69,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("cachefly api error: failed to parse response: %w", err) + return fmt.Errorf("cachefly api error: failed to unmarshal response: %w", err) } return nil diff --git a/internal/pkg/sdk3rd/cdnfly/client.go b/internal/pkg/sdk3rd/cdnfly/client.go index 0061e363..c8753ed5 100644 --- a/internal/pkg/sdk3rd/cdnfly/client.go +++ b/internal/pkg/sdk3rd/cdnfly/client.go @@ -12,21 +12,17 @@ import ( ) type Client struct { - apiHost string - apiKey string - apiSecret string - client *resty.Client } func NewClient(apiHost, apiKey, apiSecret string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetHeader("api-key", apiKey). + SetHeader("api-secret", apiSecret) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - apiSecret: apiSecret, - client: client, + client: client, } } @@ -42,11 +38,6 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = c.apiHost + path - req = req. - SetHeader("api-key", c.apiKey). - SetHeader("api-secret", c.apiSecret) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -62,16 +53,14 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -87,7 +76,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("cdnfly api error: failed to parse response: %w", err) + return fmt.Errorf("cdnfly api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode != "" && errcode != "0" { return fmt.Errorf("cdnfly api error: code='%s', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/dnsla/client.go b/internal/pkg/sdk3rd/dnsla/client.go index 7dfbd00a..d9a86fc5 100644 --- a/internal/pkg/sdk3rd/dnsla/client.go +++ b/internal/pkg/sdk3rd/dnsla/client.go @@ -11,19 +11,16 @@ import ( ) type Client struct { - apiId string - apiSecret string - client *resty.Client } func NewClient(apiId, apiSecret string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.dns.la/api"). + SetBasicAuth(apiId, apiSecret) return &Client{ - apiId: apiId, - apiSecret: apiSecret, - client: client, + client: client, } } @@ -33,9 +30,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.apiId, c.apiSecret) - req.Method = method - req.URL = "https://api.dns.la/api" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -51,16 +46,14 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -76,7 +69,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("dnsla api error: failed to parse response: %w", err) + return fmt.Errorf("dnsla api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode/100 != 2 { return fmt.Errorf("dnsla api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/dogecloud/client.go b/internal/pkg/sdk3rd/dogecloud/client.go index 46f3513d..75342907 100644 --- a/internal/pkg/sdk3rd/dogecloud/client.go +++ b/internal/pkg/sdk3rd/dogecloud/client.go @@ -164,8 +164,8 @@ func (c *Client) sendReq(method string, path string, data map[string]interface{} if err != nil { return nil, err } - req.Header.Add("Content-Type", mime) - req.Header.Add("Authorization", auth) + req.Header.Set("Content-Type", mime) + req.Header.Set("Authorization", auth) client := http.Client{} resp, err := client.Do(req) @@ -174,10 +174,10 @@ func (c *Client) sendReq(method string, path string, data map[string]interface{} } defer resp.Body.Close() - r, err := io.ReadAll(resp.Body) + bytes, err := io.ReadAll(resp.Body) if err != nil { return nil, err } - return r, nil + return bytes, nil } diff --git a/internal/pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace/applications/v7/edgio_client.go b/internal/pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace/applications/v7/edgio_client.go index fb7b7cf7..02ded6fd 100644 --- a/internal/pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace/applications/v7/edgio_client.go +++ b/internal/pkg/sdk3rd/edgio/edgio-api@v0.0.0-workspace/applications/v7/edgio_client.go @@ -140,7 +140,7 @@ func (c *EdgioClient) GetProperties(page int, pageSize int, organizationID strin } if resp.IsError() { - return nil, fmt.Errorf("unexpected status code for getProperties: %d, %s", resp.StatusCode(), resp.Body()) + return nil, fmt.Errorf("unexpected status code for getProperties: %d, %s", resp.StatusCode(), resp.String()) } return &propertiesResp, nil @@ -512,7 +512,7 @@ func (c *EdgioClient) UploadCdnConfiguration(config *dtos.CDNConfiguration) (*dt } if resp.IsError() { - return nil, fmt.Errorf("unexpected status code for uploadCdnConfiguration: %d, %s", resp.StatusCode(), resp.Body()) + return nil, fmt.Errorf("unexpected status code for uploadCdnConfiguration: %d, %s", resp.StatusCode(), resp.String()) } return &response, nil diff --git a/internal/pkg/sdk3rd/flexcdn/api.go b/internal/pkg/sdk3rd/flexcdn/api.go new file mode 100644 index 00000000..5008fdf4 --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/api.go @@ -0,0 +1,48 @@ +package flexcdn + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" && c.accessTokenExp.After(time.Now()) { + return nil + } + + req := &getAPIAccessTokenRequest{ + Type: c.apiRole, + AccessKeyId: c.accessKeyId, + AccessKey: c.accessKey, + } + res, err := c.sendRequest(http.MethodPost, "/APIAccessTokenService/getAPIAccessToken", req) + if err != nil { + return err + } + + resp := &getAPIAccessTokenResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("flexcdn get access token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) + } + + c.accessToken = resp.Data.Token + c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) + + return nil +} + +func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err + } + + resp := &UpdateSSLCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/SSLCertService/updateSSLCert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/flexcdn/client.go b/internal/pkg/sdk3rd/flexcdn/client.go new file mode 100644 index 00000000..beae469a --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/client.go @@ -0,0 +1,102 @@ +package flexcdn + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiRole string + accessKeyId string + accessKey string + + accessToken string + accessTokenExp time.Time + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { + client := &Client{ + apiRole: apiRole, + accessKeyId: accessKeyId, + accessKey: accessKey, + } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("X-Cloud-Access-Token", client.accessToken) + } + + return nil + }) + + return client +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("flexcdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("flexcdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("flexcdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/flexcdn/models.go b/internal/pkg/sdk3rd/flexcdn/models.go new file mode 100644 index 00000000..c976eccc --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/models.go @@ -0,0 +1,52 @@ +package flexcdn + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type getAPIAccessTokenRequest struct { + Type string `json:"type"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` +} + +type getAPIAccessTokenResponse struct { + baseResponse + Data *struct { + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + } `json:"data,omitempty"` +} + +type UpdateSSLCertRequest struct { + SSLCertId int64 `json:"sslCertId"` + IsOn bool `json:"isOn"` + Name string `json:"name"` + Description string `json:"description"` + ServerName string `json:"serverName"` + IsCA bool `json:"isCA"` + CertData string `json:"certData"` + KeyData string `json:"keyData"` + TimeBeginAt int64 `json:"timeBeginAt"` + TimeEndAt int64 `json:"timeEndAt"` + DNSNames []string `json:"dnsNames"` + CommonNames []string `json:"commonNames"` +} + +type UpdateSSLCertResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/gname/client.go b/internal/pkg/sdk3rd/gname/client.go index 9424e8ec..ef00e699 100644 --- a/internal/pkg/sdk3rd/gname/client.go +++ b/internal/pkg/sdk3rd/gname/client.go @@ -82,7 +82,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, if err != nil { return resp, fmt.Errorf("gname api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("gname api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("gname api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -95,7 +95,7 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("gname api error: failed to parse response: %w", err) + return fmt.Errorf("gname api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode != 1 { return fmt.Errorf("gname api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/goedge/api.go b/internal/pkg/sdk3rd/goedge/api.go index c217e8ae..4589f70c 100644 --- a/internal/pkg/sdk3rd/goedge/api.go +++ b/internal/pkg/sdk3rd/goedge/api.go @@ -7,7 +7,13 @@ import ( "time" ) -func (c *Client) getAccessToken() error { +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" && c.accessTokenExp.After(time.Now()) { + return nil + } + req := &getAPIAccessTokenRequest{ Type: c.apiRole, AccessKeyId: c.accessKeyId, @@ -20,24 +26,20 @@ func (c *Client) getAccessToken() error { resp := &getAPIAccessTokenResponse{} if err := json.Unmarshal(res.Body(), &resp); err != nil { - return fmt.Errorf("goedge api error: failed to parse response: %w", err) + return fmt.Errorf("goedge api error: failed to unmarshal response: %w", err) } else if resp.GetCode() != 200 { - return fmt.Errorf("goedge get access token failed: code: %d, message: %s", resp.GetCode(), resp.GetMessage()) + return fmt.Errorf("goedge get access token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) } - c.accessTokenMtx.Lock() c.accessToken = resp.Data.Token c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) - c.accessTokenMtx.Unlock() return nil } func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { - if c.accessToken == "" || c.accessTokenExp.Before(time.Now()) { - if err := c.getAccessToken(); err != nil { - return nil, err - } + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err } resp := &UpdateSSLCertResponse{} diff --git a/internal/pkg/sdk3rd/goedge/client.go b/internal/pkg/sdk3rd/goedge/client.go index c42b798b..3dc961e3 100644 --- a/internal/pkg/sdk3rd/goedge/client.go +++ b/internal/pkg/sdk3rd/goedge/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - apiHost string apiRole string accessKeyId string accessKey string @@ -26,15 +25,22 @@ type Client struct { } func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { - client := resty.New() - - return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), + client := &Client{ apiRole: apiRole, accessKeyId: accessKeyId, accessKey: accessKey, - client: client, } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("X-Edge-Access-Token", client.accessToken) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -48,9 +54,7 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.accessKeyId, c.accessKey) - req.Method = method - req.URL = c.apiHost + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -64,21 +68,16 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetQueryParams(qs). - SetHeader("X-Edge-Access-Token", c.accessToken) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("X-Edge-Access-Token", c.accessToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("goedge api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("goedge api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("goedge api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -94,7 +93,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("goedge api error: failed to parse response: %w", err) + return fmt.Errorf("goedge api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode != 200 { return fmt.Errorf("goedge api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/api.go b/internal/pkg/sdk3rd/lecdn/v3/client/api.go new file mode 100644 index 00000000..89f9cdc0 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/api.go @@ -0,0 +1,50 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" { + return nil + } + + req := &loginRequest{ + Email: c.username, + Username: c.username, + Password: c.password, + } + res, err := c.sendRequest(http.MethodPost, "/login", req) + if err != nil { + return err + } + + resp := &loginResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("lecdn get token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) + } + + c.accessToken = resp.Data.Token + + return nil +} + +func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certId == 0 { + return nil, fmt.Errorf("lecdn api error: invalid parameter: CertId") + } + + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err + } + + resp := &UpdateCertificateResponse{} + err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/certificate/%d", certId), req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/client.go b/internal/pkg/sdk3rd/lecdn/v3/client/client.go new file mode 100644 index 00000000..ad3a752e --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/client.go @@ -0,0 +1,99 @@ +package client + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + username string + password string + + accessToken string + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, username, password string) *Client { + client := &Client{ + username: username, + password: password, + } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/prod-api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("Authorization", "Bearer "+client.accessToken) + } + + return nil + }) + + return client +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("lecdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("lecdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/models.go b/internal/pkg/sdk3rd/lecdn/v3/client/models.go new file mode 100644 index 00000000..6d63ea79 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/models.go @@ -0,0 +1,47 @@ +package client + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"msg"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type loginRequest struct { + Email string `json:"email"` + Username string `json:"username"` + Password string `json:"password"` +} + +type loginResponse struct { + baseResponse + Data *struct { + UserId int64 `json:"user_id"` + Username string `json:"username"` + Token string `json:"token"` + } `json:"data,omitempty"` +} + +type UpdateCertificateRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + SSLPEM string `json:"ssl_pem"` + SSLKey string `json:"ssl_key"` + AutoRenewal bool `json:"auto_renewal"` +} + +type UpdateCertificateResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/api.go b/internal/pkg/sdk3rd/lecdn/v3/master/api.go new file mode 100644 index 00000000..00f24a70 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/api.go @@ -0,0 +1,49 @@ +package master + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" { + return nil + } + + req := &loginRequest{ + Username: c.username, + Password: c.password, + } + res, err := c.sendRequest(http.MethodPost, "/auth/login", req) + if err != nil { + return err + } + + resp := &loginResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("lecdn get token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) + } + + c.accessToken = resp.Data.Token + + return nil +} + +func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certId == 0 { + return nil, fmt.Errorf("lecdn api error: invalid parameter: CertId") + } + + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err + } + + resp := &UpdateCertificateResponse{} + err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/certificate/%d", certId), req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/client.go b/internal/pkg/sdk3rd/lecdn/v3/master/client.go new file mode 100644 index 00000000..2da0c0c4 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/client.go @@ -0,0 +1,99 @@ +package master + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + username string + password string + + accessToken string + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, username, password string) *Client { + client := &Client{ + username: username, + password: password, + } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/prod-api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("Authorization", "Bearer "+client.accessToken) + } + + return nil + }) + + return client +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("lecdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("lecdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/models.go b/internal/pkg/sdk3rd/lecdn/v3/master/models.go new file mode 100644 index 00000000..2e896f42 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/models.go @@ -0,0 +1,47 @@ +package master + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type loginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type loginResponse struct { + baseResponse + Data *struct { + UserId int64 `json:"user_id"` + Username string `json:"username"` + Token string `json:"token"` + } `json:"data,omitempty"` +} + +type UpdateCertificateRequest struct { + ClientId int64 `json:"client_id"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + SSLPEM string `json:"ssl_pem"` + SSLKey string `json:"ssl_key"` + AutoRenewal bool `json:"auto_renewal"` +} + +type UpdateCertificateResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/netlify/client.go b/internal/pkg/sdk3rd/netlify/client.go index 5f8bc6c2..d270e35e 100644 --- a/internal/pkg/sdk3rd/netlify/client.go +++ b/internal/pkg/sdk3rd/netlify/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.netlify.com/api/v1"). + SetHeader("Authorization", "Bearer "+apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -31,9 +30,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, queryParams interface{}, payloadParams interface{}) (*resty.Response, error) { - req := c.client.R().SetHeader("Authorization", "Bearer "+c.apiToken) - req.Method = method - req.URL = "https://api.netlify.com/api/v1" + path + req := c.client.R() if queryParams != nil { qs := make(map[string]string) @@ -63,16 +60,14 @@ func (c *Client) sendRequest(method string, path string, queryParams interface{} req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(payloadParams) + req = req.SetHeader("Content-Type", "application/json").SetBody(payloadParams) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("netlify api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("netlify api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("netlify api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -88,7 +83,7 @@ func (c *Client) sendRequestWithResult(method string, path string, queryParams i } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("netlify api error: failed to parse response: %w", err) + return fmt.Errorf("netlify api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode != 0 { return fmt.Errorf("netlify api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/rainyun/client.go b/internal/pkg/sdk3rd/rainyun/client.go index 0ead39cd..80113f0d 100644 --- a/internal/pkg/sdk3rd/rainyun/client.go +++ b/internal/pkg/sdk3rd/rainyun/client.go @@ -11,16 +11,15 @@ import ( ) type Client struct { - apiKey string - client *resty.Client } func NewClient(apiKey string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.v2.rainyun.com"). + SetHeader("x-api-key", apiKey) return &Client{ - apiKey: apiKey, client: client, } } @@ -31,25 +30,21 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetHeader("x-api-key", c.apiKey) - req.Method = method - req.URL = "https://api.v2.rainyun.com" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { if params != nil { jsonb, _ := json.Marshal(params) req = req.SetQueryParam("options", string(jsonb)) } } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("rainyun api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("rainyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("rainyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -65,7 +60,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("rainyun api error: failed to parse response: %w", err) + return fmt.Errorf("rainyun api error: failed to unmarshal response: %w", err) } else if errcode := result.GetCode(); errcode/100 != 2 { return fmt.Errorf("rainyun api error: code='%d', message='%s'", errcode, result.GetMessage()) } diff --git a/internal/pkg/sdk3rd/ratpanel/api.go b/internal/pkg/sdk3rd/ratpanel/api.go new file mode 100644 index 00000000..17f8110f --- /dev/null +++ b/internal/pkg/sdk3rd/ratpanel/api.go @@ -0,0 +1,15 @@ +package ratpanelsdk + +import "net/http" + +func (c *Client) SettingCert(req *SettingCertRequest) (*SettingCertResponse, error) { + resp := &SettingCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/setting/cert", req, resp) + return resp, err +} + +func (c *Client) WebsiteCert(req *WebsiteCertRequest) (*WebsiteCertResponse, error) { + resp := &WebsiteCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/website/cert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/ratpanel/client.go b/internal/pkg/sdk3rd/ratpanel/client.go new file mode 100644 index 00000000..47202a04 --- /dev/null +++ b/internal/pkg/sdk3rd/ratpanel/client.go @@ -0,0 +1,141 @@ +package ratpanelsdk + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + client *resty.Client +} + +func NewClient(apiHost string, accessTokenId int32, accessToken string) *Client { + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")+"/api"). + SetHeader("Accept", "application/json"). + SetHeader("Content-Type", "application/json"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + var body []byte + var err error + + if req.Body != nil { + body, err = io.ReadAll(req.Body) + if err != nil { + return err + } + req.Body = io.NopCloser(bytes.NewReader(body)) + } + + canonicalPath := req.URL.Path + if !strings.HasPrefix(canonicalPath, "/api") { + index := strings.Index(canonicalPath, "/api") + if index != -1 { + canonicalPath = canonicalPath[index:] + } + } + + canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s", + req.Method, + canonicalPath, + req.URL.Query().Encode(), + sha256Sum(string(body))) + + timestamp := time.Now().Unix() + req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp)) + + stringToSign := fmt.Sprintf("%s\n%d\n%s", + "HMAC-SHA256", + timestamp, + sha256Sum(canonicalRequest)) + signature := hmacSha256(stringToSign, accessToken) + req.Header.Set("Authorization", fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", accessTokenId, signature)) + + return nil + }) + + return &Client{ + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("ratpanel api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("ratpanel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err = json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("ratpanel api error: failed to unmarshal response: %w", err) + } else if errmsg := result.GetMessage(); errmsg != "success" { + return fmt.Errorf("ratpanel api error: message='%s'", errmsg) + } + + return nil +} + +func sha256Sum(str string) string { + sum := sha256.Sum256([]byte(str)) + dst := make([]byte, hex.EncodedLen(len(sum))) + hex.Encode(dst, sum[:]) + return string(dst) +} + +func hmacSha256(data string, secret string) string { + h := hmac.New(sha256.New, []byte(secret)) + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/internal/pkg/sdk3rd/ratpanel/models.go b/internal/pkg/sdk3rd/ratpanel/models.go new file mode 100644 index 00000000..bf5f53fb --- /dev/null +++ b/internal/pkg/sdk3rd/ratpanel/models.go @@ -0,0 +1,35 @@ +package ratpanelsdk + +type BaseResponse interface { + GetMessage() string +} + +type baseResponse struct { + Message *string `json:"msg,omitempty"` +} + +func (r *baseResponse) GetMessage() string { + if r.Message != nil { + return *r.Message + } + return "" +} + +type SettingCertRequest struct { + Certificate string `json:"cert"` + PrivateKey string `json:"key"` +} + +type SettingCertResponse struct { + baseResponse +} + +type WebsiteCertRequest struct { + SiteName string `json:"name"` + Certificate string `json:"cert"` + PrivateKey string `json:"key"` +} + +type WebsiteCertResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/safeline/client.go b/internal/pkg/sdk3rd/safeline/client.go index 4705d74e..efcd3bd6 100644 --- a/internal/pkg/sdk3rd/safeline/client.go +++ b/internal/pkg/sdk3rd/safeline/client.go @@ -11,19 +11,16 @@ import ( ) type Client struct { - apiHost string - apiToken string - client *resty.Client } func NewClient(apiHost, apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetHeader("X-SLCE-API-TOKEN", apiToken) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiToken: apiToken, - client: client, + client: client, } } @@ -38,16 +35,14 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) { - url := c.apiHost + path req := c.client.R(). SetHeader("Content-Type", "application/json"). - SetHeader("X-SLCE-API-TOKEN", c.apiToken). SetBody(params) - resp, err := req.Post(url) + resp, err := req.Post(path) if err != nil { return resp, fmt.Errorf("safeline api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("safeline api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("safeline api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -63,7 +58,7 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("safeline api error: failed to parse response: %w", err) + return fmt.Errorf("safeline api error: failed to unmarshal response: %w", err) } else if errcode := result.GetErrCode(); errcode != nil && *errcode != "" { if result.GetErrMsg() == nil { return fmt.Errorf("safeline api error: code='%s'", *errcode) diff --git a/internal/pkg/sdk3rd/upyun/console/api.go b/internal/pkg/sdk3rd/upyun/console/api.go index 28ccd734..ce62d3a6 100644 --- a/internal/pkg/sdk3rd/upyun/console/api.go +++ b/internal/pkg/sdk3rd/upyun/console/api.go @@ -7,7 +7,11 @@ import ( "net/http" ) -func (c *Client) getCookie() error { +func (c *Client) ensureCookieExists() error { + if c.loginCookie != "" { + return nil + } + req := &signinRequest{Username: c.username, Password: c.password} res, err := c.sendRequest(http.MethodPost, "/accounts/signin/", req) if err != nil { @@ -16,7 +20,7 @@ func (c *Client) getCookie() error { resp := &signinResponse{} if err := json.Unmarshal(res.Body(), &resp); err != nil { - return fmt.Errorf("upyun api error: failed to parse response: %w", err) + return fmt.Errorf("upyun api error: failed to unmarshal response: %w", err) } else if !resp.Data.Result { return errors.New("upyun console signin failed") } @@ -27,10 +31,8 @@ func (c *Client) getCookie() error { } func (c *Client) UploadHttpsCertificate(req *UploadHttpsCertificateRequest) (*UploadHttpsCertificateResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &UploadHttpsCertificateResponse{} @@ -39,10 +41,8 @@ func (c *Client) UploadHttpsCertificate(req *UploadHttpsCertificateRequest) (*Up } func (c *Client) GetHttpsCertificateManager(certificateId string) (*GetHttpsCertificateManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } req := &GetHttpsCertificateManagerRequest{CertificateId: certificateId} @@ -52,10 +52,8 @@ func (c *Client) GetHttpsCertificateManager(certificateId string) (*GetHttpsCert } func (c *Client) UpdateHttpsCertificateManager(req *UpdateHttpsCertificateManagerRequest) (*UpdateHttpsCertificateManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &UpdateHttpsCertificateManagerResponse{} @@ -64,10 +62,8 @@ func (c *Client) UpdateHttpsCertificateManager(req *UpdateHttpsCertificateManage } func (c *Client) GetHttpsServiceManager(domain string) (*GetHttpsServiceManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } req := &GetHttpsServiceManagerRequest{Domain: domain} @@ -77,10 +73,8 @@ func (c *Client) GetHttpsServiceManager(domain string) (*GetHttpsServiceManagerR } func (c *Client) MigrateHttpsDomain(req *MigrateHttpsDomainRequest) (*MigrateHttpsDomainResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &MigrateHttpsDomainResponse{} diff --git a/internal/pkg/sdk3rd/upyun/console/client.go b/internal/pkg/sdk3rd/upyun/console/client.go index 90d74bc3..b207549e 100644 --- a/internal/pkg/sdk3rd/upyun/console/client.go +++ b/internal/pkg/sdk3rd/upyun/console/client.go @@ -20,13 +20,21 @@ type Client struct { } func NewClient(username, password string) *Client { - client := resty.New() - - return &Client{ + client := &Client{ username: username, password: password, - client: client, } + client.client = resty.New(). + SetBaseURL("https://console.upyun.com"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.loginCookie != "" { + req.Header.Set("Cookie", client.loginCookie) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -35,9 +43,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.username, c.password) - req.Method = method - req.URL = "https://console.upyun.com" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -51,21 +57,16 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetQueryParams(qs). - SetHeader("Cookie", c.loginCookie) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("Cookie", c.loginCookie). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("upyun api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("upyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("upyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -81,12 +82,12 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf } if err := json.Unmarshal(resp.Body(), &result); err != nil { - return fmt.Errorf("upyun api error: failed to parse response: %w", err) + return fmt.Errorf("upyun api error: failed to unmarshal response: %w", err) } tresp := &baseResponse{} if err := json.Unmarshal(resp.Body(), &tresp); err != nil { - return fmt.Errorf("upyun api error: failed to parse response: %w", err) + return fmt.Errorf("upyun api error: failed to unmarshal response: %w", err) } else if tdata := tresp.GetData(); tdata == nil { return fmt.Errorf("upyun api error: empty data") } else if errcode := tdata.GetErrorCode(); errcode > 0 { diff --git a/internal/pkg/sdk3rd/wangsu/cdn/api.go b/internal/pkg/sdk3rd/wangsu/cdn/api.go index 0da647c8..997c05bf 100644 --- a/internal/pkg/sdk3rd/wangsu/cdn/api.go +++ b/internal/pkg/sdk3rd/wangsu/cdn/api.go @@ -1,70 +1,15 @@ package cdn import ( - "fmt" "net/http" - "net/url" - - "github.com/go-resty/resty/v2" ) -func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { - resp := &CreateCertificateResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) { - r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) - }) +func (c *Client) BatchUpdateCertificateConfig(req *BatchUpdateCertificateConfigRequest) (*BatchUpdateCertificateConfigResponse, error) { + resp := &BatchUpdateCertificateConfigResponse{} + _, err := c.client.SendRequestWithResult(http.MethodPut, "/api/config/certificate/batch", req, resp) if err != nil { return resp, err } - resp.CertificateUrl = r.Header().Get("Location") - return resp, err -} - -func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { - if certificateId == "" { - return nil, fmt.Errorf("wangsu api error: invalid parameter: certificateId") - } - - resp := &UpdateCertificateResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) { - r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) - }) - if err != nil { - return resp, err - } - - resp.CertificateUrl = r.Header().Get("Location") - return resp, err -} - -func (c *Client) GetHostnameDetail(hostname string) (*GetHostnameDetailResponse, error) { - if hostname == "" { - return nil, fmt.Errorf("wangsu api error: invalid parameter: hostname") - } - - resp := &GetHostnameDetailResponse{} - _, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/hostnames/%s", url.PathEscape(hostname)), nil, resp) - return resp, err -} - -func (c *Client) CreateDeploymentTask(req *CreateDeploymentTaskRequest) (*CreateDeploymentTaskResponse, error) { - resp := &CreateDeploymentTaskResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp) - if err != nil { - return resp, err - } - - resp.DeploymentTaskUrl = r.Header().Get("Location") - return resp, err -} - -func (c *Client) GetDeploymentTaskDetail(deploymentTaskId string) (*GetDeploymentTaskDetailResponse, error) { - if deploymentTaskId == "" { - return nil, fmt.Errorf("wangsu api error: invalid parameter: deploymentTaskId") - } - - resp := &GetDeploymentTaskDetailResponse{} - _, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/deploymentTasks/%s", url.PathEscape(deploymentTaskId)), nil, resp) return resp, err } diff --git a/internal/pkg/sdk3rd/wangsu/cdn/models.go b/internal/pkg/sdk3rd/wangsu/cdn/models.go index a9a9ec74..5bf934af 100644 --- a/internal/pkg/sdk3rd/wangsu/cdn/models.go +++ b/internal/pkg/sdk3rd/wangsu/cdn/models.go @@ -5,7 +5,7 @@ import ( ) type baseResponse struct { - RequestId *string `json:"-"` + RequestId *string `json:"requestId,omitempty"` Code *string `json:"code,omitempty"` Message *string `json:"message,omitempty"` } @@ -16,93 +16,11 @@ func (r *baseResponse) SetRequestId(requestId string) { r.RequestId = &requestId } -type CertificateVersion struct { - Comments *string `json:"comments,omitempty"` - PrivateKey *string `json:"privateKey,omitempty"` - Certificate *string `json:"certificate,omitempty"` - ChainCert *string `json:"chainCert,omitempty"` - IdentificationInfo *CertificateVersionIdentificationInfo `json:"identificationInfo,omitempty"` +type BatchUpdateCertificateConfigRequest struct { + CertificateId int64 `json:"certificateId" required:"true"` + DomainNames []string `json:"domainNames" required:"true"` } -type CertificateVersionIdentificationInfo struct { - Country *string `json:"country,omitempty"` - State *string `json:"state,omitempty"` - City *string `json:"city,omitempty"` - Company *string `json:"company,omitempty"` - Department *string `json:"department,omitempty"` - CommonName *string `json:"commonName,omitempty" required:"true"` - Email *string `json:"email,omitempty"` - SubjectAlternativeNames *[]string `json:"subjectAlternativeNames,omitempty" required:"true"` -} - -type CreateCertificateRequest struct { - Timestamp int64 `json:"-"` - Name *string `json:"name,omitempty" required:"true"` - Description *string `json:"description,omitempty"` - AutoRenew *string `json:"autoRenew,omitempty"` - ForceRenew *bool `json:"forceRenew,omitempty"` - NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"` -} - -type CreateCertificateResponse struct { +type BatchUpdateCertificateConfigResponse struct { baseResponse - CertificateUrl string `json:"location,omitempty"` -} - -type UpdateCertificateRequest struct { - Timestamp int64 `json:"-"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - AutoRenew *string `json:"autoRenew,omitempty"` - ForceRenew *bool `json:"forceRenew,omitempty"` - NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"` -} - -type UpdateCertificateResponse struct { - baseResponse - CertificateUrl string `json:"location,omitempty"` -} - -type HostnameProperty struct { - PropertyId string `json:"propertyId"` - Version int32 `json:"version"` - CertificateId *string `json:"certificateId,omitempty"` -} - -type GetHostnameDetailResponse struct { - baseResponse - Hostname string `json:"hostname"` - PropertyInProduction *HostnameProperty `json:"propertyInProduction,omitempty"` - PropertyInStaging *HostnameProperty `json:"propertyInStaging,omitempty"` -} - -type DeploymentTaskAction struct { - Action *string `json:"action,omitempty" required:"true"` - PropertyId *string `json:"propertyId,omitempty"` - CertificateId *string `json:"certificateId,omitempty"` - Version *int32 `json:"version,omitempty"` -} - -type CreateDeploymentTaskRequest struct { - Name *string `json:"name,omitempty"` - Target *string `json:"target,omitempty" required:"true"` - Actions *[]DeploymentTaskAction `json:"actions,omitempty" required:"true"` - Webhook *string `json:"webhook,omitempty"` -} - -type CreateDeploymentTaskResponse struct { - baseResponse - DeploymentTaskUrl string `json:"location,omitempty"` -} - -type GetDeploymentTaskDetailResponse struct { - baseResponse - Name string `json:"name"` - Target string `json:"target"` - Actions []DeploymentTaskAction `json:"actions"` - Status string `json:"status"` - StatusDetails string `json:"statusDetails"` - SubmissionTime string `json:"submissionTime"` - FinishTime string `json:"finishTime"` - ApiRequestId string `json:"apiRequestId"` } diff --git a/internal/pkg/sdk3rd/wangsu/cdnpro/api.go b/internal/pkg/sdk3rd/wangsu/cdnpro/api.go new file mode 100644 index 00000000..c6f8da04 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/api.go @@ -0,0 +1,70 @@ +package cdnpro + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/go-resty/resty/v2" +) + +func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + resp := &CreateCertificateResponse{} + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) { + r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) + }) + if err != nil { + return resp, err + } + + resp.CertificateUrl = rres.Header().Get("Location") + return resp, err +} + +func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certificateId == "" { + return nil, fmt.Errorf("wangsu api error: invalid parameter: certificateId") + } + + resp := &UpdateCertificateResponse{} + rres, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) { + r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) + }) + if err != nil { + return resp, err + } + + resp.CertificateUrl = rres.Header().Get("Location") + return resp, err +} + +func (c *Client) GetHostnameDetail(hostname string) (*GetHostnameDetailResponse, error) { + if hostname == "" { + return nil, fmt.Errorf("wangsu api error: invalid parameter: hostname") + } + + resp := &GetHostnameDetailResponse{} + _, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/hostnames/%s", url.PathEscape(hostname)), nil, resp) + return resp, err +} + +func (c *Client) CreateDeploymentTask(req *CreateDeploymentTaskRequest) (*CreateDeploymentTaskResponse, error) { + resp := &CreateDeploymentTaskResponse{} + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp) + if err != nil { + return resp, err + } + + resp.DeploymentTaskUrl = rres.Header().Get("Location") + return resp, err +} + +func (c *Client) GetDeploymentTaskDetail(deploymentTaskId string) (*GetDeploymentTaskDetailResponse, error) { + if deploymentTaskId == "" { + return nil, fmt.Errorf("wangsu api error: invalid parameter: deploymentTaskId") + } + + resp := &GetDeploymentTaskDetailResponse{} + _, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/deploymentTasks/%s", url.PathEscape(deploymentTaskId)), nil, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/wangsu/cdnpro/client.go b/internal/pkg/sdk3rd/wangsu/cdnpro/client.go new file mode 100644 index 00000000..b5c0f530 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/client.go @@ -0,0 +1,20 @@ +package cdnpro + +import ( + "time" + + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type Client struct { + client *openapi.Client +} + +func NewClient(accessKey, secretKey string) *Client { + return &Client{client: openapi.NewClient(accessKey, secretKey)} +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.WithTimeout(timeout) + return c +} diff --git a/internal/pkg/sdk3rd/wangsu/cdnpro/models.go b/internal/pkg/sdk3rd/wangsu/cdnpro/models.go new file mode 100644 index 00000000..9cb1e648 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/models.go @@ -0,0 +1,108 @@ +package cdnpro + +import ( + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type baseResponse struct { + RequestId *string `json:"requestId,omitempty"` + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +var _ openapi.Result = (*baseResponse)(nil) + +func (r *baseResponse) SetRequestId(requestId string) { + r.RequestId = &requestId +} + +type CertificateVersion struct { + Comments *string `json:"comments,omitempty"` + PrivateKey *string `json:"privateKey,omitempty"` + Certificate *string `json:"certificate,omitempty"` + ChainCert *string `json:"chainCert,omitempty"` + IdentificationInfo *CertificateVersionIdentificationInfo `json:"identificationInfo,omitempty"` +} + +type CertificateVersionIdentificationInfo struct { + Country *string `json:"country,omitempty"` + State *string `json:"state,omitempty"` + City *string `json:"city,omitempty"` + Company *string `json:"company,omitempty"` + Department *string `json:"department,omitempty"` + CommonName *string `json:"commonName,omitempty" required:"true"` + Email *string `json:"email,omitempty"` + SubjectAlternativeNames *[]string `json:"subjectAlternativeNames,omitempty" required:"true"` +} + +type CreateCertificateRequest struct { + Timestamp int64 `json:"-"` + Name *string `json:"name,omitempty" required:"true"` + Description *string `json:"description,omitempty"` + AutoRenew *string `json:"autoRenew,omitempty"` + ForceRenew *bool `json:"forceRenew,omitempty"` + NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"` +} + +type CreateCertificateResponse struct { + baseResponse + CertificateUrl string `json:"location,omitempty"` +} + +type UpdateCertificateRequest struct { + Timestamp int64 `json:"-"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AutoRenew *string `json:"autoRenew,omitempty"` + ForceRenew *bool `json:"forceRenew,omitempty"` + NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"` +} + +type UpdateCertificateResponse struct { + baseResponse + CertificateUrl string `json:"location,omitempty"` +} + +type HostnameProperty struct { + PropertyId string `json:"propertyId"` + Version int32 `json:"version"` + CertificateId *string `json:"certificateId,omitempty"` +} + +type GetHostnameDetailResponse struct { + baseResponse + Hostname string `json:"hostname"` + PropertyInProduction *HostnameProperty `json:"propertyInProduction,omitempty"` + PropertyInStaging *HostnameProperty `json:"propertyInStaging,omitempty"` +} + +type DeploymentTaskAction struct { + Action *string `json:"action,omitempty" required:"true"` + PropertyId *string `json:"propertyId,omitempty"` + CertificateId *string `json:"certificateId,omitempty"` + Version *int32 `json:"version,omitempty"` +} + +type CreateDeploymentTaskRequest struct { + Name *string `json:"name,omitempty"` + Target *string `json:"target,omitempty" required:"true"` + Actions *[]DeploymentTaskAction `json:"actions,omitempty" required:"true"` + Webhook *string `json:"webhook,omitempty"` +} + +type CreateDeploymentTaskResponse struct { + baseResponse + DeploymentTaskUrl string `json:"location,omitempty"` +} + +type GetDeploymentTaskDetailResponse struct { + baseResponse + Name string `json:"name"` + Target string `json:"target"` + Actions []DeploymentTaskAction `json:"actions"` + Status string `json:"status"` + StatusDetails string `json:"statusDetails"` + SubmissionTime string `json:"submissionTime"` + FinishTime string `json:"finishTime"` + ApiRequestId string `json:"apiRequestId"` +} diff --git a/internal/pkg/sdk3rd/wangsu/certificate/api.go b/internal/pkg/sdk3rd/wangsu/certificate/api.go new file mode 100644 index 00000000..037fb6e7 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/api.go @@ -0,0 +1,42 @@ +package certificate + +import ( + "fmt" + "net/http" + "net/url" +) + +func (c *Client) ListCertificates() (*ListCertificatesResponse, error) { + resp := &ListCertificatesResponse{} + _, err := c.client.SendRequestWithResult(http.MethodGet, "/api/certificate", nil, resp) + if err != nil { + return resp, err + } + + return resp, err +} + +func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + resp := &CreateCertificateResponse{} + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/api/certificate", req, resp) + if err != nil { + return resp, err + } + + resp.CertificateUrl = rres.Header().Get("Location") + return resp, err +} + +func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certificateId == "" { + return nil, fmt.Errorf("wangsu api error: invalid parameter: certificateId") + } + + resp := &UpdateCertificateResponse{} + _, err := c.client.SendRequestWithResult(http.MethodPut, fmt.Sprintf("/api/certificate/%s", url.PathEscape(certificateId)), req, resp) + if err != nil { + return resp, err + } + + return resp, err +} diff --git a/internal/pkg/sdk3rd/wangsu/certificate/client.go b/internal/pkg/sdk3rd/wangsu/certificate/client.go new file mode 100644 index 00000000..19f4cfaa --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/client.go @@ -0,0 +1,20 @@ +package certificate + +import ( + "time" + + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type Client struct { + client *openapi.Client +} + +func NewClient(accessKey, secretKey string) *Client { + return &Client{client: openapi.NewClient(accessKey, secretKey)} +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.WithTimeout(timeout) + return c +} diff --git a/internal/pkg/sdk3rd/wangsu/certificate/models.go b/internal/pkg/sdk3rd/wangsu/certificate/models.go new file mode 100644 index 00000000..4e882e7c --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/models.go @@ -0,0 +1,52 @@ +package certificate + +import ( + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type baseResponse struct { + RequestId *string `json:"requestId,omitempty"` + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +var _ openapi.Result = (*baseResponse)(nil) + +func (r *baseResponse) SetRequestId(requestId string) { + r.RequestId = &requestId +} + +type CreateCertificateRequest struct { + Name *string `json:"name,omitempty" required:"true"` + Certificate *string `json:"certificate,omitempty" required:"true"` + PrivateKey *string `json:"privateKey,omitempty"` + Comment *string `json:"comment,omitempty" ` +} + +type CreateCertificateResponse struct { + baseResponse + CertificateUrl string `json:"location,omitempty"` +} + +type UpdateCertificateRequest struct { + Name *string `json:"name,omitempty" required:"true"` + Certificate *string `json:"certificate,omitempty"` + PrivateKey *string `json:"privateKey,omitempty"` + Comment *string `json:"comment,omitempty" ` +} + +type UpdateCertificateResponse struct { + baseResponse +} + +type ListCertificatesResponse struct { + baseResponse + Certificates []*struct { + CertificateId string `json:"certificate-id"` + Name string `json:"name"` + Comment string `json:"comment"` + ValidityFrom string `json:"certificate-validity-from"` + ValidityTo string `json:"certificate-validity-to"` + Serial string `json:"certificate-serial"` + } `json:"ssl-certificates,omitempty"` +} diff --git a/internal/pkg/sdk3rd/wangsu/openapi/client.go b/internal/pkg/sdk3rd/wangsu/openapi/client.go index d63c20c7..a8f4f2af 100644 --- a/internal/pkg/sdk3rd/wangsu/openapi/client.go +++ b/internal/pkg/sdk3rd/wangsu/openapi/client.go @@ -134,8 +134,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}, configureReq ...func(req *resty.Request)) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -151,18 +149,20 @@ func (c *Client) sendRequest(method string, path string, params interface{}, con req = req.SetQueryParams(qs) } else { - req = req.SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - for _, fn := range configureReq { - fn(req) + if configureReq != nil { + for _, fn := range configureReq { + fn(req) + } } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("wangsu api error: failed to send request: %w", err) } else if resp.IsError() { - return resp, fmt.Errorf("wangsu api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + return resp, fmt.Errorf("wangsu api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) } return resp, nil @@ -181,7 +181,7 @@ func (c *Client) SendRequestWithResult(method string, path string, params interf respBody := resp.Body() if len(respBody) != 0 { if err := json.Unmarshal(respBody, &result); err != nil { - return resp, fmt.Errorf("wangsu api error: failed to parse response: %w", err) + return resp, fmt.Errorf("wangsu api error: failed to unmarshal response: %w", err) } } diff --git a/internal/pkg/utils/map/getter.go b/internal/pkg/utils/map/getter.go index f30f6d33..512da3ee 100644 --- a/internal/pkg/utils/map/getter.go +++ b/internal/pkg/utils/map/getter.go @@ -68,31 +68,42 @@ func GetOrDefaultInt32(dict map[string]any, key string, defaultValue int32) int3 } if value, ok := dict[key]; ok { - if result, ok := value.(int32); ok { - if result != 0 { - return result + var result int32 + + switch v := value.(type) { + case int: + result = int32(v) + case int8: + result = int32(v) + case int16: + result = int32(v) + case int32: + result = v + case int64: + result = int32(v) + case uint: + result = int32(v) + case uint8: + result = int32(v) + case uint16: + result = int32(v) + case uint32: + result = int32(v) + case uint64: + result = int32(v) + case float32: + result = int32(v) + case float64: + result = int32(v) + case string: + // 兼容字符串类型的值 + if t, err := strconv.ParseInt(v, 10, 32); err == nil { + result = int32(t) } } - if result, ok := value.(int64); ok { - if result != 0 { - return int32(result) - } - } - - if result, ok := value.(int); ok { - if result != 0 { - return int32(result) - } - } - - // 兼容字符串类型的值 - if str, ok := value.(string); ok { - if result, err := strconv.ParseInt(str, 10, 32); err == nil { - if result != 0 { - return int32(result) - } - } + if result != 0 { + return int32(result) } } @@ -126,31 +137,42 @@ func GetOrDefaultInt64(dict map[string]any, key string, defaultValue int64) int6 } if value, ok := dict[key]; ok { - if result, ok := value.(int64); ok { - if result != 0 { - return result + var result int64 + + switch v := value.(type) { + case int: + result = int64(v) + case int8: + result = int64(v) + case int16: + result = int64(v) + case int32: + result = int64(v) + case int64: + result = v + case uint: + result = int64(v) + case uint8: + result = int64(v) + case uint16: + result = int64(v) + case uint32: + result = int64(v) + case uint64: + result = int64(v) + case float32: + result = int64(v) + case float64: + result = int64(v) + case string: + // 兼容字符串类型的值 + if t, err := strconv.ParseInt(v, 10, 32); err == nil { + result = t } } - if result, ok := value.(int32); ok { - if result != 0 { - return int64(result) - } - } - - if result, ok := value.(int); ok { - if result != 0 { - return int64(result) - } - } - - // 兼容字符串类型的值 - if str, ok := value.(string); ok { - if result, err := strconv.ParseInt(str, 10, 64); err == nil { - if result != 0 { - return result - } - } + if result != 0 { + return int64(result) } } diff --git a/internal/repository/certificate.go b/internal/repository/certificate.go index 13d2c094..95bfd713 100644 --- a/internal/repository/certificate.go +++ b/internal/repository/certificate.go @@ -101,7 +101,7 @@ func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Ce record.Set("serialNumber", certificate.SerialNumber) record.Set("certificate", certificate.Certificate) record.Set("privateKey", certificate.PrivateKey) - record.Set("issuer", certificate.Issuer) + record.Set("issuerOrg", certificate.IssuerOrg) record.Set("issuerCertificate", certificate.IssuerCertificate) record.Set("keyAlgorithm", string(certificate.KeyAlgorithm)) record.Set("effectAt", certificate.EffectAt) @@ -162,7 +162,7 @@ func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain. SerialNumber: record.GetString("serialNumber"), Certificate: record.GetString("certificate"), PrivateKey: record.GetString("privateKey"), - Issuer: record.GetString("issuer"), + IssuerOrg: record.GetString("issuerOrg"), IssuerCertificate: record.GetString("issuerCertificate"), KeyAlgorithm: domain.CertificateKeyAlgorithmType(record.GetString("keyAlgorithm")), EffectAt: record.GetDateTime("effectAt").Time(), diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index 97b7575d..ff8c573d 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -66,14 +66,14 @@ func (n *applyNode) Process(ctx context.Context) error { } // 解析证书并生成实体 - certX509, err := certutil.ParseCertificateFromPEM(applyResult.CertificateFullChain) + certX509, err := certutil.ParseCertificateFromPEM(applyResult.FullChainCertificate) if err != nil { n.logger.Warn("failed to parse certificate, may be the CA responded error") return err } certificate := &domain.Certificate{ Source: domain.CertificateSourceTypeWorkflow, - Certificate: applyResult.CertificateFullChain, + Certificate: applyResult.FullChainCertificate, PrivateKey: applyResult.PrivateKey, IssuerCertificate: applyResult.IssuerCertificate, ACMEAccountUrl: applyResult.ACMEAccountUrl, diff --git a/migrations/1747389600_upgrade.go b/migrations/1747389600_upgrade.go new file mode 100644 index 00000000..a145679a --- /dev/null +++ b/migrations/1747389600_upgrade.go @@ -0,0 +1,73 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + // update collection `certificate` + { + collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{ + "autogeneratePattern": "", + "hidden": false, + "id": "text2910474005", + "max": 0, + "min": 0, + "name": "issuerOrg", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }`)); err != nil { + return err + } + + if err := app.Save(collection); err != nil { + return err + } + } + + // migrate data + { + accesses, err := app.FindAllRecords("access") + if err != nil { + return err + } + + for _, access := range accesses { + changed := false + + if access.GetString("provider") == "1panel" { + config := make(map[string]any) + if err := access.UnmarshalJSONField("config", &config); err != nil { + return err + } + + config["apiVersion"] = "v1" + access.Set("config", config) + changed = true + } + + if changed { + err = app.Save(access) + if err != nil { + return err + } + } + } + } + + return nil + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/public/imgs/providers/acmeca.svg b/ui/public/imgs/providers/acmeca.svg new file mode 100644 index 00000000..260530c9 --- /dev/null +++ b/ui/public/imgs/providers/acmeca.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/baotawaf.svg b/ui/public/imgs/providers/baotawaf.svg new file mode 100644 index 00000000..34ab8ec8 --- /dev/null +++ b/ui/public/imgs/providers/baotawaf.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/flexcdn.png b/ui/public/imgs/providers/flexcdn.png new file mode 100644 index 00000000..00805598 Binary files /dev/null and b/ui/public/imgs/providers/flexcdn.png differ diff --git a/ui/public/imgs/providers/lecdn.svg b/ui/public/imgs/providers/lecdn.svg new file mode 100644 index 00000000..f9c18fa7 --- /dev/null +++ b/ui/public/imgs/providers/lecdn.svg @@ -0,0 +1 @@ +LeCDN \ No newline at end of file diff --git a/ui/public/imgs/providers/ratpanel.png b/ui/public/imgs/providers/ratpanel.png new file mode 100644 index 00000000..f89808dd Binary files /dev/null and b/ui/public/imgs/providers/ratpanel.png differ diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 0dd2828a..fdf4f93f 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -12,6 +12,7 @@ import { ACCESS_PROVIDERS, ACCESS_USAGES, type AccessProvider } from "@/domain/p import { useAntdForm, useAntdFormName } from "@/hooks"; import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; +import AccessFormACMECAConfig from "./AccessFormACMECAConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAWSConfig from "./AccessFormAWSConfig"; @@ -19,6 +20,7 @@ import AccessFormAzureConfig from "./AccessFormAzureConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; import AccessFormBaishanConfig from "./AccessFormBaishanConfig"; import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig"; +import AccessFormBaotaWAFConfig from "./AccessFormBaotaWAFConfig"; import AccessFormBunnyConfig from "./AccessFormBunnyConfig"; import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig"; import AccessFormCacheFlyConfig from "./AccessFormCacheFlyConfig"; @@ -33,6 +35,7 @@ import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormDynv6Config from "./AccessFormDynv6Config"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; import AccessFormEmailConfig from "./AccessFormEmailConfig"; +import AccessFormFlexCDNConfig from "./AccessFormFlexCDNConfig"; import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; import AccessFormGnameConfig from "./AccessFormGnameConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; @@ -42,6 +45,7 @@ import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLarkBotConfig from "./AccessFormLarkBotConfig"; +import AccessFormLeCDNConfig from "./AccessFormLeCDNConfig"; import AccessFormMattermostConfig from "./AccessFormMattermostConfig"; import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; @@ -54,10 +58,11 @@ import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; import AccessFormProxmoxVEConfig from "./AccessFormProxmoxVEConfig"; import AccessFormQiniuConfig from "./AccessFormQiniuConfig"; import AccessFormRainYunConfig from "./AccessFormRainYunConfig"; +import AccessFormRatPanelConfig from "./AccessFormRatPanelConfig"; import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormSSLComConfig from "./AccessFormSSLComConfig"; -import AccessFormTelegramConfig from "./AccessFormTelegramConfig"; +import AccessFormTelegramBotConfig from "./AccessFormTelegramBotConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; @@ -176,6 +181,8 @@ const AccessForm = forwardRef(({ className, switch (fieldProvider) { case ACCESS_PROVIDERS["1PANEL"]: return ; + case ACCESS_PROVIDERS.ACMECA: + return ; case ACCESS_PROVIDERS.ACMEHTTPREQ: return ; case ACCESS_PROVIDERS.ALIYUN: @@ -190,6 +197,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.BAOTAPANEL: return ; + case ACCESS_PROVIDERS.BAOTAWAF: + return ; case ACCESS_PROVIDERS.BUNNY: return ; case ACCESS_PROVIDERS.BYTEPLUS: @@ -214,6 +223,12 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.DYNV6: return ; + case ACCESS_PROVIDERS.EDGIO: + return ; + case ACCESS_PROVIDERS.EMAIL: + return ; + case ACCESS_PROVIDERS.FLEXCDN: + return ; case ACCESS_PROVIDERS.GCORE: return ; case ACCESS_PROVIDERS.GNAME: @@ -224,10 +239,6 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES: return ; - case ACCESS_PROVIDERS.EDGIO: - return ; - case ACCESS_PROVIDERS.EMAIL: - return ; case ACCESS_PROVIDERS.HUAWEICLOUD: return ; case ACCESS_PROVIDERS.JDCLOUD: @@ -236,6 +247,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.LARKBOT: return ; + case ACCESS_PROVIDERS.LECDN: + return ; case ACCESS_PROVIDERS.MATTERMOST: return ; case ACCESS_PROVIDERS.NAMECHEAP: @@ -260,12 +273,14 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.RAINYUN: return ; + case ACCESS_PROVIDERS.RATPANEL: + return ; case ACCESS_PROVIDERS.SAFELINE: return ; case ACCESS_PROVIDERS.SSH: return ; - case ACCESS_PROVIDERS.TELEGRAM: - return ; + case ACCESS_PROVIDERS.TELEGRAMBOT: + return ; case ACCESS_PROVIDERS.SSLCOM: return ; case ACCESS_PROVIDERS.TENCENTCLOUD: diff --git a/ui/src/components/access/AccessForm1PanelConfig.tsx b/ui/src/components/access/AccessForm1PanelConfig.tsx index c0762bbd..29481f15 100644 --- a/ui/src/components/access/AccessForm1PanelConfig.tsx +++ b/ui/src/components/access/AccessForm1PanelConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Switch } from "antd"; +import { Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -18,6 +18,7 @@ export type AccessForm1PanelConfigProps = { const initFormModel = (): AccessForm1PanelConfigFieldValues => { return { apiUrl: "http://:20410/", + apiVersion: "v1", apiKey: "", }; }; @@ -27,6 +28,7 @@ const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialVal const formSchema = z.object({ apiUrl: z.string().url(t("common.errmsg.url_invalid")), + apiVersion: z.string().nonempty(t("access.form.1panel_api_version.placeholder")), apiKey: z .string() .min(1, t("access.form.1panel_api_key.placeholder")) @@ -53,6 +55,10 @@ const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialVal + + + + + } + > + + + + } + > + + + + ); +}; + +export default AccessFormACMECAConfig; diff --git a/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx b/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx index 03cf163a..57cbc22d 100644 --- a/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx +++ b/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx @@ -84,7 +84,7 @@ const AccessFormACMEHttpReqConfig = ({ form: formInst, formName, disabled, initi rules={[formRule]} tooltip={} > - + } > - + ); diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx index d03c0f1b..dd355b5e 100644 --- a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx +++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx @@ -27,11 +27,7 @@ const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initia const formSchema = z.object({ apiUrl: z.string().url(t("common.errmsg.url_invalid")), - apiKey: z - .string() - .min(1, t("access.form.baotapanel_api_key.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + apiKey: z.string().nonempty(t("access.form.baotapanel_api_key.placeholder")).trim(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/access/AccessFormBaotaWAFConfig.tsx b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx new file mode 100644 index 00000000..e87ed596 --- /dev/null +++ b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForBaotaWAF } from "@/domain/access"; + +type AccessFormBaotaWAFConfigFieldValues = Nullish; + +export type AccessFormBaotaWAFConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormBaotaWAFConfigFieldValues; + onValuesChange?: (values: AccessFormBaotaWAFConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormBaotaWAFConfigFieldValues => { + return { + apiUrl: "http://:8379/", + apiKey: "", + }; +}; + +const AccessFormBaotaWAFConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormBaotaWAFConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + apiKey: z.string().nonempty(t("access.form.baotawaf_api_key.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormBaotaWAFConfig; diff --git a/ui/src/components/access/AccessFormCloudflareConfig.tsx b/ui/src/components/access/AccessFormCloudflareConfig.tsx index a06d753d..79b33e3a 100644 --- a/ui/src/components/access/AccessFormCloudflareConfig.tsx +++ b/ui/src/components/access/AccessFormCloudflareConfig.tsx @@ -66,7 +66,7 @@ const AccessFormCloudflareConfig = ({ form: formInst, formName, disabled, initia rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormFlexCDNConfig.tsx b/ui/src/components/access/AccessFormFlexCDNConfig.tsx new file mode 100644 index 00000000..6ca020bf --- /dev/null +++ b/ui/src/components/access/AccessFormFlexCDNConfig.tsx @@ -0,0 +1,90 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Radio, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForFlexCDN } from "@/domain/access"; + +type AccessFormFlexCDNConfigFieldValues = Nullish; + +export type AccessFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormFlexCDNConfigFieldValues; + onValuesChange?: (values: AccessFormFlexCDNConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormFlexCDNConfigFieldValues => { + return { + apiUrl: "http://:8000/", + apiRole: "user", + accessKeyId: "", + accessKey: "", + }; +}; + +const AccessFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + role: z.union([z.literal("user"), z.literal("admin")], { + message: t("access.form.flexcdn_api_role.placeholder"), + }), + accessKeyId: z.string().nonempty(t("access.form.flexcdn_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.flexcdn_access_key.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + ({ label: t(`access.form.flexcdn_api_role.option.${s}.label`), value: s }))} /> + + + } + > + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormFlexCDNConfig; diff --git a/ui/src/components/access/AccessFormGoEdgeConfig.tsx b/ui/src/components/access/AccessFormGoEdgeConfig.tsx index ced9b09a..9c03f2be 100644 --- a/ui/src/components/access/AccessFormGoEdgeConfig.tsx +++ b/ui/src/components/access/AccessFormGoEdgeConfig.tsx @@ -32,16 +32,8 @@ const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialVal role: z.union([z.literal("user"), z.literal("admin")], { message: t("access.form.goedge_api_role.placeholder"), }), - accessKeyId: z - .string() - .min(1, t("access.form.goedge_access_key_id.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), - accessKey: z - .string() - .min(1, t("access.form.goedge_access_key.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + accessKeyId: z.string().nonempty(t("access.form.goedge_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.goedge_access_key.placeholder")).trim(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/access/AccessFormLeCDNConfig.tsx b/ui/src/components/access/AccessFormLeCDNConfig.tsx new file mode 100644 index 00000000..4af5a639 --- /dev/null +++ b/ui/src/components/access/AccessFormLeCDNConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Radio, Select, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForLeCDN } from "@/domain/access"; + +type AccessFormLeCDNConfigFieldValues = Nullish; + +export type AccessFormLeCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormLeCDNConfigFieldValues; + onValuesChange?: (values: AccessFormLeCDNConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormLeCDNConfigFieldValues => { + return { + apiUrl: "http://:5090/", + apiVersion: "v3", + apiRole: "user", + username: "", + password: "", + }; +}; + +const AccessFormLeCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormLeCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + role: z.union([z.literal("client"), z.literal("master")], { + message: t("access.form.lecdn_api_role.placeholder"), + }), + username: z.string().nonempty(t("access.form.lecdn_username.placeholder")).trim(), + password: z.string().nonempty(t("access.form.lecdn_password.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + + + + + + + + + + +
+ ); +}; + +export default AccessFormLeCDNConfig; diff --git a/ui/src/components/access/AccessFormProxmoxVEConfig.tsx b/ui/src/components/access/AccessFormProxmoxVEConfig.tsx index afdc02de..d0a66745 100644 --- a/ui/src/components/access/AccessFormProxmoxVEConfig.tsx +++ b/ui/src/components/access/AccessFormProxmoxVEConfig.tsx @@ -65,7 +65,7 @@ const AccessFormProxmoxVEConfig = ({ form: formInst, formName, disabled, initial rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/access/AccessFormRatPanelConfig.tsx b/ui/src/components/access/AccessFormRatPanelConfig.tsx new file mode 100644 index 00000000..ca3d2182 --- /dev/null +++ b/ui/src/components/access/AccessFormRatPanelConfig.tsx @@ -0,0 +1,82 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForRatPanel } from "@/domain/access"; + +type AccessFormRatPanelConfigFieldValues = Nullish; + +export type AccessFormRatPanelConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormRatPanelConfigFieldValues; + onValuesChange?: (values: AccessFormRatPanelConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormRatPanelConfigFieldValues => { + return { + apiUrl: "http://:8888/", + accessTokenId: 1, + accessToken: "", + }; +}; + +const AccessFormRatPanelConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormRatPanelConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + accessTokenId: z.preprocess((v) => Number(v), z.number().positive(t("access.form.ratpanel_access_token_id.placeholder"))), + accessToken: z.string().nonempty(t("access.form.ratpanel_access_token.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormRatPanelConfig; diff --git a/ui/src/components/access/AccessFormSSHConfig.tsx b/ui/src/components/access/AccessFormSSHConfig.tsx index db1790a4..32d1b8bc 100644 --- a/ui/src/components/access/AccessFormSSHConfig.tsx +++ b/ui/src/components/access/AccessFormSSHConfig.tsx @@ -1,5 +1,6 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, InputNumber } from "antd"; +import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons"; +import { Button, Collapse, Form, type FormInstance, Input, InputNumber, Space } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -39,7 +40,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues ), username: z .string() - .min(1, "access.form.ssh_username.placeholder") + .min(1, t("access.form.ssh_username.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })), password: z .string() @@ -54,6 +55,46 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues .max(20480, t("common.errmsg.string_max", { max: 20480 })) .nullish() .refine((v) => !v || formInst.getFieldValue("key"), t("access.form.ssh_key.placeholder")), + jumpServers: z + .array( + z + .object({ + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + port: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("access.form.ssh_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + username: z + .string() + .min(1, t("access.form.ssh_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + password: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .nullish(), + key: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + keyPassphrase: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + }) + .superRefine((data, ctx) => { + if (data.keyPassphrase && !data.key) { + ctx.addIssue({ + path: ["keyPassphrase"], + code: z.ZodIssueCode.custom, + message: t("access.form.ssh_key.placeholder"), + }); + } + }) + ) + .nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -114,6 +155,123 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues >
+ + + + {(fields, { add, remove, move }) => ( + + {fields?.length > 0 ? ( + { + const Label = () => { + const host = Form.useWatch(["jumpServers", field.name, "host"], formInst); + const port = Form.useWatch(["jumpServers", field.name, "port"], formInst); + const addr = !!host && !!port ? `${host}:${port}` : host ? host : port ? `:${port}` : "unknown"; + return ( + + [{t("access.form.ssh_jump_servers.item.label")} {field.name + 1}] {addr} + + ); + }; + + return { + key: field.key, + label: + )} + + ); }; diff --git a/ui/src/components/access/AccessFormTelegramConfig.tsx b/ui/src/components/access/AccessFormTelegramBotConfig.tsx similarity index 65% rename from ui/src/components/access/AccessFormTelegramConfig.tsx rename to ui/src/components/access/AccessFormTelegramBotConfig.tsx index a4eccafb..3181c71d 100644 --- a/ui/src/components/access/AccessFormTelegramConfig.tsx +++ b/ui/src/components/access/AccessFormTelegramBotConfig.tsx @@ -3,25 +3,25 @@ import { Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import { type AccessConfigForTelegram } from "@/domain/access"; +import { type AccessConfigForTelegramBot } from "@/domain/access"; -type AccessFormTelegramConfigFieldValues = Nullish; +type AccessFormTelegramBotConfigFieldValues = Nullish; -export type AccessFormTelegramConfigProps = { +export type AccessFormTelegramBotConfigProps = { form: FormInstance; formName: string; disabled?: boolean; - initialValues?: AccessFormTelegramConfigFieldValues; - onValuesChange?: (values: AccessFormTelegramConfigFieldValues) => void; + initialValues?: AccessFormTelegramBotConfigFieldValues; + onValuesChange?: (values: AccessFormTelegramBotConfigFieldValues) => void; }; -const initFormModel = (): AccessFormTelegramConfigFieldValues => { +const initFormModel = (): AccessFormTelegramBotConfigFieldValues => { return { botToken: "", }; }; -const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormTelegramConfigProps) => { +const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormTelegramBotConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -37,8 +37,8 @@ const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialV .nullish() .refine((v) => { if (v == null || v + "" === "") return true; - return /^\d+$/.test(v + "") && +v! > 0; - }, t("access.form.telegram_default_chat_id.placeholder")) + return !Number.isNaN(+v!) && +v! !== 0; + }, t("access.form.telegram_bot_default_chat_id.placeholder")) ) .nullish(), }); @@ -68,14 +68,14 @@ const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialV } + tooltip={} > - + ); }; -export default AccessFormTelegramConfig; +export default AccessFormTelegramBotConfig; diff --git a/ui/src/components/access/AccessFormUCloudConfig.tsx b/ui/src/components/access/AccessFormUCloudConfig.tsx index 495d21b9..fd623925 100644 --- a/ui/src/components/access/AccessFormUCloudConfig.tsx +++ b/ui/src/components/access/AccessFormUCloudConfig.tsx @@ -81,7 +81,7 @@ const AccessFormUCloudConfig = ({ form: formInst, formName, disabled, initialVal rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormVercelConfig.tsx b/ui/src/components/access/AccessFormVercelConfig.tsx index b1ed7b6f..4483a9f9 100644 --- a/ui/src/components/access/AccessFormVercelConfig.tsx +++ b/ui/src/components/access/AccessFormVercelConfig.tsx @@ -66,7 +66,7 @@ const AccessFormVercelConfig = ({ form: formInst, formName, disabled, initialVal rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 69286aa8..6e6ec87a 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -67,7 +67,6 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa } return true; }, t("access.form.webhook_headers.errmsg.invalid")), - allowInsecureConnections: z.boolean().nullish(), defaultDataForDeployment: z .string() .nullish() @@ -96,11 +95,12 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa return false; } }, t("access.form.webhook_default_data.errmsg.json_invalid")), + allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); - const handleWebhookHeadersBlur = (e: React.FocusEvent) => { - let value = e.target.value; + const handleWebhookHeadersBlur = () => { + let value = formInst.getFieldValue("headers"); value = value.trim(); value = value.replace(/(?", - device_keys: "", + device_key: "", }, null, 2 @@ -280,7 +279,13 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index 1023bf16..2fc0d9d0 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -42,7 +42,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { - + diff --git a/ui/src/components/provider/DeploymentProviderPicker.tsx b/ui/src/components/provider/DeploymentProviderPicker.tsx index 0ea5a97c..b1bcd6fe 100644 --- a/ui/src/components/provider/DeploymentProviderPicker.tsx +++ b/ui/src/components/provider/DeploymentProviderPicker.tsx @@ -72,8 +72,10 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeho DEPLOYMENT_CATEGORIES.LOADBALANCE, DEPLOYMENT_CATEGORIES.FIREWALL, DEPLOYMENT_CATEGORIES.AV, + DEPLOYMENT_CATEGORIES.APIGATEWAY, DEPLOYMENT_CATEGORIES.SERVERLESS, DEPLOYMENT_CATEGORIES.WEBSITE, + DEPLOYMENT_CATEGORIES.SSL, DEPLOYMENT_CATEGORIES.NAS, DEPLOYMENT_CATEGORIES.OTHER, ].map((key) => ({ diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 9e4ade18..49ed12ec 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -27,6 +27,7 @@ import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDC import DeployNodeConfigFormAliyunDDoSConfig from "./DeployNodeConfigFormAliyunDDoSConfig"; import DeployNodeConfigFormAliyunESAConfig from "./DeployNodeConfigFormAliyunESAConfig"; import DeployNodeConfigFormAliyunFCConfig from "./DeployNodeConfigFormAliyunFCConfig"; +import DeployNodeConfigFormAliyunGAConfig from "./DeployNodeConfigFormAliyunGAConfig"; import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig"; import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; @@ -41,11 +42,13 @@ import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaidu import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig"; import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig"; import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig"; +import DeployNodeConfigFormBaotaWAFSiteConfig from "./DeployNodeConfigFormBaotaWAFSiteConfig"; import DeployNodeConfigFormBunnyCDNConfig from "./DeployNodeConfigFormBunnyCDNConfig.tsx"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig"; +import DeployNodeConfigFormFlexCDNConfig from "./DeployNodeConfigFormFlexCDNConfig"; import DeployNodeConfigFormGcoreCDNConfig from "./DeployNodeConfigFormGcoreCDNConfig"; import DeployNodeConfigFormGoEdgeConfig from "./DeployNodeConfigFormGoEdgeConfig"; import DeployNodeConfigFormHuaweiCloudCDNConfig from "./DeployNodeConfigFormHuaweiCloudCDNConfig"; @@ -56,6 +59,7 @@ import DeployNodeConfigFormJDCloudCDNConfig from "./DeployNodeConfigFormJDCloudC import DeployNodeConfigFormJDCloudLiveConfig from "./DeployNodeConfigFormJDCloudLiveConfig"; import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudVODConfig"; import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig"; +import DeployNodeConfigFormLeCDNConfig from "./DeployNodeConfigFormLeCDNConfig"; import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig"; import DeployNodeConfigFormNetlifySiteConfig from "./DeployNodeConfigFormNetlifySiteConfig"; import DeployNodeConfigFormProxmoxVEConfig from "./DeployNodeConfigFormProxmoxVEConfig"; @@ -63,6 +67,7 @@ import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNCo import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig"; import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormRainYunRCDNConfig from "./DeployNodeConfigFormRainYunRCDNConfig"; +import DeployNodeConfigFormRatPanelSiteConfig from "./DeployNodeConfigFormRatPanelSiteConfig"; import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx"; @@ -87,7 +92,9 @@ import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolc import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx"; import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx"; import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx"; +import DeployNodeConfigFormWangsuCDNConfig from "./DeployNodeConfigFormWangsuCDNConfig.tsx"; import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx"; +import DeployNodeConfigFormWangsuCertificateConfig from "./DeployNodeConfigFormWangsuCertificateConfig.tsx"; import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx"; type DeployNodeConfigFormFieldValues = Partial; @@ -201,6 +208,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.ALIYUN_FC: return ; + case DEPLOYMENT_PROVIDERS.ALIYUN_GA: + return ; case DEPLOYMENT_PROVIDERS.ALIYUN_LIVE: return ; case DEPLOYMENT_PROVIDERS.ALIYUN_NLB: @@ -229,6 +238,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE: return ; + case DEPLOYMENT_PROVIDERS.BAOTAWAF_SITE: + return ; case DEPLOYMENT_PROVIDERS.BUNNY_CDN: return ; case DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN: @@ -239,6 +250,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS: return ; + case DEPLOYMENT_PROVIDERS.FLEXCDN: + return ; case DEPLOYMENT_PROVIDERS.GCORE_CDN: return ; case DEPLOYMENT_PROVIDERS.GOEDGE: @@ -259,6 +272,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET: return ; + case DEPLOYMENT_PROVIDERS.LECDN: + return ; case DEPLOYMENT_PROVIDERS.LOCAL: return ; case DEPLOYMENT_PROVIDERS.NETLIFY_SITE: @@ -273,6 +288,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.RAINYUN_RCDN: return ; + case DEPLOYMENT_PROVIDERS.RATPANEL_SITE: + return ; case DEPLOYMENT_PROVIDERS.SAFELINE: return ; case DEPLOYMENT_PROVIDERS.SSH: @@ -321,8 +338,12 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS: return ; + case DEPLOYMENT_PROVIDERS.WANGSU_CDN: + return ; case DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO: return ; + case DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE: + return ; case DEPLOYMENT_PROVIDERS.WEBHOOK: return ; } diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx index f0964493..2e539453 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx @@ -28,7 +28,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled, .string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") }) .nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder")) .trim(), - certificateArn: z.string({ message: t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder") }).nullish(), + certificateArn: z.string().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -60,7 +60,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled, rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx index 3afcb7a1..bbfca5e6 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormAliyunALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx index 2c2e43b6..e666800e 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormAliyunCLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx new file mode 100644 index 00000000..20dd1ae1 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx @@ -0,0 +1,118 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormAliyunGAConfigFieldValues = Nullish<{ + resourceType: string; + acceleratorId?: string; + listenerId?: string; + domain?: string; +}>; + +export type DeployNodeConfigFormAliyunGAConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunGAConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunGAConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_ACCELERATOR = "accelerator" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const initFormModel = (): DeployNodeConfigFormAliyunGAConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunGAConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAliyunGAConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.union([z.literal(RESOURCE_TYPE_ACCELERATOR), z.literal(RESOURCE_TYPE_LISTENER)], { + message: t("workflow_node.deploy.form.aliyun_ga_resource_type.placeholder"), + }), + acceleratorId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + listenerId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim() + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_ga_listener_id.placeholder")), + domain: z + .string() + .nullish() + .refine((v) => { + if (![RESOURCE_TYPE_ACCELERATOR, RESOURCE_TYPE_LISTENER].includes(fieldResourceType)) return true; + return !v || validDomainName(v!); + }, t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + } + > + + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunGAConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx index 5f81cf71..a46c7327 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx @@ -102,7 +102,7 @@ const DeployNodeConfigFormAliyunWAFConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx index 2a54bb99..bd2347df 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx @@ -35,7 +35,7 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({ .nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")) .trim(), certificateName: z - .string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") }) + .string() .nullish() .refine((v) => { if (!v) return true; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx index 7bd40b82..875d254b 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx @@ -135,7 +135,7 @@ const DeployNodeConfigFormBaiduCloudAppBLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx index 20bb22f1..99c0b059 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx @@ -135,7 +135,7 @@ const DeployNodeConfigFormBaiduCloudBLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx index ad05b6a8..7d32bef5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx @@ -73,7 +73,7 @@ const DeployNodeConfigFormBaishanCDNConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx new file mode 100644 index 00000000..6f992fb8 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx @@ -0,0 +1,78 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, InputNumber } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validPortNumber } from "@/utils/validators"; + +type DeployNodeConfigFormBaotaWAFSiteConfigFieldValues = Nullish<{ + siteName: string; + sitePort: number; +}>; + +export type DeployNodeConfigFormBaotaWAFSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormBaotaWAFSiteConfigFieldValues => { + return { + siteName: "", + sitePort: 443, + }; +}; + +const DeployNodeConfigFormBaotaWAFSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormBaotaWAFSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.baotawaf_site_name.placeholder")).trim(), + sitePort: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("workflow_node.deploy.form.baotawaf_site_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + + +
+ ); +}; + +export default DeployNodeConfigFormBaotaWAFSiteConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx index 45662e75..4d6ae25c 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx @@ -79,13 +79,23 @@ const DeployNodeConfigFormCdnflyConfig = ({ form: formInst, formName, disabled, - + } + > - + } + > diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx new file mode 100644 index 00000000..e24652be --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx @@ -0,0 +1,84 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormFlexCDNConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; +}>; + +export type DeployNodeConfigFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormFlexCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormFlexCDNConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormFlexCDNConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.flexcdn_resource_type.placeholder"), + }), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.flexcdn_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormFlexCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx index 4d548949..f21a4bb9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx @@ -67,7 +67,7 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx index 89dffb5f..5e0f7f85 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx @@ -68,7 +68,12 @@ const DeployNodeConfigFormGoEdgeConfig = ({ form: formInst, formName, disabled, - + } + > diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx index f54477ce..22c5bf08 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormJDCloudALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx new file mode 100644 index 00000000..0636cedf --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx @@ -0,0 +1,103 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormLeCDNConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; + clientId?: string | number; +}>; + +export type DeployNodeConfigFormLeCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormLeCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormLeCDNConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormLeCDNConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + clientId: "", + }; +}; + +const DeployNodeConfigFormLeCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormLeCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.lecdn_resource_type.placeholder"), + }), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.lecdn_certificate_id.placeholder")), + clientId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + if (v == null || v === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.lecdn_client_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormLeCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx index 75853eb7..282503e5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx @@ -351,7 +351,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i rules={[formRule]} tooltip={} > - + } > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx new file mode 100644 index 00000000..03f86913 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx @@ -0,0 +1,63 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormRatPanelSiteConfigFieldValues = Nullish<{ + siteName: string; +}>; + +export type DeployNodeConfigFormRatPanelSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormRatPanelSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormRatPanelSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormRatPanelSiteConfigFieldValues => { + return { + siteName: "", + }; +}; + +const DeployNodeConfigFormRatPanelSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormRatPanelSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.ratpanel_site_name.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormRatPanelSiteConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index e99a2431..49110ce9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -363,7 +363,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini rules={[formRule]} tooltip={} > - + } > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx index 2da3ef16..c5905e14 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx @@ -68,7 +68,12 @@ const DeployNodeConfigFormSafeLineConfig = ({ form: formInst, formName, disabled - + } + > diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx index 2e7dc127..760c6fac 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx @@ -144,7 +144,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx index 348f4d8d..650323ab 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx @@ -140,7 +140,7 @@ const DeployNodeConfigFormVolcEngineALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx new file mode 100644 index 00000000..57d0d381 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx @@ -0,0 +1,146 @@ +import { memo } from "react"; +import { useTranslation } from "react-i18next"; +import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; +import { Button, Form, type FormInstance, Input, Space } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import ModalForm from "@/components/ModalForm"; +import MultipleInput from "@/components/MultipleInput"; +import { useAntdForm } from "@/hooks"; +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormWangsuCDNConfigFieldValues = Nullish<{ + domains: string; +}>; + +export type DeployNodeConfigFormWangsuCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormWangsuCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCDNConfigFieldValues) => void; +}; + +const MULTIPLE_INPUT_DELIMITER = ";"; + +const initFormModel = (): DeployNodeConfigFormWangsuCDNConfigFieldValues => { + return { + domains: "", + }; +}; + +const DeployNodeConfigFormWangsuCDNConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormWangsuCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domains: z + .string() + .nullish() + .refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => validDomainName(e)); + }, t("workflow_node.deploy.form.wangsu_cdn_domains.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldDomains = Form.useWatch("domains", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + { + formInst.setFieldValue("domains", e.target.value); + }} + onClear={() => { + formInst.setFieldValue("domains", ""); + }} + /> + + + + + } + onChange={(value) => { + formInst.setFieldValue("domains", value); + }} + /> + + +
+ ); +}; + +const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domains: z.array(z.string()).refine((v) => { + return v.every((e) => validDomainName(e)); + }, t("workflow_node.deploy.form.wangsu_cdn_domains.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + name: "workflowNodeDeployConfigFormWangsuCDNNamesModalInput", + initialValues: { domains: value?.split(MULTIPLE_INPUT_DELIMITER) }, + onSubmit: (values) => { + onChange?.( + values.domains + .map((e) => e.trim()) + .filter((e) => !!e) + .join(MULTIPLE_INPUT_DELIMITER) + ); + }, + }); + + return ( + + + + + + ); +}); + +export default DeployNodeConfigFormWangsuCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx index 90bdb064..e89e1e8d 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx @@ -5,37 +5,37 @@ import { z } from "zod"; import { validDomainName } from "@/utils/validators"; -type DeployNodeConfigFormBaishanCDNConfigFieldValues = Nullish<{ +type DeployNodeConfigFormWangsuCDNProConfigFieldValues = Nullish<{ environment: string; domain: string; certificateId?: string; webhookId?: string; }>; -export type DeployNodeConfigFormBaishanCDNConfigProps = { +export type DeployNodeConfigFormWangsuCDNProConfigProps = { form: FormInstance; formName: string; disabled?: boolean; - initialValues?: DeployNodeConfigFormBaishanCDNConfigFieldValues; - onValuesChange?: (values: DeployNodeConfigFormBaishanCDNConfigFieldValues) => void; + initialValues?: DeployNodeConfigFormWangsuCDNProConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCDNProConfigFieldValues) => void; }; const ENVIRONMENT_PRODUCTION = "production" as const; const ENVIRONMENT_STAGING = "stating" as const; -const initFormModel = (): DeployNodeConfigFormBaishanCDNConfigFieldValues => { +const initFormModel = (): DeployNodeConfigFormWangsuCDNProConfigFieldValues => { return { environment: ENVIRONMENT_PRODUCTION, }; }; -const DeployNodeConfigFormBaishanCDNConfig = ({ +const DeployNodeConfigFormWangsuCDNProConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange, -}: DeployNodeConfigFormBaishanCDNConfigProps) => { +}: DeployNodeConfigFormWangsuCDNProConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -89,7 +89,7 @@ const DeployNodeConfigFormBaishanCDNConfig = ({ rules={[formRule]} tooltip={} > - + } > - + ); }; -export default DeployNodeConfigFormBaishanCDNConfig; +export default DeployNodeConfigFormWangsuCDNProConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx new file mode 100644 index 00000000..739e4b28 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.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"; + +type DeployNodeConfigFormWangsuCertificateConfigFieldValues = Nullish<{ + certificateId?: string; +}>; + +export type DeployNodeConfigFormWangsuCertificateConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormWangsuCertificateConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCertificateConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormWangsuCertificateConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormWangsuCertificateConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormWangsuCertificateConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + certificateId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormWangsuCertificateConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx index 32488aeb..d303c2ba 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx @@ -19,7 +19,7 @@ import { useNotifyChannelsStore } from "@/stores/notify"; import NotifyNodeConfigFormEmailConfig from "./NotifyNodeConfigFormEmailConfig"; import NotifyNodeConfigFormMattermostConfig from "./NotifyNodeConfigFormMattermostConfig"; -import NotifyNodeConfigFormTelegramConfig from "./NotifyNodeConfigFormTelegramConfig"; +import NotifyNodeConfigFormTelegramBotConfig from "./NotifyNodeConfigFormTelegramBotConfig"; import NotifyNodeConfigFormWebhookConfig from "./NotifyNodeConfigFormWebhookConfig"; type NotifyNodeConfigFormFieldValues = Partial; @@ -114,8 +114,8 @@ const NotifyNodeConfigForm = forwardRef; case NOTIFICATION_PROVIDERS.MATTERMOST: return ; - case NOTIFICATION_PROVIDERS.TELEGRAM: - return ; + case NOTIFICATION_PROVIDERS.TELEGRAMBOT: + return ; case NOTIFICATION_PROVIDERS.WEBHOOK: return ; } diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx similarity index 55% rename from ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx rename to ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx index 07774413..a40142ee 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx @@ -3,23 +3,29 @@ import { Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -type NotifyNodeConfigFormTelegramConfigFieldValues = Nullish<{ +type NotifyNodeConfigFormTelegramBotConfigFieldValues = Nullish<{ chatId?: string | number; }>; -export type NotifyNodeConfigFormTelegramConfigProps = { +export type NotifyNodeConfigFormTelegramBotConfigProps = { form: FormInstance; formName: string; disabled?: boolean; - initialValues?: NotifyNodeConfigFormTelegramConfigFieldValues; - onValuesChange?: (values: NotifyNodeConfigFormTelegramConfigFieldValues) => void; + initialValues?: NotifyNodeConfigFormTelegramBotConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormTelegramBotConfigFieldValues) => void; }; -const initFormModel = (): NotifyNodeConfigFormTelegramConfigFieldValues => { +const initFormModel = (): NotifyNodeConfigFormTelegramBotConfigFieldValues => { return {}; }; -const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormTelegramConfigProps) => { +const NotifyNodeConfigFormTelegramBotConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: NotifyNodeConfigFormTelegramBotConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -31,8 +37,8 @@ const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled .nullish() .refine((v) => { if (v == null || v + "" === "") return true; - return /^\d+$/.test(v + "") && +v! > 0; - }, t("workflow_node.notify.form.telegram_chat_id.placeholder")) + return !Number.isNaN(+v!) && +v! !== 0; + }, t("workflow_node.notify.form.telegram_bot_chat_id.placeholder")) ) .nullish(), }); @@ -53,14 +59,14 @@ const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled > } + tooltip={} > - + ); }; -export default NotifyNodeConfigFormTelegramConfig; +export default NotifyNodeConfigFormTelegramBotConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 0d516385..f60f39c8 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -7,6 +7,7 @@ export interface AccessModel extends BaseModel { */ Record & ( | AccessConfigFor1Panel + | AccessConfigForACMECA | AccessConfigForACMEHttpReq | AccessConfigForAliyun | AccessConfigForAWS @@ -14,6 +15,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForBaiduCloud | AccessConfigForBaishan | AccessConfigForBaotaPanel + | AccessConfigForBaotaWAF | AccessConfigForBunny | AccessConfigForBytePlus | AccessConfigForCacheFly @@ -28,6 +30,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForDynv6 | AccessConfigForEdgio | AccessConfigForEmail + | AccessConfigForFlexCDN | AccessConfigForGcore | AccessConfigForGname | AccessConfigForGoDaddy @@ -37,6 +40,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForJDCloud | AccessConfigForKubernetes | AccessConfigForLarkBot + | AccessConfigForLeCDN | AccessConfigForMattermost | AccessConfigForNamecheap | AccessConfigForNameDotCom @@ -48,10 +52,11 @@ export interface AccessModel extends BaseModel { | AccessConfigForProxmoxVE | AccessConfigForQiniu | AccessConfigForRainYun + | AccessConfigForRatPanel | AccessConfigForSafeLine | AccessConfigForSSH | AccessConfigForSSLCom - | AccessConfigForTelegram + | AccessConfigForTelegramBot | AccessConfigForTencentCloud | AccessConfigForUCloud | AccessConfigForUpyun @@ -69,10 +74,17 @@ export interface AccessModel extends BaseModel { // #region AccessConfig export type AccessConfigFor1Panel = { apiUrl: string; + apiVersion: string; apiKey: string; allowInsecureConnections?: boolean; }; +export type AccessConfigForACMECA = { + endpoint: string; + eabKid?: string; + eabHmacKey?: string; +}; + export type AccessConfigForACMEHttpReq = { endpoint: string; mode?: string; @@ -112,6 +124,12 @@ export type AccessConfigForBaotaPanel = { allowInsecureConnections?: boolean; }; +export type AccessConfigForBaotaWAF = { + apiUrl: string; + apiKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForBunny = { apiKey: string; }; @@ -185,6 +203,14 @@ export type AccessConfigForEmail = { defaultReceiverAddress?: string; }; +export type AccessConfigForFlexCDN = { + apiUrl: string; + apiRole: string; + accessKeyId: string; + accessKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForGcore = { apiToken: string; }; @@ -230,6 +256,15 @@ export type AccessConfigForLarkBot = { webhookUrl: string; }; +export type AccessConfigForLeCDN = { + apiUrl: string; + apiVersion: string; + apiRole: string; + username: string; + password: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForMattermost = { serverUrl: string; username: string; @@ -292,6 +327,13 @@ export type AccessConfigForRainYun = { apiKey: string; }; +export type AccessConfigForRatPanel = { + apiUrl: string; + accessTokenId: number; + accessToken: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForSafeLine = { apiUrl: string; apiToken: string; @@ -312,7 +354,7 @@ export type AccessConfigForSSLCom = { eabHmacKey: string; }; -export type AccessConfigForTelegram = { +export type AccessConfigForTelegramBot = { botToken: string; defaultChatId?: number; }; diff --git a/ui/src/domain/certificate.ts b/ui/src/domain/certificate.ts index 563d2476..c4bc8710 100644 --- a/ui/src/domain/certificate.ts +++ b/ui/src/domain/certificate.ts @@ -6,7 +6,7 @@ export interface CertificateModel extends BaseModel { serialNumber: string; certificate: string; privateKey: string; - issuer: string; + issuerOrg: string; keyAlgorithm: string; effectAt: ISO8601String; expireAt: ISO8601String; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index fbac84ba..15542455 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -5,6 +5,7 @@ */ export const ACCESS_PROVIDERS = Object.freeze({ ["1PANEL"]: "1panel", + ACMECA: "acmeca", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", AWS: "aws", @@ -12,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ BAIDUCLOUD: "baiducloud", BAISHAN: "baishan", BAOTAPANEL: "baotapanel", + BAOTAWAF: "baotawaf", BUNNY: "bunny", BYTEPLUS: "byteplus", BUYPASS: "buypass", @@ -27,6 +29,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ DYNV6: "dynv6", EDGIO: "edgio", EMAIL: "email", + FLEXCDN: "flexcdn", GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", @@ -36,6 +39,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ JDCLOUD: "jdcloud", KUBERNETES: "k8s", LARKBOT: "larkbot", + LECDN: "lecdn", LETSENCRYPT: "letsencrypt", LETSENCRYPTSTAGING: "letsencryptstaging", LOCAL: "local", @@ -51,10 +55,11 @@ export const ACCESS_PROVIDERS = Object.freeze({ PROXMOXVE: "proxmoxve", QINIU: "qiniu", RAINYUN: "rainyun", + RATPANEL: "ratpanel", SAFELINE: "safeline", SSH: "ssh", SSLCOM: "sslcom", - TELEGRAM: "telegram", + TELEGRAMBOT: "telegrambot", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", UPYUN: "upyun", @@ -120,10 +125,14 @@ export const accessProvidersMap: Map [ e[0] as string, { @@ -177,6 +187,7 @@ export const accessProvidersMap: Map = new [CA_PROVIDERS.GOOGLETRUSTSERVICES], [CA_PROVIDERS.SSLCOM], [CA_PROVIDERS.ZEROSSL], + [CA_PROVIDERS.ACMECA], ].map(([type, builtin]) => [ type, { @@ -345,6 +357,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ ALIYUN_DDOS: `${ACCESS_PROVIDERS.ALIYUN}-ddospro`, ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`, ALIYUN_FC: `${ACCESS_PROVIDERS.ALIYUN}-fc`, + ALIYUN_GA: `${ACCESS_PROVIDERS.ALIYUN}-ga`, ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`, ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`, ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`, @@ -360,12 +373,15 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ BAISHAN_CDN: `${ACCESS_PROVIDERS.BAISHAN}-cdn`, BAOTAPANEL_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANEL}-console`, BAOTAPANEL_SITE: `${ACCESS_PROVIDERS.BAOTAPANEL}-site`, + BAOTAWAF_CONSOLE: `${ACCESS_PROVIDERS.BAOTAWAF}-console`, + BAOTAWAF_SITE: `${ACCESS_PROVIDERS.BAOTAWAF}-site`, BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`, BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`, CACHEFLY: `${ACCESS_PROVIDERS.CACHEFLY}`, CDNFLY: `${ACCESS_PROVIDERS.CDNFLY}`, DOGECLOUD_CDN: `${ACCESS_PROVIDERS.DOGECLOUD}-cdn`, EDGIO_APPLICATIONS: `${ACCESS_PROVIDERS.EDGIO}-applications`, + FLEXCDN: `${ACCESS_PROVIDERS.FLEXCDN}`, GCORE_CDN: `${ACCESS_PROVIDERS.GCORE}-cdn`, GOEDGE: `${ACCESS_PROVIDERS.GOEDGE}`, HUAWEICLOUD_CDN: `${ACCESS_PROVIDERS.HUAWEICLOUD}-cdn`, @@ -377,6 +393,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ JDCLOUD_LIVE: `${ACCESS_PROVIDERS.JDCLOUD}-live`, JDCLOUD_VOD: `${ACCESS_PROVIDERS.JDCLOUD}-vod`, KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`, + LECDN: `${ACCESS_PROVIDERS.LECDN}`, LOCAL: `${ACCESS_PROVIDERS.LOCAL}`, NETLIFY_SITE: `${ACCESS_PROVIDERS.NETLIFY}-site`, PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`, @@ -384,6 +401,8 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`, QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`, RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`, + RATPANEL_CONSOLE: `${ACCESS_PROVIDERS.RATPANEL}-console`, + RATPANEL_SITE: `${ACCESS_PROVIDERS.RATPANEL}-site`, SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`, SSH: `${ACCESS_PROVIDERS.SSH}`, TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`, @@ -409,7 +428,9 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`, VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`, VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`, + WANGSU_CDN: `${ACCESS_PROVIDERS.WANGSU}-cdn`, WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`, + WANGSU_CERTIFICATE: `${ACCESS_PROVIDERS.WANGSU}-certificate`, WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, } as const); @@ -422,8 +443,10 @@ export const DEPLOYMENT_CATEGORIES = Object.freeze({ LOADBALANCE: "loadbalance", FIREWALL: "firewall", AV: "av", + APIGATEWAY: "apigw", SERVERLESS: "serverless", WEBSITE: "website", + SSL: "ssl", NAS: "nas", OTHER: "other", } as const); @@ -461,9 +484,10 @@ export const deploymentProvidersMap: Map [ @@ -547,7 +579,7 @@ export const NOTIFICATION_PROVIDERS = Object.freeze({ EMAIL: `${ACCESS_PROVIDERS.EMAIL}`, LARKBOT: `${ACCESS_PROVIDERS.LARKBOT}`, MATTERMOST: `${ACCESS_PROVIDERS.MATTERMOST}`, - TELEGRAM: `${ACCESS_PROVIDERS.TELEGRAM}`, + TELEGRAMBOT: `${ACCESS_PROVIDERS.TELEGRAMBOT}`, WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, WECOMBOT: `${ACCESS_PROVIDERS.WECOMBOT}`, } as const); @@ -573,7 +605,7 @@ export const notificationProvidersMap: Map [ type, { diff --git a/ui/src/domain/version.ts b/ui/src/domain/version.ts index 312f45a1..442a7506 100644 --- a/ui/src/domain/version.ts +++ b/ui/src/domain/version.ts @@ -1 +1 @@ -export const version = "v0.3.12"; +export const version = "v0.3.13"; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index cff44b7c..41b81636 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -36,12 +36,21 @@ "access.form.notification_channel.placeholder": "Please select a notification channel", "access.form.1panel_api_url.label": "1Panel URL", "access.form.1panel_api_url.placeholder": "Please enter 1Panel URL", + "access.form.1panel_api_version.label": "1Panel version", + "access.form.1panel_api_version.placeholder": "Please select 1Panel version", "access.form.1panel_api_key.label": "1Panel API key", "access.form.1panel_api_key.placeholder": "Please enter 1Panel API key", "access.form.1panel_api_key.tooltip": "For more information, see https://docs.1panel.pro/dev_manual/api_manual/", "access.form.1panel_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.1panel_allow_insecure_conns.switch.on": "Allow", "access.form.1panel_allow_insecure_conns.switch.off": "Disallow", + "access.form.acmeca_endpoint.label": "Endpoint", + "access.form.acmeca_endpoint.placeholder": "Please enter endpoint", + "access.form.acmeca_endpoint.tooltip": "For more information, see https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1", + "access.form.acmeca_eab_kid.label": "ACME EAB KID (Optional)", + "access.form.acmeca_eab_kid.placeholder": "Please enter ACME EAB KID", + "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC key (Optional)", + "access.form.acmeca_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", @@ -103,6 +112,14 @@ "access.form.baotapanel_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.baotapanel_allow_insecure_conns.switch.on": "Allow", "access.form.baotapanel_allow_insecure_conns.switch.off": "Disallow", + "access.form.baotawaf_api_url.label": "aaWAF URL", + "access.form.baotawaf_api_url.placeholder": "Please enter aaWAF URL", + "access.form.baotawaf_api_key.label": "aaWAF API key", + "access.form.baotawaf_api_key.placeholder": "Please enter aaWAF API key", + "access.form.baotawaf_api_key.tooltip": "For more information, see https://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.baotawaf_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.baotawaf_allow_insecure_conns.switch.on": "Allow", + "access.form.baotawaf_allow_insecure_conns.switch.off": "Disallow", "access.form.byteplus_access_key.label": "BytePlus AccessKey", "access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey", "access.form.byteplus_access_key.tooltip": "For more information, see https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys", @@ -112,8 +129,8 @@ "access.form.cachefly_api_token.label": "CacheFly API token", "access.form.cachefly_api_token.placeholder": "Please enter CacheFly API token", "access.form.cachefly_api_token.tooltip": "For more information, see https://kb.cachefly.com/kb/guide/en/generating-tokens-and-keys-Oll9Irt5TI/Steps/2460228", - "access.form.cdnfly_api_url.label": "Cdnfly API URL", - "access.form.cdnfly_api_url.placeholder": "Please enter Cdnfly API URL", + "access.form.cdnfly_api_url.label": "Cdnfly URL", + "access.form.cdnfly_api_url.placeholder": "Please enter Cdnfly URL", "access.form.cdnfly_api_key.label": "Cdnfly user API key", "access.form.cdnfly_api_key.placeholder": "Please enter Cdnfly user API key", "access.form.cdnfly_api_key.tooltip": "For more information, see https://doc.cdnfly.cn/shiyongjieshao.html", @@ -184,6 +201,21 @@ "access.form.email_default_sender_address.placeholder": "Please enter default sender email address", "access.form.email_default_receiver_address.label": "Default receiver email address (Optional)", "access.form.email_default_receiver_address.placeholder": "Please enter default receiver email address", + "access.form.flexcdn_api_url.label": "FlexCDN URL", + "access.form.flexcdn_api_url.placeholder": "Please enter FlexCDN URL", + "access.form.flexcdn_api_role.label": "FlexCDN user role", + "access.form.flexcdn_api_role.placeholder": "Please select FlexCDN user role", + "access.form.flexcdn_api_role.option.user.label": "Platform user", + "access.form.flexcdn_api_role.option.admin.label": "Administrator user", + "access.form.flexcdn_access_key_id.label": "FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.placeholder": "Please enter FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.tooltip": "For more information, see https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "Please enter FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "For more information, see https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.flexcdn_allow_insecure_conns.switch.on": "Allow", + "access.form.flexcdn_allow_insecure_conns.switch.off": "Disallow", "access.form.gcore_api_token.label": "Gcore API token", "access.form.gcore_api_token.placeholder": "Please enter Gcore API token", "access.form.gcore_api_token.tooltip": "For more information, see https://api.gcore.com/docs/iam#section/Authentication", @@ -199,8 +231,8 @@ "access.form.godaddy_api_secret.label": "GoDaddy API secret", "access.form.godaddy_api_secret.placeholder": "Please enter GoDaddy API secret", "access.form.godaddy_api_secret.tooltip": "For more information, see https://developer.godaddy.com/", - "access.form.goedge_api_url.label": "GoEdge API URL", - "access.form.goedge_api_url.placeholder": "Please enter GoEdge API URL", + "access.form.goedge_api_url.label": "GoEdge URL", + "access.form.goedge_api_url.placeholder": "Please enter GoEdge URL", "access.form.goedge_api_role.label": "GoEdge user role", "access.form.goedge_api_role.placeholder": "Please select GoEdge user role", "access.form.goedge_api_role.option.user.label": "Platform user", @@ -238,6 +270,21 @@ "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", "access.form.larkbot_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", + "access.form.lecdn_api_url.label": "LeCDN URL", + "access.form.lecdn_api_url.placeholder": "Please enter LeCDN URL", + "access.form.lecdn_api_version.label": "LeCDN version", + "access.form.lecdn_api_version.placeholder": "Please select LeCDN version", + "access.form.lecdn_api_role.label": "LeCDN user role", + "access.form.lecdn_api_role.placeholder": "Please select LeCDN user role", + "access.form.lecdn_api_role.option.client.label": "Client", + "access.form.lecdn_api_role.option.master.label": "Master", + "access.form.lecdn_username.label": "LeCDN username", + "access.form.lecdn_username.placeholder": "Please enter LeCDN username", + "access.form.lecdn_password.label": "LeCDN password", + "access.form.lecdn_password.placeholder": "Please enter GoEdge password", + "access.form.lecdn_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.lecdn_allow_insecure_conns.switch.on": "Allow", + "access.form.lecdn_allow_insecure_conns.switch.off": "Disallow", "access.form.mattermost_server_url.label": "Mattermost server URL", "access.form.mattermost_server_url.placeholder": "Please enter Mattermost server URL", "access.form.mattermost_username.label": "Mattermost username", @@ -262,8 +309,8 @@ "access.form.namesilo_api_key.label": "NameSilo API key", "access.form.namesilo_api_key.placeholder": "Please enter NameSilo API key", "access.form.namesilo_api_key.tooltip": "For more information, see https://www.namesilo.com/support/v2/articles/account-options/api-manager", - "access.form.netlify_api_token.label": "netlify API token", - "access.form.netlify_api_token.placeholder": "Please enter netlify API token", + "access.form.netlify_api_token.label": "Netlify API token", + "access.form.netlify_api_token.placeholder": "Please enter Netlify API token", "access.form.netlify_api_token.tooltip": "For more information, see https://docs.netlify.com/api/get-started/#authentication", "access.form.netcup_customer_number.label": "netcup customer number", "access.form.netcup_customer_number.placeholder": "Please enter netcup customer number", @@ -283,8 +330,8 @@ "access.form.porkbun_secret_api_key.label": "Porkbun secret API key", "access.form.porkbun_secret_api_key.placeholder": "Please enter Porkbun secret API key", "access.form.porkbun_secret_api_key.tooltip": "For more information, see https://porkbun.com/api/json/v3/documentation", - "access.form.powerdns_api_url.label": "PowerDNS API URL", - "access.form.powerdns_api_url.placeholder": "Please enter PowerDNS API URL", + "access.form.powerdns_api_url.label": "PowerDNS URL", + "access.form.powerdns_api_url.placeholder": "Please enter PowerDNS URL", "access.form.powerdns_api_key.label": "PowerDNS API key", "access.form.powerdns_api_key.placeholder": "Please enter PowerDNS API key", "access.form.powerdns_api_key.tooltip": "For more information, see https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api", @@ -311,6 +358,17 @@ "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://app.rainyun.com/account/settings/api-key", + "access.form.ratpanel_api_url.label": "RatPanel URL", + "access.form.ratpanel_api_url.placeholder": "Please enter RatPanel URL", + "access.form.ratpanel_access_token_id.label": "RatPanel access token ID", + "access.form.ratpanel_access_token_id.placeholder": "Please enter RatPanel access token ID", + "access.form.ratpanel_access_token_id.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_access_token.label": "RatPanel access token", + "access.form.ratpanel_access_token.placeholder": "Please enter RatPanel access token", + "access.form.ratpanel_access_token.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.ratpanel_allow_insecure_conns.switch.on": "Allow", + "access.form.ratpanel_allow_insecure_conns.switch.off": "Disallow", "access.form.safeline_api_url.label": "SafeLine URL", "access.form.safeline_api_url.placeholder": "Please enter SafeLine URL", "access.form.safeline_api_token.label": "SafeLine API token", @@ -334,6 +392,9 @@ "access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)", "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.ssh_jump_servers.label": "SSH jump server (Optional)", + "access.form.ssh_jump_servers.item.label": "Jump server", + "access.form.ssh_jump_servers.add": "Add jump server", "access.form.sslcom_eab_kid.label": "ACME EAB KID", "access.form.sslcom_eab_kid.placeholder": "Please enter ACME EAB KID", "access.form.sslcom_eab_kid.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", @@ -343,9 +404,9 @@ "access.form.telegram_bot_token.label": "Telegram bot token", "access.form.telegram_bot_token.placeholder": "Please enter Telegram bot token", "access.form.telegram_bot_token.tooltip": "How to get the bot token? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", - "access.form.telegram_default_chat_id.label": "Default Telegram chat ID (Optional)", - "access.form.telegram_default_chat_id.placeholder": "Please enter default Telegram chat ID", - "access.form.telegram_default_chat_id.tooltip": "How to get the chat ID? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_bot_default_chat_id.label": "Default Telegram chat ID (Optional)", + "access.form.telegram_bot_default_chat_id.placeholder": "Please enter default Telegram chat ID", + "access.form.telegram_bot_default_chat_id.tooltip": "How to get the chat ID? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "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", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 6d67f55c..a3bbfecc 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -2,7 +2,8 @@ "provider.1panel": "1Panel", "provider.1panel.console": "1Panel - Console", "provider.1panel.site": "1Panel - Website", - "provider.acmehttpreq": "Http Request (ACME Proxy)", + "provider.acmeca": "ACME Custom CA Endpoint", + "provider.acmehttpreq": "ACME Custom HTTP Endpoint", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", "provider.aliyun.apigw": "Alibaba Cloud - API Gateway", @@ -15,6 +16,7 @@ "provider.aliyun.dns": "Alibaba Cloud - DNS (Domain Name Service)", "provider.aliyun.esa": "Alibaba Cloud - ESA (Edge Security Acceleration)", "provider.aliyun.fc": "Alibaba Cloud - FC (Function Compute)", + "provider.aliyun.ga": "Alibaba Cloud - GA (Global Accelerator)", "provider.aliyun.live": "Alibaba Cloud - ApsaraVideo Live", "provider.aliyun.nlb": "Alibaba Cloud - NLB (Network Load Balancer)", "provider.aliyun.oss": "Alibaba Cloud - OSS (Object Storage Service)", @@ -40,6 +42,9 @@ "provider.baotapanel": "aaPanel (aka BaoTaPanel)", "provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console", "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website", + "provider.baotawaf": "aaWAF (aka BaotaWAF)", + "provider.baotawaf.console": "aaWAF (aka BaotaWAF) - Console", + "provider.baotawaf.site": "aaWAF (aka BaotaWAF) - Website", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - CDN (Content Delivery Network)", "provider.byteplus": "BytePlus", @@ -62,6 +67,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "Email", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - CDN (Content Delivery Network)", "provider.gname": "GNAME", @@ -83,6 +89,7 @@ "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "Lark Bot", + "provider.lecdn": "LeCDN", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt Staging Environment", "provider.local": "Local deployment", @@ -91,8 +98,8 @@ "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", "provider.netcup": "netcup", - "provider.netlify": "netlify", - "provider.netlify.site": "netlify - Site", + "provider.netlify": "Netlify", + "provider.netlify.site": "Netlify - Site", "provider.ns1": "NS1 (IBM NS1 Connect)", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", @@ -103,10 +110,13 @@ "provider.qiniu.pili": "Qiniu - Pili", "provider.rainyun": "Rain Yun", "provider.rainyun.rcdn": "Rain Yun - RCDN (Rain Content Delivery Network)", + "provider.ratpanel": "RatPanel", + "provider.ratpanel.console": "RatPanel - Console", + "provider.ratpanel.site": "RatPanel - Website", "provider.safeline": "SafeLine", "provider.ssh": "SSH deployment", "provider.sslcom": "SSL.com", - "provider.telegram": "Telegram", + "provider.telegrambot": "Telegram Bot", "provider.tencentcloud": "Tencent Cloud", "provider.tencentcloud.cdn": "Tencent Cloud - CDN (Content Delivery Network)", "provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)", @@ -138,7 +148,9 @@ "provider.volcengine.live": "Volcengine - Live", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", "provider.wangsu": "Wangsu Cloud", - "provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro", + "provider.wangsu.cdn": "Wangsu Cloud - CDN (Content Delivery Network)", + "provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro (CDN 360)", + "provider.wangsu.certificate_upload": "Wangsu Cloud - Upload to Certificate Management", "provider.webhook": "Webhook", "provider.wecombot": "WeCom Bot", "provider.westcn": "West.cn", @@ -150,8 +162,10 @@ "provider.category.loadbalance": "Loadbalance", "provider.category.firewall": "Firewall", "provider.category.av": "Audio/Video", + "provider.category.apigw": "API Gateway", "provider.category.serverless": "Serverless", "provider.category.website": "Website", + "provider.category.ssl": "SSL", "provider.category.nas": "NAS", "provider.category.other": "Other", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index c9468b33..80237287 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -213,6 +213,19 @@ "workflow_node.deploy.form.aliyun_fc_domain.label": "Alibaba Cloud FC domain", "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "Please enter Alibaba Cloud FC domain name", "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "For more information, see https://fcnext.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_resource_type.label": "Resource type", + "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.accelerator.label": "GA accelerator", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.listener.label": "GA listener", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.label": "Alibaba Cloud GA accelerator ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.placeholder": "Please enter Alibaba Cloud GA accelerator ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.tooltip": "For more information, https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_listener_id.label": "Alibaba Cloud GA listener ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.placeholder": "Please enter Alibaba Cloud GA listener ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.tooltip": "For more information, https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_snidomain.label": "Alibaba Cloud GA SNI domain (Optional)", + "workflow_node.deploy.form.aliyun_ga_snidomain.placeholder": "Please enter Alibaba Cloud GA SNI domain name", + "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "For more information, https://ga.console.aliyun.com", "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud Live region", "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud Live region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions", @@ -325,13 +338,18 @@ "workflow_node.deploy.form.baotapanel_site_type.option.other.label": "Other sites", "workflow_node.deploy.form.baotapanel_site_name.label": "aaPanel site name", "workflow_node.deploy.form.baotapanel_site_name.placeholder": "Please enter aaPanel site name", - "workflow_node.deploy.form.baotapanel_site_name.tooltip": "Usually equal to the website domain name.", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "You can find it on aaPanel WebUI.", "workflow_node.deploy.form.baotapanel_site_names.label": "aaPanel site names", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter aaPanel site names (separated by semicolons)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid aaPanel site name", - "workflow_node.deploy.form.baotapanel_site_names.tooltip": "Usually equal to the websites domain name.", + "workflow_node.deploy.form.baotapanel_site_names.tooltip": "You can find it on aaPanel WebUI.", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "Change aaPanel site names", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "Please enter aaPanel site name", + "workflow_node.deploy.form.baotawaf_site_name.label": "aaWAF site name", + "workflow_node.deploy.form.baotawaf_site_name.placeholder": "Please enter aaWAF site name", + "workflow_node.deploy.form.baotawaf_site_name.tooltip": "You can find it on aaWAF WebUI.", + "workflow_node.deploy.form.baotawaf_site_port.label": "aaWAF site SSL port", + "workflow_node.deploy.form.baotawaf_site_port.placeholder": "Please enter aaWAF SSL port", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN pull zone ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "Please enter Bunny CDN pull zone ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "What is this? See https://dash.bunny.net/cdn", @@ -347,14 +365,22 @@ "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly site ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "Please enter Cdnfly site ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly certificate ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "Please enter Cdnfly certificate ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "Doge Cloud CDN domain", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "Please enter Doge Cloud CDN domain name", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "For more information, see https://console.dogecloud.com/", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "Please enter Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "For more information, see https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "Resource type", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "Please enter FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "You can find it on FlexCDN WebUI.", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "Please enter Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "For more information, see https://cdn.gcore.com/resources/list", @@ -366,6 +392,7 @@ "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge certificate ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "Please enter GoEdge certificate ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "You can find it on GoEdge WebUI.", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "Huawei Cloud CDN region", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "Please enter Huawei Cloud CDN region (e.g. cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", @@ -443,6 +470,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.lecdn_resource_type.label": "Resource type", + "workflow_node.deploy.form.lecdn_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN certificate ID", + "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "Please enter LeCDN certificate ID", + "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "You can find it on LeCDN WebUI.", + "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN user ID (Optional)", + "workflow_node.deploy.form.lecdn_client_id.placeholder": "Please enter LeCDN user ID", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "You can find it on LeCDN WebUI.

Required when using administrator's authorization. It Must be the same as the user to which the certificate belongs.", "workflow_node.deploy.form.local.guide": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", "workflow_node.deploy.form.local_format.label": "File format", "workflow_node.deploy.form.local_format.placeholder": "Please select file format", @@ -489,8 +525,8 @@ "workflow_node.deploy.form.local_preset_scripts.option.ps_binding_iis.label": "PowerShell - Binding IIS", "workflow_node.deploy.form.local_preset_scripts.option.ps_binding_netsh.label": "PowerShell - Binding netsh", "workflow_node.deploy.form.local_preset_scripts.option.ps_.label": "PowerShell - Binding RDP", - "workflow_node.deploy.form.netlify_site_id.label": "netlify site ID", - "workflow_node.deploy.form.netlify_site_id.placeholder": "Please enter netlify site ID", + "workflow_node.deploy.form.netlify_site_id.label": "Netlify site ID", + "workflow_node.deploy.form.netlify_site_id.placeholder": "Please enter Netlify site ID", "workflow_node.deploy.form.netlify_site_id.tooltip": "For more information, see https://docs.netlify.com/api/get-started/#get-site", "workflow_node.deploy.form.proxmoxve_node_name.label": "Proxmox VE cluster node name", "workflow_node.deploy.form.proxmoxve_node_name.placeholder": "Please enter Proxmox VE cluster node name", @@ -513,11 +549,15 @@ "workflow_node.deploy.form.rainyun_rcdn_domain.label": "Rain Yun RCDN domain", "workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "Please enter Rain Yun RCDN domain name", "workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "For more information, see https://app.rainyun.com/apps/rcdn/list", + "workflow_node.deploy.form.ratpanel_site_name.label": "RatPanel site name", + "workflow_node.deploy.form.ratpanel_site_name.placeholder": "Please enter RatPanel site name", + "workflow_node.deploy.form.ratpanel_site_name.tooltip": "You can find it on RatPanel WebUI.", "workflow_node.deploy.form.safeline_resource_type.label": "Resource type", "workflow_node.deploy.form.safeline_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.safeline_certificate_id.label": "SafeLine certificate ID", "workflow_node.deploy.form.safeline_certificate_id.placeholder": "Please enter SafeLine certificate ID", + "workflow_node.deploy.form.safeline_certificate_id.tooltip": "You can find it on SafeLine WebUI.", "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)", @@ -720,6 +760,11 @@ "workflow_node.deploy.form.volcengine_tos_domain.label": "VolcEngine TOS domain", "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "Please enter VolcEngine TOS domain name", "workflow_node.deploy.form.volcengine_tos_domain.tooltip": "For more information, see https://console.volcengine.com/tos", + "workflow_node.deploy.form.wangsu_cdn_domains.label": "Wangsu Cloud CDN domains", + "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "Please enter Wangsu Cloud CDN domain names (separated by semicolons)", + "workflow_node.deploy.form.wangsu_cdn_domains.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index/#/property/list", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.title": "Change Wangsu Cloud CDN domains", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.placeholder": "Please enter Wangsu Cloud CDN domain", "workflow_node.deploy.form.wangsu_cdnpro_environment.label": "Wangsu Cloud environment", "workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "Please select Wangsu Cloud environment", "workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "Production environment", @@ -733,6 +778,9 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "Wangsu Cloud CDN Webhook ID (Optional)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "Please enter Wangsu Cloud CDN Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "For more information, see https://cdnpro.console.wangsu.com/v2/index/#/certificate", + "workflow_node.deploy.form.wangsu_certificate_id.label": "Wangsu Cloud certificate ID (Optional)", + "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "Please enter Wangsu Cloud certificate ID", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index#/certificate/list", "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", @@ -776,9 +824,9 @@ "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)", "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value", "workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", - "workflow_node.notify.form.telegram_chat_id.label": "Telegram chat ID (Optional)", - "workflow_node.notify.form.telegram_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", - "workflow_node.notify.form.telegram_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", + "workflow_node.notify.form.telegram_bot_chat_id.label": "Telegram chat ID (Optional)", + "workflow_node.notify.form.telegram_bot_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", + "workflow_node.notify.form.telegram_bot_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", "workflow_node.notify.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", "workflow_node.notify.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index f73065ea..a5e89438 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -36,12 +36,21 @@ "access.form.notification_channel.placeholder": "请选择通知渠道", "access.form.1panel_api_url.label": "1Panel URL", "access.form.1panel_api_url.placeholder": "请输入 1Panel URL", + "access.form.1panel_api_version.label": "1Panel 版本", + "access.form.1panel_api_version.placeholder": "请选择 1Panel 版本", "access.form.1panel_api_key.label": "1Panel 接口密钥", "access.form.1panel_api_key.placeholder": "请输入 1Panel 接口密钥", "access.form.1panel_api_key.tooltip": "这是什么?请参阅 https://1panel.cn/docs/dev_manual/api_manual/", "access.form.1panel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.1panel_allow_insecure_conns.switch.on": "允许", "access.form.1panel_allow_insecure_conns.switch.off": "不允许", + "access.form.acmeca_endpoint.label": "服务端点", + "access.form.acmeca_endpoint.placeholder": "请输入服务端点", + "access.form.acmeca_endpoint.tooltip": "这是什么?请参阅 https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1", + "access.form.acmeca_eab_kid.label": "ACME EAB KID(可选)", + "access.form.acmeca_eab_kid.placeholder": "请输入 ACME EAB KID", + "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC Key(可选)", + "access.form.acmeca_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", @@ -94,6 +103,14 @@ "access.form.baotapanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.baotapanel_allow_insecure_conns.switch.on": "允许", "access.form.baotapanel_allow_insecure_conns.switch.off": "不允许", + "access.form.baotawaf_api_url.label": "堡塔云 WAF URL", + "access.form.baotawaf_api_url.placeholder": "请输入堡塔云 WAF URL", + "access.form.baotawaf_api_key.label": "堡塔云 WAF 接口密钥", + "access.form.baotawaf_api_key.placeholder": "请输入 堡塔云 WAF 接口密钥", + "access.form.baotawaf_api_key.tooltip": "这是什么?请参阅 https://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.baotawaf_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.baotawaf_allow_insecure_conns.switch.on": "允许", + "access.form.baotawaf_allow_insecure_conns.switch.off": "不允许", "access.form.bunny_api_key.label": "Bunny API Key", "access.form.bunny_api_key.placeholder": "请输入 Bunny API Key", "access.form.bunny_api_key.tooltip": "这是什么?请参阅 https://docs.bunny.net/reference/bunnynet-api-overview", @@ -106,8 +123,8 @@ "access.form.cachefly_api_token.label": "CacheFly API Token", "access.form.cachefly_api_token.placeholder": "请输入 CacheFly API Token", "access.form.cachefly_api_token.tooltip": "这是什么?请参阅 https://kb.cachefly.com/kb/guide/en/generating-tokens-and-keys-Oll9Irt5TI/Steps/2460228", - "access.form.cdnfly_api_url.label": "Cdnfly API URL", - "access.form.cdnfly_api_url.placeholder": "请输入 Cdnfly API URL", + "access.form.cdnfly_api_url.label": "Cdnfly URL", + "access.form.cdnfly_api_url.placeholder": "请输入 Cdnfly URL", "access.form.cdnfly_api_key.label": "Cdnfly 用户端 API Key", "access.form.cdnfly_api_key.placeholder": "请输入 Cdnfly 用户端 API Key", "access.form.cdnfly_api_key.tooltip": "这是什么?请参阅 https://doc.cdnfly.cn/shiyongjieshao.html", @@ -178,6 +195,21 @@ "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", + "access.form.flexcdn_api_url.label": "FlexCDN URL", + "access.form.flexcdn_api_url.placeholder": "请输入 FlexCDN URL", + "access.form.flexcdn_api_role.label": "FlexCDN 用户角色", + "access.form.flexcdn_api_role.placeholder": "请选择 FlexCDN 用户角色", + "access.form.flexcdn_api_role.option.user.label": "平台用户", + "access.form.flexcdn_api_role.option.admin.label": "系统管理员", + "access.form.flexcdn_access_key_id.label": "FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.placeholder": "请输入 FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "请输入 FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.flexcdn_allow_insecure_conns.switch.on": "允许", + "access.form.flexcdn_allow_insecure_conns.switch.off": "不允许", "access.form.gcore_api_token.label": "Gcore API Token", "access.form.gcore_api_token.placeholder": "请输入 Gcore API Token", "access.form.gcore_api_token.tooltip": "这是什么?请参阅 https://api.gcore.com/docs/iam#section/Authentication", @@ -193,8 +225,8 @@ "access.form.godaddy_api_secret.label": "GoDaddy API Secret", "access.form.godaddy_api_secret.placeholder": "请输入 GoDaddy API Secret", "access.form.godaddy_api_secret.tooltip": "这是什么?请参阅 https://developer.godaddy.com/", - "access.form.goedge_api_url.label": "GoEdge API URL", - "access.form.goedge_api_url.placeholder": "请输入 GoEdge API URL", + "access.form.goedge_api_url.label": "GoEdge URL", + "access.form.goedge_api_url.placeholder": "请输入 GoEdge URL", "access.form.goedge_api_role.label": "GoEdge 用户角色", "access.form.goedge_api_role.placeholder": "请选择 GoEdge 用户角色", "access.form.goedge_api_role.option.user.label": "平台用户", @@ -232,6 +264,21 @@ "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", + "access.form.lecdn_api_url.label": "LeCDN URL", + "access.form.lecdn_api_url.placeholder": "请输入 LeCDN URL", + "access.form.lecdn_api_version.label": "LeCDN 版本", + "access.form.lecdn_api_version.placeholder": "请选择 LeCDN 版本", + "access.form.lecdn_api_role.label": "LeCDN 用户角色", + "access.form.lecdn_api_role.placeholder": "请选择 LeCDN 用户角色", + "access.form.lecdn_api_role.option.client.label": "客户用户", + "access.form.lecdn_api_role.option.master.label": "主控管理员", + "access.form.lecdn_username.label": "LeCDN 用户名", + "access.form.lecdn_username.placeholder": "请输入 LeCDN 用户名", + "access.form.lecdn_password.label": "LeCDN 用户密码", + "access.form.lecdn_password.placeholder": "请输入 LeCDN 用户密码", + "access.form.lecdn_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.lecdn_allow_insecure_conns.switch.on": "允许", + "access.form.lecdn_allow_insecure_conns.switch.off": "不允许", "access.form.mattermost_server_url.label": "Mattermost 服务地址", "access.form.mattermost_server_url.placeholder": "请输入 Mattermost 服务地址", "access.form.mattermost_username.label": "Mattermost 用户名", @@ -256,8 +303,8 @@ "access.form.namesilo_api_key.label": "NameSilo API Key", "access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key", "access.form.namesilo_api_key.tooltip": "这是什么?请参阅 https://www.namesilo.com/support/v2/articles/account-options/api-manager", - "access.form.netlify_api_token.label": "netlify API Token", - "access.form.netlify_api_token.placeholder": "请输入 netlify API Token", + "access.form.netlify_api_token.label": "Netlify API Token", + "access.form.netlify_api_token.placeholder": "请输入 Netlify API Token", "access.form.netlify_api_token.tooltip": "这是什么?请参阅 https://docs.netlify.com/api/get-started/#authentication", "access.form.netcup_customer_number.label": "netcup 客户编号", "access.form.netcup_customer_number.placeholder": "请输入 netcup 客户编号", @@ -277,8 +324,8 @@ "access.form.porkbun_secret_api_key.label": "Porkbun Secret API Key", "access.form.porkbun_secret_api_key.placeholder": "请输入 Porkbun Secret API Key", "access.form.porkbun_secret_api_key.tooltip": "这是什么?请参阅 https://porkbun.com/api/json/v3/documentation", - "access.form.powerdns_api_url.label": "PowerDNS API URL", - "access.form.powerdns_api_url.placeholder": "请输入 PowerDNS API URL", + "access.form.powerdns_api_url.label": "PowerDNS URL", + "access.form.powerdns_api_url.placeholder": "请输入 PowerDNS URL", "access.form.powerdns_api_key.label": "PowerDNS API Key", "access.form.powerdns_api_key.placeholder": "请输入 PowerDNS API Key", "access.form.powerdns_api_key.tooltip": "这是什么?请参阅 https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api", @@ -305,6 +352,17 @@ "access.form.rainyun_api_key.label": "雨云 API 密钥", "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥", "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://app.rainyun.com/account/settings/api-key", + "access.form.ratpanel_api_url.label": "耗子面板 URL", + "access.form.ratpanel_api_url.placeholder": "请输入耗子面板 URL", + "access.form.ratpanel_access_token_id.label": "耗子面板 AccessToken ID", + "access.form.ratpanel_access_token_id.placeholder": "请输入耗子面板 AccessToken ID", + "access.form.ratpanel_access_token_id.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_access_token.label": "耗子面板 AccessToken", + "access.form.ratpanel_access_token.placeholder": "请输入耗子面板 AccessToken", + "access.form.ratpanel_access_token.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.ratpanel_allow_insecure_conns.switch.on": "允许", + "access.form.ratpanel_allow_insecure_conns.switch.off": "不允许", "access.form.safeline_api_url.label": "雷池 URL", "access.form.safeline_api_url.placeholder": "请输入雷池 URL", "access.form.safeline_api_token.label": "雷池 API Token", @@ -328,18 +386,21 @@ "access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", + "access.form.ssh_jump_servers.label": "SSH 跳板机(可选)", + "access.form.ssh_jump_servers.item.label": "跳板机", + "access.form.ssh_jump_servers.add": "添加跳板机", "access.form.sslcom_eab_kid.label": "ACME EAB KID", "access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", - "access.form.telegram_bot_token.label": "Telegram 机器人 API Token", - "access.form.telegram_bot_token.placeholder": "请输入 Telegram 机器人 API Token", + "access.form.telegram_bot_token.label": "Telegram 群机器人 API Token", + "access.form.telegram_bot_token.placeholder": "请输入 Telegram 群机器人 API Token", "access.form.telegram_bot_token.tooltip": "如何获取机器人 API Token?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", - "access.form.telegram_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", - "access.form.telegram_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", - "access.form.telegram_default_chat_id.tooltip": "如何获取会话 ID?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_bot_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", + "access.form.telegram_bot_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", + "access.form.telegram_bot_default_chat_id.tooltip": "如何获取会话 ID?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "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", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 93b5137d..b9fb8777 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -1,8 +1,9 @@ { "provider.1panel": "1Panel", - "provider.1panel.console": "1Panel - 面板", + "provider.1panel.console": "1Panel - 控制台", "provider.1panel.site": "1Panel - 网站", - "provider.acmehttpreq": "Http Request (ACME Proxy)", + "provider.acmeca": "ACME 自定义 CA 端点", + "provider.acmehttpreq": "ACME 自定义 HTTP 端点", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", "provider.aliyun.apigw": "阿里云 - API 网关", @@ -15,6 +16,7 @@ "provider.aliyun.dns": "阿里云 - 云解析 DNS", "provider.aliyun.esa": "阿里云 - 边缘安全加速 ESA", "provider.aliyun.fc": "阿里云 - 函数计算 FC", + "provider.aliyun.ga": "阿里云 - 全球加速 GA", "provider.aliyun.live": "阿里云 - 视频直播 Live", "provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", "provider.aliyun.oss": "阿里云 - 对象存储 OSS", @@ -38,8 +40,11 @@ "provider.baishan": "白山云", "provider.baishan.cdn": "白山云 - 内容分发网络 CDN", "provider.baotapanel": "宝塔面板", - "provider.baotapanel.console": "宝塔面板 - 面板", + "provider.baotapanel.console": "宝塔面板 - 控制台", "provider.baotapanel.site": "宝塔面板 - 网站", + "provider.baotawaf": "堡塔云 WAF", + "provider.baotawaf.console": "堡塔云 WAF - 控制台", + "provider.baotawaf.site": "堡塔云 WAF - 网站", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - 内容分发网络 CDN", "provider.byteplus": "BytePlus", @@ -62,6 +67,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "邮件", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - 内容分发网络 CDN", "provider.gname": "GNAME", @@ -83,6 +89,7 @@ "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "飞书群机器人", + "provider.lecdn": "LeCDN", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt 测试环境", "provider.local": "本地部署", @@ -91,8 +98,8 @@ "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", "provider.netcup": "netcup", - "provider.netlify": "netlify", - "provider.netlify.site": "netlify - Site", + "provider.netlify": "Netlify", + "provider.netlify.site": "Netlify - Site", "provider.ns1": "NS1 (IBM NS1 Connect)", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", @@ -103,10 +110,13 @@ "provider.qiniu.pili": "七牛云 - 视频直播 Pili", "provider.rainyun": "雨云", "provider.rainyun.rcdn": "雨云 - 雨盾 CDN", + "provider.ratpanel": "耗子面板", + "provider.ratpanel.console": "耗子面板 - 控制台", + "provider.ratpanel.site": "耗子面板 - 网站", "provider.safeline": "雷池", "provider.ssh": "SSH 部署", "provider.sslcom": "SSL.com", - "provider.telegram": "Telegram", + "provider.telegrambot": "Telegram 群机器人", "provider.tencentcloud": "腾讯云", "provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", "provider.tencentcloud.clb": "腾讯云 - 负载均衡 CLB", @@ -138,7 +148,9 @@ "provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "provider.wangsu": "网宿云", - "provider.wangsu.cdnpro": "网宿云 - CDN Pro", + "provider.wangsu.cdn": "网宿云 - 内容分发网络 CDN", + "provider.wangsu.cdnpro": "网宿云 - CDN Pro (CDN 360)", + "provider.wangsu.certificate_upload": "网宿云 - 上传到证书管理", "provider.webhook": "Webhook", "provider.wecombot": "企业微信群机器人", "provider.westcn": "西部数码", @@ -150,8 +162,10 @@ "provider.category.loadbalance": "负载均衡", "provider.category.firewall": "防火墙", "provider.category.av": "音视频", + "provider.category.apigw": "API 网关", "provider.category.serverless": "Serverless", "provider.category.website": "网站托管", + "provider.category.ssl": "证书托管", "provider.category.nas": "NAS", "provider.category.other": "其他", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index b8c98418..faf40816 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -109,18 +109,18 @@ "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.params_config.label": "参数设置", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启宝塔面板服务", - "workflow_node.deploy.form.1panel_site_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.1panel_site_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "替换指定网站的证书", "workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", "workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID", - "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请在 1Panel 管理面板查看。", + "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请登录 1Panel 面板查看。", "workflow_node.deploy.form.1panel_site_certificate_id.label": "1Panel 证书 ID", "workflow_node.deploy.form.1panel_site_certificate_id.placeholder": "请输入 1Panel 证书 ID", - "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请在 1Panel 管理面板查看。", - "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请登录 1Panel 面板查看。", + "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.aliyun_alb_region.label": "阿里云 ALB 服务地域", @@ -170,8 +170,8 @@ "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

不填写时,将使用系统联系人列表中的第一个。", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "修改阿里云联系人 ID", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "请输入阿里云联系人 ID", - "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.aliyun_clb_region.label": "阿里云 CLB 服务地域", @@ -212,14 +212,27 @@ "workflow_node.deploy.form.aliyun_fc_domain.label": "阿里云 FC 自定义域名", "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "请输入阿里云 FC 自定义域名(支持泛域名)", "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "这是什么?请参阅 see https://fcnext.console.aliyun.com/", + "workflow_node.deploy.form.aliyun_ga_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.accelerator.label": "替换指定全球加速器下的全部 HTTPS 监听的证书", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.listener.label": "替换指定全球加速器监听器的证书", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.label": "阿里云全球加速实例 ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.placeholder": "请输入阿里云全球加速实例 ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_listener_id.label": "阿里云全球加速监听 ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.placeholder": "请输入阿里云全球加速监听 ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_snidomain.label": "阿里云全球加速扩展域名(可选)", + "workflow_node.deploy.form.aliyun_ga_snidomain.placeholder": "请输入阿里云全球加速扩展域名", + "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com

不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_live_region.label": "阿里云视频直播服务地域", "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云视频直播服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", "workflow_node.deploy.form.aliyun_live_domain.label": "阿里云视频直播流域名", "workflow_node.deploy.form.aliyun_live_domain.placeholder": "请输入阿里云视频直播流域名(支持泛域名)", "workflow_node.deploy.form.aliyun_live_domain.tooltip": "这是什么?请参阅 https://live.console.aliyun.com", - "workflow_node.deploy.form.aliyun_nlb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_nlb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.aliyun_nlb_region.label": "阿里云 NLB 服务地域", @@ -276,8 +289,8 @@ "workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder": "请输入 Azure KeyVault 证书名称", "workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip": "不填写时,将由 Certimate 自动生成证书名称。", "workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid": "证书名称只能包含字母、数字和连字符(-),长度限制为 1 到 127 个字符", - "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.baiducloud_appblb_region.label": "百度智能云 BLB 服务地域", @@ -292,8 +305,8 @@ "workflow_node.deploy.form.baiducloud_appblb_snidomain.label": "百度智能云 BLB 扩展域名(可选)", "workflow_node.deploy.form.baiducloud_appblb_snidomain.placeholder": "请输入百度智能云 BLB 扩展域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_appblb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/appblb/list

不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", - "workflow_node.deploy.form.baiducloud_blb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.baiducloud_blb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.baiducloud_blb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.baiducloud_blb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.baiducloud_blb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书", "workflow_node.deploy.form.baiducloud_blb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.baiducloud_blb_region.label": "百度智能云 BLB 服务地域", @@ -324,13 +337,18 @@ "workflow_node.deploy.form.baotapanel_site_type.option.other.label": "其他", "workflow_node.deploy.form.baotapanel_site_name.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_name.placeholder": "请输入宝塔面板网站名称", - "workflow_node.deploy.form.baotapanel_site_name.tooltip": "通常为网站域名。", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看。", "workflow_node.deploy.form.baotapanel_site_names.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "请输入宝塔面板网站名称(多个值请用半角分号隔开)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "请输入正确的宝塔面板网站名称", - "workflow_node.deploy.form.baotapanel_site_names.tooltip": "通常为网站域名。", + "workflow_node.deploy.form.baotapanel_site_names.tooltip": "请登录宝塔面板查看。", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "修改宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "请输入宝塔面板网站名称", + "workflow_node.deploy.form.baotawaf_site_name.label": "堡塔云 WAF 网站名称", + "workflow_node.deploy.form.baotawaf_site_name.placeholder": "请输入堡塔云 WAF 网站名称", + "workflow_node.deploy.form.baotawaf_site_name.tooltip": "请登录堡塔云 WAF 面板查看。", + "workflow_node.deploy.form.baotawaf_site_port.label": "堡塔云 WAF 网站 SSL 端口", + "workflow_node.deploy.form.baotawaf_site_port.placeholder": "请输入堡塔云 WAF 网站 SSL 端口", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN 拉取区域 ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "请输入 Bunny CDN 拉取区域 ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "这是什么?请参阅 https://dash.bunny.net/cdn", @@ -340,39 +358,48 @@ "workflow_node.deploy.form.byteplus_cdn_domain.label": "BytePlus CDN 域名", "workflow_node.deploy.form.byteplus_cdn_domain.placeholder": "请输入 BytePlus CDN 域名(支持泛域名)", "workflow_node.deploy.form.byteplus_cdn_domain.tooltip": "这是什么?请参阅 https://console.byteplus.com/cdn", - "workflow_node.deploy.form.cdnfly_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.cdnfly_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.cdnfly_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.cdnfly_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.cdnfly_resource_type.option.site.label": "替换指定网站的证书", "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly 网站 ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "请输入 Cdnfly 网站 ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "请登录 Cdnfly 控制台查看。", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly 证书 ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "请输入 Cdnfly 证书 ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "请登录 Cdnfly 控制台查看。", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "请输入多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.dogecloud.com", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "请输入 Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "这是什么?请参阅 https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "请输入 FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 控制台查看。", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "请输入 Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", "workflow_node.deploy.form.gcore_cdn_certificate_id.label": "Gcore CDN 原证书 ID(可选)", "workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder": "请输入 Gcore CDN 原证书 ID", "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/ssl

不填写时,将上传新证书;否则,将替换原证书。", - "workflow_node.deploy.form.goedge_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.goedge_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge 证书 ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "请输入 GoEdge 证书 ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 控制台查看。", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "华为云 CDN 服务区域", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云 CDN 服务区域(例如:cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "请输入华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/cdn", - "workflow_node.deploy.form.huaweicloud_elb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.huaweicloud_elb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听器的证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.listener.label": "替换指定监听器的证书", @@ -388,8 +415,8 @@ "workflow_node.deploy.form.huaweicloud_elb_listener_id.label": "华为云 ELB 监听器 ID", "workflow_node.deploy.form.huaweicloud_elb_listener_id.placeholder": "请输入华为云 ELB 监听器 ID", "workflow_node.deploy.form.huaweicloud_elb_listener_id.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/vpc/#/elb/list/grid", - "workflow_node.deploy.form.huaweicloud_waf_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.huaweicloud_waf_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.huaweicloud_waf_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.huaweicloud_waf_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.cloudserver.label": "替换指定云模式防护网站的证书", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.premiumhost.label": "替换指定独享模式防护网站的证书", @@ -402,8 +429,8 @@ "workflow_node.deploy.form.huaweicloud_waf_domain.label": "华为云 WAF 防护域名", "workflow_node.deploy.form.huaweicloud_waf_domain.placeholder": "请输入华为云 WAF 防护域名(支持泛域名)", "workflow_node.deploy.form.huaweicloud_waf_domain.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/console/#/waf/domain/list", - "workflow_node.deploy.form.jdcloud_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.jdcloud_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.jdcloud_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.jdcloud_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.jdcloud_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/TLS 监听的证书", "workflow_node.deploy.form.jdcloud_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.jdcloud_alb_region_id.label": "京东云 ALB 服务地域 ID", @@ -442,6 +469,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.lecdn_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.lecdn_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN 证书 ID", + "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "请输入 LeCDN 证书 ID", + "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "请登录 LeCDN 控制台查看。", + "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN 客户 ID(可选)", + "workflow_node.deploy.form.lecdn_client_id.placeholder": "请输入 LeCDN 客户 ID", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "请登录 LeCDN 控制台查看。

使用的是系统管理员的授权信息时必填,需与证书所属客户相同。", "workflow_node.deploy.form.local.guide": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。", "workflow_node.deploy.form.local_format.label": "文件格式", "workflow_node.deploy.form.local_format.placeholder": "请选择文件格式", @@ -488,7 +524,7 @@ "workflow_node.deploy.form.local_preset_scripts.option.ps_binding_iis.label": "PowerShell - 导入并绑定到 IIS", "workflow_node.deploy.form.local_preset_scripts.option.ps_binding_netsh.label": "PowerShell - 导入并绑定到 netsh", "workflow_node.deploy.form.local_preset_scripts.option.ps_binding_rdp.label": "PowerShell - 导入并绑定到 RDP", - "workflow_node.deploy.form.netlify_site_id.label": "netlify 网站 ID", + "workflow_node.deploy.form.netlify_site_id.label": "Netlify 网站 ID", "workflow_node.deploy.form.netlify_site_id.placeholder": "请输入 netlify 网站 ID", "workflow_node.deploy.form.netlify_site_id.tooltip": "这是什么?请参阅 https://docs.netlify.com/api/get-started/#get-site", "workflow_node.deploy.form.proxmoxve_node_name.label": "Proxmox VE 集群节点名称", @@ -512,11 +548,15 @@ "workflow_node.deploy.form.rainyun_rcdn_domain.label": "雨云 RCDN 加速域名", "workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "请输入雨云 RCDN 加速域名(支持泛域名)", "workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "这是什么?请参阅 https://app.rainyun.com/apps/rcdn/list", - "workflow_node.deploy.form.safeline_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.ratpanel_site_name.label": "耗子面板网站名称", + "workflow_node.deploy.form.ratpanel_site_name.placeholder": "请输入耗子面板网站名称", + "workflow_node.deploy.form.ratpanel_site_name.tooltip": "请登录耗子面板查看。", + "workflow_node.deploy.form.safeline_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.safeline_certificate_id.label": "雷池证书 ID", "workflow_node.deploy.form.safeline_certificate_id.placeholder": "请输入雷池证书 ID", + "workflow_node.deploy.form.safeline_certificate_id.tooltip": "请登录雷池控制台查看。", "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)", @@ -564,8 +604,8 @@ "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", - "workflow_node.deploy.form.tencentcloud_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.tencentcloud_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ssl_deploy.label": "通过 SSL 服务部署到云资源实例", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.loadbalancer.label": "替换指定实例下的全部 HTTPS/TCPSSL/QUIC 监听器的证书", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.listener.label": "替换指定监听器的证书", @@ -661,8 +701,8 @@ "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/file/", - "workflow_node.deploy.form.volcengine_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.volcengine_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.volcengine_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.volcengine_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.volcengine_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.volcengine_alb_resource_type.option.listener.label": "替换指定监听器的证书", "workflow_node.deploy.form.volcengine_alb_region.label": "火山引擎 ALB 服务地域", @@ -682,8 +722,8 @@ "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage", "workflow_node.deploy.form.volcengine_certcenter_region.label": "火山引擎证书中心服务地域", "workflow_node.deploy.form.volcengine_certcenter_region.placeholder": "请输入火山引擎证书中心服务地域(例如:cn-beijing)", - "workflow_node.deploy.form.volcengine_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.volcengine_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.volcengine_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.volcengine_clb_resource_type.option.listener.label": "替换指定监听器的证书", "workflow_node.deploy.form.volcengine_clb_region.label": "火山引擎 CLB 服务地域", @@ -719,6 +759,11 @@ "workflow_node.deploy.form.volcengine_tos_domain.label": "火山引擎 TOS 自定义域名", "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "请输入火山引擎 TOS 自定义域名", "workflow_node.deploy.form.volcengine_tos_domain.tooltip": "这是什么?请参阅 see https://console.volcengine.com/tos", + "workflow_node.deploy.form.wangsu_cdn_domains.label": "网宿云 CDN 加速域名", + "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "请输入网宿云 CDN 加速域名(多个值请用半角分号隔开)", + "workflow_node.deploy.form.wangsu_cdn_domains.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index/#/property/list", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.title": "修改网宿云 CDN 加速域名", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.placeholder": "请输入网宿云 CDN 加速域名", "workflow_node.deploy.form.wangsu_cdnpro_environment.label": "网宿云环境", "workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "请选择网宿云环境", "workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "生产环境", @@ -732,6 +777,9 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "网宿云 CDN Pro 部署任务 Webhook ID(可选)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "请输入网宿云 CDN Pro 部署任务 Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", + "workflow_node.deploy.form.wangsu_certificate_id.label": "网宿云证书 ID(可选)", + "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "请输入网宿云证书 ID", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list

不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", @@ -775,9 +823,9 @@ "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID(可选)", "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID 以覆盖默认值", "workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", - "workflow_node.notify.form.telegram_chat_id.label": "Telegram 会话 ID(可选)", - "workflow_node.notify.form.telegram_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", - "workflow_node.notify.form.telegram_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", + "workflow_node.notify.form.telegram_bot_chat_id.label": "Telegram 会话 ID(可选)", + "workflow_node.notify.form.telegram_bot_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", + "workflow_node.notify.form.telegram_bot_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", "workflow_node.notify.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.notify.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.notify.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index 46bc1745..97eab0ef 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -126,11 +126,11 @@ const CertificateList = () => { }, }, { - key: "issuer", + key: "brand", title: t("certificate.props.brand"), render: (_, record) => ( - {record.issuer} + {record.issuerOrg} {record.keyAlgorithm} ),