mirror of
https://github.com/certimate-go/certimate.git
synced 2026-10-31 03:01:59 +08:00
feat: deployer migration
This commit is contained in:
@@ -43,6 +43,7 @@ require (
|
||||
github.com/baidubce/bce-sdk-go v0.9.270
|
||||
github.com/byteplus-sdk/byteplus-go-sdk-v2 v1.0.71
|
||||
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.71
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-acme/lego/v5 v5.2.2
|
||||
github.com/go-cmd/cmd v1.4.3
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
@@ -63,7 +64,6 @@ require (
|
||||
github.com/pocketbase/pocketbase v0.39.6
|
||||
github.com/povsister/scp v0.0.0-20250701154629-777cf82de5df
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/qiniu/go-sdk/v7 v7.26.15
|
||||
github.com/samber/lo v1.53.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
@@ -122,9 +122,6 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.5 // indirect
|
||||
github.com/go-openapi/swag v0.25.5 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
@@ -134,7 +131,6 @@ require (
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/namedotcom/go/v4 v4.0.2 // indirect
|
||||
@@ -146,8 +142,6 @@ require (
|
||||
github.com/peterhellberg/link v1.2.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/qiniu/dyn v1.3.0 // indirect
|
||||
github.com/qiniu/x v1.17.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
@@ -174,7 +168,6 @@ require (
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
@@ -204,7 +197,6 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
|
||||
github.com/go-acme/alidns-20150109/v5 v5.4.1 // indirect
|
||||
|
||||
@@ -66,8 +66,6 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/G-Core/gcorelabscdn-go v1.0.37 h1:IHqiSfnHp3tnuUSU1FIH5+3XepNnSsyJn0JoWgvUYL8=
|
||||
@@ -417,17 +415,9 @@ github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lE
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
@@ -675,8 +665,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -831,13 +819,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/qiniu/dyn v1.3.0 h1:s+xPTeV0H8yikgM4ZMBc7Rrefam8UNI3asBlkaOQg5o=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.15 h1:CaKVcP29ZnOp/pqE7U3RAZxqwL7CmtMTWlV1gAV71H0=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.15/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/qiniu/x v1.17.0 h1:OsyDKXzYp5vw9Hc7VAe4Cso1Sp50fLKGsBuDteyTevE=
|
||||
github.com/qiniu/x v1.17.0/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package deployers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-cdn"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeQiniuCDN, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForQiniu{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
|
||||
AccessKey: credentials.AccessKey,
|
||||
SecretKey: credentials.SecretKey,
|
||||
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package deployers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-kodo"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeQiniuKodo, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForQiniu{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
|
||||
AccessKey: credentials.AccessKey,
|
||||
SecretKey: credentials.SecretKey,
|
||||
Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package deployers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-pili"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeQiniuPili, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForQiniu{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
|
||||
AccessKey: credentials.AccessKey,
|
||||
SecretKey: credentials.SecretKey,
|
||||
Hub: xmaps.GetString(options.ProviderExtendedConfig, "hub"),
|
||||
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package deployers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/webhook"
|
||||
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeWebhook, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForWebhook{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
mergedHeaders := make(map[string]string)
|
||||
if defaultHeadersString := credentials.HeadersString; defaultHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(defaultHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
if extendedHeadersString := xmaps.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(extendedHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
|
||||
WebhookUrl: credentials.Url,
|
||||
WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString),
|
||||
Method: credentials.Method,
|
||||
Headers: mergedHeaders,
|
||||
Timeout: xmaps.GetInt(options.ProviderExtendedConfig, "timeout"),
|
||||
AllowInsecureConnections: credentials.AllowInsecureConnections,
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package qiniusslcert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Certmgr
|
||||
UploadResult = core.CertmgrUploadResult
|
||||
ReplaceResult = core.CertmgrReplaceResult
|
||||
)
|
||||
|
||||
type CertmgrConfig struct {
|
||||
// 七牛云 AccessKey。
|
||||
AccessKey string `json:"accessKey"`
|
||||
// 七牛云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
type Certmgr struct {
|
||||
config *CertmgrConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *qiniusdk.SslCertManager
|
||||
}
|
||||
|
||||
var _ Provider = (*Certmgr)(nil)
|
||||
|
||||
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the certmgr provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
return &Certmgr{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
c.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
c.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
|
||||
// 解析证书内容
|
||||
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 生成新证书名(需符合七牛云命名规则)
|
||||
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||
|
||||
// 查询已有证书,避免重复上传
|
||||
getSslCertListMarker := ""
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
getSslCertListResp, err := c.sdkClient.GetSslCertList(ctx, getSslCertListMarker, 200)
|
||||
c.logger.Debug("sdk request 'sslcert.GetList'", slog.Any("params.marker", getSslCertListMarker), slog.Any("response", getSslCertListResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'sslcert.GetList': %w", err)
|
||||
}
|
||||
|
||||
for _, sslItem := range getSslCertListResp.Certs {
|
||||
// 对比证书通用名称
|
||||
if !strings.EqualFold(certX509.Subject.CommonName, sslItem.CommonName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比证书备用名称
|
||||
if !slices.Equal(certX509.DNSNames, sslItem.DnsNames) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比证书有效期
|
||||
if certX509.NotBefore.Unix() != sslItem.NotBefore {
|
||||
continue
|
||||
} else if certX509.NotAfter.Unix() != sslItem.NotAfter {
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比证书公钥算法
|
||||
switch certX509.PublicKeyAlgorithm {
|
||||
case x509.RSA:
|
||||
if !strings.EqualFold(sslItem.Encrypt, "RSA") {
|
||||
continue
|
||||
}
|
||||
case x509.ECDSA:
|
||||
if !strings.EqualFold(sslItem.Encrypt, "ECDSA") {
|
||||
continue
|
||||
}
|
||||
case x509.Ed25519:
|
||||
if !strings.EqualFold(sslItem.Encrypt, "Ed25519") {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果以上信息都一致,则视为已存在相同证书,直接返回
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return &UploadResult{
|
||||
CertId: sslItem.CertID,
|
||||
CertName: sslItem.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(getSslCertListResp.Certs) == 0 || getSslCertListResp.Marker == "" {
|
||||
break
|
||||
}
|
||||
|
||||
getSslCertListMarker = getSslCertListResp.Marker
|
||||
}
|
||||
|
||||
// 上传新证书
|
||||
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
||||
uploadSslCertResp, err := c.sdkClient.UploadSslCert(ctx, certName, certX509.Subject.CommonName, certPEM, privkeyPEM)
|
||||
c.logger.Debug("sdk request 'sslcert.Upload'", slog.Any("response", uploadSslCertResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'sslcert.Upload': %w", err)
|
||||
}
|
||||
|
||||
return &UploadResult{
|
||||
CertId: uploadSslCertResp.CertID,
|
||||
CertName: certName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
|
||||
return nil, core.ErrUnsupported
|
||||
}
|
||||
|
||||
func createSDKClient(accessKey, secretKey string) (*qiniusdk.SslCertManager, error) {
|
||||
if secretKey == "" {
|
||||
return nil, fmt.Errorf("qiniu: invalid access key")
|
||||
}
|
||||
if secretKey == "" {
|
||||
return nil, fmt.Errorf("qiniu: invalid secret key")
|
||||
}
|
||||
|
||||
credential := auth.New(accessKey, secretKey)
|
||||
client := qiniusdk.NewSslCertManager(credential)
|
||||
return client, nil
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package qiniusslcert_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/qiniu-sslcert"
|
||||
tester "github.com/certimate-go/certimate/pkg/core/certmgr/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("QINIUSSLCERT_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKey, "ACCESSKEY")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./qiniu_sslcert_test.go -args \
|
||||
--QINIUSSLCERT_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--QINIUSSLCERT_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--QINIUSSLCERT_ACCESSKEY="your-access-key" \
|
||||
--QINIUSSLCERT_SECRETKEY="your-secret-key"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
|
||||
AccessKey: fAccessKey,
|
||||
SecretKey: fSecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestUpload(t, provider, tester.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package qiniucdn
|
||||
|
||||
const (
|
||||
// 匹配模式:精确匹配。
|
||||
DOMAIN_MATCH_PATTERN_EXACT = "exact"
|
||||
// 匹配模式:通配符匹配。
|
||||
DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard"
|
||||
// 匹配模式:证书 SAN 匹配。
|
||||
DOMAIN_MATCH_PATTERN_CERTSAN = "certsan"
|
||||
)
|
||||
@@ -1,231 +0,0 @@
|
||||
package qiniucdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/qiniu-sslcert"
|
||||
qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 七牛云 AccessKey。
|
||||
AccessKey string `json:"accessKey"`
|
||||
// 七牛云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
// 域名匹配模式。
|
||||
// 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。
|
||||
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *qiniusdk.CdnManager
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client := qiniusdk.NewCdnManager(auth.New(config.AccessKey, config.SecretKey))
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
d.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
|
||||
d.sdkCertmgr.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
|
||||
// 上传证书
|
||||
upres, err := d.sdkCertmgr.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))
|
||||
}
|
||||
|
||||
// 获取待部署的域名列表
|
||||
var domains []string
|
||||
switch d.config.DomainMatchPattern {
|
||||
case "", DOMAIN_MATCH_PATTERN_EXACT:
|
||||
{
|
||||
domain := normalizeDomain(d.config.Domain)
|
||||
if domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
domains = []string{domain}
|
||||
}
|
||||
|
||||
case DOMAIN_MATCH_PATTERN_WILDCARD:
|
||||
{
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||
domainCandidates, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return xcerthostname.IsMatch(d.config.Domain, domain)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
} else {
|
||||
domains = []string{d.config.Domain}
|
||||
}
|
||||
}
|
||||
|
||||
case DOMAIN_MATCH_PATTERN_CERTSAN:
|
||||
{
|
||||
domainCandidates, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return xcerthostname.IsMatchByCertificatePEM(certPEM, domain)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern)
|
||||
}
|
||||
|
||||
// 批量更新域名证书
|
||||
if len(domains) == 0 {
|
||||
d.logger.Info("no cdn domains to deploy")
|
||||
} else {
|
||||
d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains))
|
||||
|
||||
if err := xloop.ForRangeAllWithContext(ctx, domains, func(ctx context.Context, domain string, _ int) error {
|
||||
return d.updateDomainCertificate(ctx, domain, upres.CertId)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) {
|
||||
domains := make([]string, 0)
|
||||
|
||||
// 查询域名列表
|
||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||
getDomainListMarker := ""
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
getDomainListResp, err := d.sdkClient.GetDomainList(ctx, getDomainListMarker, 100)
|
||||
d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.String("params.marker", getDomainListMarker), slog.Any("response", getDomainListResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err)
|
||||
}
|
||||
|
||||
ignoredStatuses := []string{"frozen", "offlined"}
|
||||
for _, domainItem := range getDomainListResp.Domains {
|
||||
if lo.Contains(ignoredStatuses, domainItem.OperatingState) {
|
||||
continue
|
||||
}
|
||||
|
||||
domains = append(domains, domainItem.Name)
|
||||
}
|
||||
|
||||
if len(getDomainListResp.Domains) == 0 || getDomainListResp.Marker == "" {
|
||||
break
|
||||
}
|
||||
|
||||
getDomainListMarker = getDomainListResp.Marker
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error {
|
||||
// 获取域名信息
|
||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(ctx, domain)
|
||||
d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("params.domain", domain), slog.Any("response", getDomainInfoResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err)
|
||||
}
|
||||
|
||||
// 判断域名是否已启用 HTTPS
|
||||
// 如果已启用,修改域名证书;否则,启用 HTTPS
|
||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||
if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" {
|
||||
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(ctx, domain, cloudCertId, true, true)
|
||||
d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("params.domain", domain), slog.String("params.certId", cloudCertId), slog.Any("response", enableDomainHttpsResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err)
|
||||
}
|
||||
} else if getDomainInfoResp.Https.CertID != cloudCertId {
|
||||
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(ctx, domain, cloudCertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
||||
d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("params.domain", domain), slog.String("params.certId", cloudCertId), slog.Any("response", modifyDomainHttpsConfResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeDomain(domain string) string {
|
||||
// "*.example.com" → ".example.com",适配七牛云 CDN 的泛域名参数要求
|
||||
if strings.HasPrefix(domain, "*.") {
|
||||
return strings.TrimPrefix(domain, "*")
|
||||
}
|
||||
return domain
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package qiniucdn_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-cdn"
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("QINIUCDN_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKey, "ACCESSKEY")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./qiniu_cdn_test.go -args \
|
||||
--QINIUCDN_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--QINIUCDN_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--QINIUCDN_ACCESSKEY="your-access-key" \
|
||||
--QINIUCDN_SECRETKEY="your-secret-key" \
|
||||
--QINIUCDN_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
AccessKey: fAccessKey,
|
||||
SecretKey: fSecretKey,
|
||||
DomainMatchPattern: impl.DOMAIN_MATCH_PATTERN_EXACT,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package qiniukodo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/qiniu-sslcert"
|
||||
qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 七牛云 AccessKey。
|
||||
AccessKey string `json:"accessKey"`
|
||||
// 七牛云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
// 存储桶名。暂时无用。
|
||||
Bucket string `json:"bucket"`
|
||||
// 自定义域名(不支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *qiniusdk.KodoManager
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client := qiniusdk.NewKodoManager(auth.New(config.AccessKey, config.SecretKey))
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
d.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
|
||||
d.sdkCertmgr.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
upres, err := d.sdkCertmgr.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))
|
||||
}
|
||||
|
||||
// 绑定空间域名证书
|
||||
bindBucketCertResp, err := d.sdkClient.BindBucketCert(ctx, d.config.Domain, upres.CertId)
|
||||
d.logger.Debug("sdk request 'kodo.BindCert'", slog.String("params.domain", d.config.Domain), slog.String("params.certId", upres.CertId), slog.Any("response", bindBucketCertResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'kodo.BindCert': %w", err)
|
||||
}
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package qiniukodo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-kodo"
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("QINIUKODO_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
fBucket string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKey, "ACCESSKEY")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
fp.DefineString(&fBucket, "BUCKET")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./qiniu_kodo_test.go -args \
|
||||
--QINIUKODO_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--QINIUKODO_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--QINIUKODO_ACCESSKEY="your-access-key" \
|
||||
--QINIUKODO_SECRETKEY="your-secret-key" \
|
||||
--QINIUKODO_BUCKET="your-bucket" \
|
||||
--QINIUKODO_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
AccessKey: fAccessKey,
|
||||
SecretKey: fSecretKey,
|
||||
Bucket: fBucket,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package qiniupili
|
||||
|
||||
const (
|
||||
// 匹配模式:精确匹配。
|
||||
DOMAIN_MATCH_PATTERN_EXACT = "exact"
|
||||
// 匹配模式:证书 SAN 匹配。
|
||||
DOMAIN_MATCH_PATTERN_CERTSAN = "certsan"
|
||||
)
|
||||
@@ -1,174 +0,0 @@
|
||||
package qiniupili
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/pili"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/qiniu-sslcert"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 七牛云 AccessKey。
|
||||
AccessKey string `json:"accessKey"`
|
||||
// 七牛云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
// 直播空间名。
|
||||
Hub string `json:"hub"`
|
||||
// 域名匹配模式。
|
||||
// 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。
|
||||
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
|
||||
// 直播流域名(不支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *pili.Manager
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
manager := pili.NewManager(pili.ManagerConfig{AccessKey: config.AccessKey, SecretKey: config.SecretKey})
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: manager,
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
d.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
|
||||
d.sdkCertmgr.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
upres, err := d.sdkCertmgr.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))
|
||||
}
|
||||
|
||||
// 获取待部署的域名列表
|
||||
var domains []string
|
||||
switch d.config.DomainMatchPattern {
|
||||
case "", DOMAIN_MATCH_PATTERN_EXACT:
|
||||
{
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
domains = []string{d.config.Domain}
|
||||
}
|
||||
|
||||
case DOMAIN_MATCH_PATTERN_CERTSAN:
|
||||
{
|
||||
domainCandidates, err := d.getAllDomainsByHub(ctx, d.config.Hub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return xcerthostname.IsMatchByCertificatePEM(certPEM, domain)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern)
|
||||
}
|
||||
|
||||
// 批量更新域名证书
|
||||
if len(domains) == 0 {
|
||||
d.logger.Info("no pili domains to deploy")
|
||||
} else {
|
||||
d.logger.Info("found pili domains to deploy", slog.Any("domains", domains))
|
||||
|
||||
if err := xloop.ForRangeAllWithContext(ctx, domains, func(ctx context.Context, domain string, _ int) error {
|
||||
return d.updateDomainCertificate(ctx, d.config.Hub, domain, upres.CertName)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) getAllDomainsByHub(ctx context.Context, hub string) ([]string, error) {
|
||||
domains := make([]string, 0)
|
||||
|
||||
// 查询域名列表
|
||||
// REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6
|
||||
getDomainListReq := pili.GetDomainsListRequest{
|
||||
Hub: hub,
|
||||
}
|
||||
getDomainListResp, err := d.sdkClient.GetDomainsList(ctx, getDomainListReq)
|
||||
d.logger.Debug("sdk request 'pili.GetDomainsList'", slog.Any("request", getDomainListReq), slog.Any("response", getDomainListResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'pili.GetDomainsList': %w", err)
|
||||
}
|
||||
|
||||
for _, domainItem := range getDomainListResp.Domains {
|
||||
domains = append(domains, domainItem.Domain)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, hub string, domain string, cloudCertName string) error {
|
||||
// 修改域名证书配置
|
||||
// REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6
|
||||
setDomainCertReq := pili.SetDomainCertRequest{
|
||||
Hub: hub,
|
||||
Domain: domain,
|
||||
CertName: cloudCertName,
|
||||
}
|
||||
err := d.sdkClient.SetDomainCert(ctx, setDomainCertReq)
|
||||
d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package qiniupili_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/qiniu-pili"
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("QINIUPILI_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
fHub string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKey, "ACCESSKEY")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
fp.DefineString(&fHub, "HUB")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./qiniu_pili_test.go -args \
|
||||
--QINIUPILI_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--QINIUPILI_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--QINIUPILI_ACCESSKEY="your-access-key" \
|
||||
--QINIUPILI_SECRETKEY="your-secret-key" \
|
||||
--QINIUPILI_HUB="your-hub-name" \
|
||||
--QINIUPILI_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
AccessKey: fAccessKey,
|
||||
SecretKey: fSecretKey,
|
||||
DomainMatchPattern: impl.DOMAIN_MATCH_PATTERN_EXACT,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
xcertx509 "github.com/certimate-go/certimate/pkg/utils/cert/x509"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// Webhook URL。
|
||||
WebhookUrl string `json:"webhookUrl"`
|
||||
// Webhook 回调数据(application/json 或 application/x-www-form-urlencoded 格式)。
|
||||
WebhookData string `json:"webhookData,omitempty"`
|
||||
// 请求谓词。
|
||||
// 零值时默认值 "POST"。
|
||||
Method string `json:"method,omitempty"`
|
||||
// 请求标头。
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
// 请求超时(单位:秒)。
|
||||
// 零值时默认值 30。
|
||||
Timeout int `json:"timeout,omitempty"`
|
||||
// 是否允许不安全的连接。
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
httpClient *resty.Client
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
|
||||
const (
|
||||
contentTypeJson = "application/json"
|
||||
contentTypeForm = "application/x-www-form-urlencoded"
|
||||
contentTypeMultipart = "multipart/form-data"
|
||||
)
|
||||
|
||||
var allowedContentTypes = map[string]bool{
|
||||
contentTypeJson: true,
|
||||
contentTypeForm: true,
|
||||
contentTypeMultipart: true,
|
||||
}
|
||||
|
||||
var allowedMethods = map[string]bool{
|
||||
http.MethodGet: true,
|
||||
http.MethodPost: true,
|
||||
http.MethodPut: true,
|
||||
http.MethodPatch: true,
|
||||
http.MethodDelete: true,
|
||||
}
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client := resty.New().
|
||||
SetTimeout(30 * time.Second).
|
||||
SetRetryCount(3).
|
||||
SetRetryWaitTime(5 * time.Second).
|
||||
AddRetryCondition(func(resp *resty.Response, _ error) bool {
|
||||
return resp == nil || resp.StatusCode() >= 500
|
||||
})
|
||||
if config.Timeout > 0 {
|
||||
client.SetTimeout(time.Duration(config.Timeout) * time.Second)
|
||||
}
|
||||
if config.AllowInsecureConnections {
|
||||
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
httpClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
d.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
|
||||
// 解析证书内容
|
||||
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse x509: %w", err)
|
||||
}
|
||||
|
||||
// 提取服务器证书和中间证书
|
||||
serverCertPEM, issuerCertPEM, err := xcert.ExtractCertificatesFromPEM(certPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||
}
|
||||
|
||||
// 处理 Webhook URL
|
||||
webhookUrl, err := url.Parse(d.config.WebhookUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook url: %w", err)
|
||||
} else if webhookUrl.Scheme != "http" && webhookUrl.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported webhook url scheme '%s'", webhookUrl.Scheme)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求谓词
|
||||
webhookMethod := strings.ToUpper(d.config.Method)
|
||||
if webhookMethod == "" {
|
||||
webhookMethod = http.MethodPost
|
||||
} else if !allowedMethods[webhookMethod] {
|
||||
return nil, fmt.Errorf("unsupported webhook request method '%s'", webhookMethod)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求标头
|
||||
webhookHeaders := make(http.Header)
|
||||
for k, v := range d.config.Headers {
|
||||
webhookHeaders.Set(k, v)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求内容类型
|
||||
webhookContentType := webhookHeaders.Get("Content-Type")
|
||||
if webhookContentType == "" {
|
||||
webhookContentType = contentTypeJson
|
||||
webhookHeaders.Set("Content-Type", contentTypeJson)
|
||||
} else if mediaType, _, err := mime.ParseMediaType(webhookContentType); err != nil || !allowedContentTypes[mediaType] {
|
||||
return nil, fmt.Errorf("unsupported webhook content type '%s'", webhookContentType)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求数据
|
||||
var webhookData any
|
||||
if d.config.WebhookData == "" {
|
||||
webhookData = map[string]string{
|
||||
"name": strings.Join(xcertx509.GetSubjectAltNames(certX509), ";"),
|
||||
"cert": certPEM,
|
||||
"privkey": privkeyPEM,
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
}
|
||||
|
||||
if webhookMethod == http.MethodGet || webhookContentType == contentTypeForm || webhookContentType == contentTypeMultipart {
|
||||
temp := make(map[string]string)
|
||||
jsonb, err := json.Marshal(webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else if err := json.Unmarshal(jsonb, &temp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else {
|
||||
webhookData = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 替换变量值
|
||||
webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${CERTIMATE_DEPLOYER_COMMONNAME}", url.PathEscape(xcertx509.GetSubjectCommonName(certX509)))
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_COMMONNAME}", xcertx509.GetSubjectCommonName(certX509))
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_SUBJECTALTNAMES}", strings.Join(xcertx509.GetSubjectAltNames(certX509), ";"))
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE}", certPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_SERVER}", serverCertPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_INTERMEDIA}", issuerCertPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_PRIVATEKEY}", privkeyPEM)
|
||||
|
||||
// 兼容旧版变量
|
||||
// TODO: remove in future version
|
||||
webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${DOMAIN}", url.PathEscape(certX509.Subject.CommonName))
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${DOMAIN}", certX509.Subject.CommonName)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";"))
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIFICATE}", certPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${SERVER_CERTIFICATE}", serverCertPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${INTERMEDIA_CERTIFICATE}", issuerCertPEM)
|
||||
xmaps.DeepReplaceValueUnsafe(webhookData, "${PRIVATE_KEY}", privkeyPEM)
|
||||
|
||||
// 生成请求
|
||||
// 其中 GET 请求需转换为查询参数
|
||||
req := d.httpClient.R().SetHeaderMultiValues(webhookHeaders)
|
||||
req.URL = webhookUrl.String()
|
||||
req.Method = webhookMethod
|
||||
if webhookMethod == http.MethodGet {
|
||||
req.SetQueryParams(webhookData.(map[string]string))
|
||||
} else {
|
||||
switch webhookContentType {
|
||||
case contentTypeJson:
|
||||
req.SetBody(webhookData)
|
||||
case contentTypeForm:
|
||||
req.SetFormData(webhookData.(map[string]string))
|
||||
case contentTypeMultipart:
|
||||
req.SetMultipartFormData(webhookData.(map[string]string))
|
||||
}
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send webhook request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode())
|
||||
}
|
||||
|
||||
d.logger.Debug("webhook responded", slog.Any("response", resp.String()))
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package webhook_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/webhook"
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("WEBHOOK_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fWebhookUrl string
|
||||
fWebhookContentType string
|
||||
fWebhookData string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fWebhookUrl, "URL")
|
||||
fp.DefineString(&fWebhookContentType, "CONTENTTYPE", "application/json")
|
||||
fp.DefineString(&fWebhookData, "DATA")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./webhook_test.go -args \
|
||||
--WEBHOOK_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--WEBHOOK_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--WEBHOOK_URL="https://example.com/your-webhook-url" \
|
||||
--WEBHOOK_CONTENTTYPE="application/json" \
|
||||
--WEBHOOK_DATA="{\"certificate\":\"${CERTIFICATE}\",\"privateKey\":\"${PRIVATE_KEY}\"}"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
WebhookUrl: fWebhookUrl,
|
||||
WebhookData: fWebhookData,
|
||||
Method: "POST",
|
||||
Headers: map[string]string{
|
||||
"Content-Type": fWebhookContentType,
|
||||
},
|
||||
AllowInsecureConnections: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"github.com/qiniu/go-sdk/v7/client"
|
||||
)
|
||||
|
||||
type transport struct {
|
||||
http.RoundTripper
|
||||
mac *auth.Credentials
|
||||
}
|
||||
|
||||
func newTransport(mac *auth.Credentials, tr http.RoundTripper) *transport {
|
||||
if tr == nil {
|
||||
tr = client.DefaultTransport
|
||||
}
|
||||
return &transport{tr, mac}
|
||||
}
|
||||
|
||||
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
token, err := t.mac.SignRequestV2(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Qiniu "+token)
|
||||
return t.RoundTripper.RoundTrip(req)
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"github.com/qiniu/go-sdk/v7/client"
|
||||
)
|
||||
|
||||
type CdnManager struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func NewCdnManager(mac *auth.Credentials) *CdnManager {
|
||||
if mac == nil {
|
||||
mac = auth.Default()
|
||||
}
|
||||
|
||||
client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}}
|
||||
return &CdnManager{client: client}
|
||||
}
|
||||
|
||||
type GetDomainListResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
Marker string `json:"marker"`
|
||||
Domains []*struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CName string `json:"cname"`
|
||||
OperatingState string `json:"operatingState"`
|
||||
OperatingStateDesc string `json:"operatingStateDesc"`
|
||||
CreateAt string `json:"createAt"`
|
||||
ModifyAt string `json:"modifyAt"`
|
||||
} `json:"domains"`
|
||||
}
|
||||
|
||||
func (m *CdnManager) GetDomainList(ctx context.Context, marker string, limit int) (*GetDomainListResponse, error) {
|
||||
query := url.Values{}
|
||||
if marker != "" {
|
||||
query.Set("marker", marker)
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Set("limit", fmt.Sprintf("%d", limit))
|
||||
}
|
||||
|
||||
resp := new(GetDomainListResponse)
|
||||
if err := m.client.Call(ctx, resp, http.MethodGet, urlf("domain?%s", query.Encode()), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type GetDomainInfoResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CName string `json:"cname"`
|
||||
Https *struct {
|
||||
CertID string `json:"certId"`
|
||||
ForceHttps bool `json:"forceHttps"`
|
||||
Http2Enable bool `json:"http2Enable"`
|
||||
} `json:"https"`
|
||||
PareDomain string `json:"pareDomain"`
|
||||
OperationType string `json:"operationType"`
|
||||
OperatingState string `json:"operatingState"`
|
||||
OperatingStateDesc string `json:"operatingStateDesc"`
|
||||
CreateAt string `json:"createAt"`
|
||||
ModifyAt string `json:"modifyAt"`
|
||||
}
|
||||
|
||||
func (m *CdnManager) GetDomainInfo(ctx context.Context, domain string) (*GetDomainInfoResponse, error) {
|
||||
resp := new(GetDomainInfoResponse)
|
||||
if err := m.client.Call(ctx, resp, http.MethodGet, urlf("domain/%s", domain), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type ModifyDomainHttpsConfRequest struct {
|
||||
CertID string `json:"certId"`
|
||||
ForceHttps bool `json:"forceHttps"`
|
||||
Http2Enable bool `json:"http2Enable"`
|
||||
}
|
||||
|
||||
type ModifyDomainHttpsConfResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CdnManager) ModifyDomainHttpsConf(ctx context.Context, domain string, certId string, forceHttps bool, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) {
|
||||
req := &ModifyDomainHttpsConfRequest{
|
||||
CertID: certId,
|
||||
ForceHttps: forceHttps,
|
||||
Http2Enable: http2Enable,
|
||||
}
|
||||
resp := new(ModifyDomainHttpsConfResponse)
|
||||
if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("domain/%s/httpsconf", domain), nil, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type EnableDomainHttpsRequest struct {
|
||||
CertID string `json:"certId"`
|
||||
ForceHttps bool `json:"forceHttps"`
|
||||
Http2Enable bool `json:"http2Enable"`
|
||||
}
|
||||
|
||||
type EnableDomainHttpsResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CdnManager) EnableDomainHttps(ctx context.Context, domain string, certId string, forceHttps bool, http2Enable bool) (*EnableDomainHttpsResponse, error) {
|
||||
req := &EnableDomainHttpsRequest{
|
||||
CertID: certId,
|
||||
ForceHttps: forceHttps,
|
||||
Http2Enable: http2Enable,
|
||||
}
|
||||
resp := new(EnableDomainHttpsResponse)
|
||||
if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("domain/%s/sslize", domain), nil, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// An extension SDK client for Qiniu.
|
||||
// Based on github.com/qiniu/go-sdk.
|
||||
package qiniu
|
||||
@@ -1,44 +0,0 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"github.com/qiniu/go-sdk/v7/client"
|
||||
)
|
||||
|
||||
type KodoManager struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func NewKodoManager(mac *auth.Credentials) *KodoManager {
|
||||
if mac == nil {
|
||||
mac = auth.Default()
|
||||
}
|
||||
|
||||
client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}}
|
||||
return &KodoManager{client: client}
|
||||
}
|
||||
|
||||
type BindBucketCertRequest struct {
|
||||
CertID string `json:"certid"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type BindBucketCertResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *KodoManager) BindBucketCert(ctx context.Context, domain string, certId string) (*BindBucketCertResponse, error) {
|
||||
req := &BindBucketCertRequest{
|
||||
CertID: certId,
|
||||
Domain: domain,
|
||||
}
|
||||
resp := new(BindBucketCertResponse)
|
||||
if err := m.client.CallWithJson(ctx, resp, http.MethodPut, urlf("cert/bind"), nil, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"github.com/qiniu/go-sdk/v7/client"
|
||||
)
|
||||
|
||||
type SslCertManager struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func NewSslCertManager(mac *auth.Credentials) *SslCertManager {
|
||||
if mac == nil {
|
||||
mac = auth.Default()
|
||||
}
|
||||
|
||||
client := &client.Client{Client: &http.Client{Transport: newTransport(mac, nil)}}
|
||||
return &SslCertManager{client: client}
|
||||
}
|
||||
|
||||
type GetSslCertListResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
Certs []*struct {
|
||||
CertID string `json:"certid"`
|
||||
Name string `json:"name"`
|
||||
CommonName string `json:"common_name"`
|
||||
DnsNames []string `json:"dnsnames"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
NotBefore int64 `json:"not_before"`
|
||||
NotAfter int64 `json:"not_after"`
|
||||
ProductType string `json:"product_type"`
|
||||
ProductShortName string `json:"product_short_name,omitempty"`
|
||||
OrderId string `json:"orderid,omitempty"`
|
||||
CertType string `json:"cert_type"`
|
||||
Encrypt string `json:"encrypt"`
|
||||
EncryptParameter string `json:"encryptParameter,omitempty"`
|
||||
Enable bool `json:"enable"`
|
||||
} `json:"certs"`
|
||||
Marker string `json:"marker"`
|
||||
}
|
||||
|
||||
func (m *SslCertManager) GetSslCertList(ctx context.Context, marker string, limit int32) (*GetSslCertListResponse, error) {
|
||||
resp := new(GetSslCertListResponse)
|
||||
if err := m.client.Call(ctx, resp, http.MethodGet, urlf("sslcert?marker=%s&limit=%d", url.QueryEscape(marker), limit), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type UploadSslCertRequest struct {
|
||||
Name string `json:"name"`
|
||||
CommonName string `json:"common_name"`
|
||||
Certificate string `json:"ca"`
|
||||
PrivateKey string `json:"pri"`
|
||||
}
|
||||
|
||||
type UploadSslCertResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
CertID string `json:"certID"`
|
||||
}
|
||||
|
||||
func (m *SslCertManager) UploadSslCert(ctx context.Context, name string, commonName string, certificate string, privateKey string) (*UploadSslCertResponse, error) {
|
||||
req := &UploadSslCertRequest{
|
||||
Name: name,
|
||||
CommonName: commonName,
|
||||
Certificate: certificate,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
resp := new(UploadSslCertResponse)
|
||||
if err := m.client.CallWithJson(ctx, resp, http.MethodPost, urlf("sslcert"), nil, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const qiniuHost = "https://api.qiniu.com"
|
||||
|
||||
func urlf(pathf string, pathargs ...any) string {
|
||||
path := fmt.Sprintf(pathf, pathargs...)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
return qiniuHost + "/" + path
|
||||
}
|
||||
@@ -90,7 +90,6 @@ import AccessConfigFieldsProviderPorkbun from "./AccessConfigFieldsProviderPorkb
|
||||
import AccessConfigFieldsProviderPowerDNS from "./AccessConfigFieldsProviderPowerDNS";
|
||||
import AccessConfigFieldsProviderProxmoxVE from "./AccessConfigFieldsProviderProxmoxVE";
|
||||
import AccessConfigFieldsProviderQingCloud from "./AccessConfigFieldsProviderQingCloud";
|
||||
import AccessConfigFieldsProviderQiniu from "./AccessConfigFieldsProviderQiniu";
|
||||
import AccessConfigFieldsProviderRainYun from "./AccessConfigFieldsProviderRainYun";
|
||||
import AccessConfigFieldsProviderRatPanel from "./AccessConfigFieldsProviderRatPanel";
|
||||
import AccessConfigFieldsProviderRegru from "./AccessConfigFieldsProviderRegru";
|
||||
@@ -215,7 +214,6 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.POWERDNS]: AccessConfigFieldsProviderPowerDNS,
|
||||
[ACCESS_PROVIDERS.PROXMOXVE]: AccessConfigFieldsProviderProxmoxVE,
|
||||
[ACCESS_PROVIDERS.QINGCLOUD]: AccessConfigFieldsProviderQingCloud,
|
||||
[ACCESS_PROVIDERS.QINIU]: AccessConfigFieldsProviderQiniu,
|
||||
[ACCESS_PROVIDERS.RAINYUN]: AccessConfigFieldsProviderRainYun,
|
||||
[ACCESS_PROVIDERS.RATPANEL]: AccessConfigFieldsProviderRatPanel,
|
||||
[ACCESS_PROVIDERS.REGRU]: AccessConfigFieldsProviderRegru,
|
||||
@@ -287,7 +285,9 @@ const useComponent = (provider: string, { initProps, deps = [] }: { initProps?:
|
||||
setComponent(envelope ? <SchemaConfigFields envelope={envelope} /> : initComponent());
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [provider, envelope]);
|
||||
useEffect(() => setComponent(initComponent()), deps);
|
||||
useEffect(() => {
|
||||
setComponent(envelope ? <SchemaConfigFields envelope={envelope} /> : initComponent());
|
||||
}, deps);
|
||||
|
||||
return component;
|
||||
};
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderQiniu = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "accessKey"]}
|
||||
initialValue={initialValues.accessKey}
|
||||
label={t("access.form.qiniu_access_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_access_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.qiniu_access_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "secretKey"]}
|
||||
initialValue={initialValues.secretKey}
|
||||
label={t("access.form.qiniu_secret_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_secret_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.qiniu_secret_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
|
||||
return z.object({
|
||||
accessKey: z.string().nonempty(),
|
||||
secretKey: z.string().nonempty(),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderQiniu, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -69,13 +69,13 @@ const DeploymentProviderPicker = forwardRef<DeploymentProviderPickerInstance, De
|
||||
<Tooltip title={t(provider.name)} mouseEnterDelay={1}>
|
||||
<Typography.Text>{t(provider.name) || "\u00A0"}</Typography.Text>
|
||||
</Tooltip>
|
||||
{provider.source === "plugin" ? (
|
||||
<Tag className="ml-1" color="orange">
|
||||
{t("provider.tag.plugin")}
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{provider.source === "plugin" ? (
|
||||
<Tag className="shrink-0" color="orange">
|
||||
{t("provider.tag.plugin")}
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -92,9 +92,6 @@ import BizDeployNodeConfigFieldsProviderNginxProxyManager from "./BizDeployNodeC
|
||||
import BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt from "./BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt";
|
||||
import BizDeployNodeConfigFieldsProviderProxmoxVE from "./BizDeployNodeConfigFieldsProviderProxmoxVE";
|
||||
import BizDeployNodeConfigFieldsProviderQingCloudLB from "./BizDeployNodeConfigFieldsProviderQingCloudLB";
|
||||
import BizDeployNodeConfigFieldsProviderQiniuCDN from "./BizDeployNodeConfigFieldsProviderQiniuCDN";
|
||||
import BizDeployNodeConfigFieldsProviderQiniuKodo from "./BizDeployNodeConfigFieldsProviderQiniuKodo";
|
||||
import BizDeployNodeConfigFieldsProviderQiniuPili from "./BizDeployNodeConfigFieldsProviderQiniuPili";
|
||||
import BizDeployNodeConfigFieldsProviderRainYunRCDN from "./BizDeployNodeConfigFieldsProviderRainYunRCDN";
|
||||
import BizDeployNodeConfigFieldsProviderRainYunSSLCenter from "./BizDeployNodeConfigFieldsProviderRainYunSSLCenter";
|
||||
import BizDeployNodeConfigFieldsProviderRatPanel from "./BizDeployNodeConfigFieldsProviderRatPanel";
|
||||
@@ -143,6 +140,7 @@ import BizDeployNodeConfigFieldsProviderVolcEngineWAF from "./BizDeployNodeConfi
|
||||
import BizDeployNodeConfigFieldsProviderWangsuCDN from "./BizDeployNodeConfigFieldsProviderWangsuCDN";
|
||||
import BizDeployNodeConfigFieldsProviderWangsuCDNPro from "./BizDeployNodeConfigFieldsProviderWangsuCDNPro";
|
||||
import BizDeployNodeConfigFieldsProviderWangsuCertificate from "./BizDeployNodeConfigFieldsProviderWangsuCertificate";
|
||||
import BizDeployNodeConfigFieldsProviderWebhook from "./BizDeployNodeConfigFieldsProviderWebhook";
|
||||
import BizDeployNodeConfigFieldsProviderZenlayerCDN from "./BizDeployNodeConfigFieldsProviderZenlayerCDN";
|
||||
import BizDeployNodeConfigFieldsProviderZenlayerGA from "./BizDeployNodeConfigFieldsProviderZenlayerGA";
|
||||
import SchemaConfigFields from "./SchemaConfigFields";
|
||||
@@ -241,9 +239,6 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
|
||||
[DEPLOYMENT_PROVIDERS.ORACLECLOUD_CERTIFICATESMGMT]: BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt,
|
||||
[DEPLOYMENT_PROVIDERS.PROXMOXVE]: BizDeployNodeConfigFieldsProviderProxmoxVE,
|
||||
[DEPLOYMENT_PROVIDERS.QINGCLOUD_LB]: BizDeployNodeConfigFieldsProviderQingCloudLB,
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_CDN]: BizDeployNodeConfigFieldsProviderQiniuCDN,
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_KODO]: BizDeployNodeConfigFieldsProviderQiniuKodo,
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_PILI]: BizDeployNodeConfigFieldsProviderQiniuPili,
|
||||
[DEPLOYMENT_PROVIDERS.RAINYUN_RCDN]: BizDeployNodeConfigFieldsProviderRainYunRCDN,
|
||||
[DEPLOYMENT_PROVIDERS.RAINYUN_SSLCENTER]: BizDeployNodeConfigFieldsProviderRainYunSSLCenter,
|
||||
[DEPLOYMENT_PROVIDERS.RATPANEL]: BizDeployNodeConfigFieldsProviderRatPanel,
|
||||
@@ -292,6 +287,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CDN]: BizDeployNodeConfigFieldsProviderWangsuCDN,
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO]: BizDeployNodeConfigFieldsProviderWangsuCDNPro,
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE]: BizDeployNodeConfigFieldsProviderWangsuCertificate,
|
||||
[DEPLOYMENT_PROVIDERS.WEBHOOK]: BizDeployNodeConfigFieldsProviderWebhook,
|
||||
[DEPLOYMENT_PROVIDERS.ZENLAYER_CDN]: BizDeployNodeConfigFieldsProviderZenlayerCDN,
|
||||
[DEPLOYMENT_PROVIDERS.ZENLAYER_GA]: BizDeployNodeConfigFieldsProviderZenlayerGA,
|
||||
};
|
||||
@@ -333,7 +329,9 @@ const useComponent = (provider: string, { initProps, deps = [] }: { initProps?:
|
||||
setComponent(envelope ? <SchemaConfigFields envelope={envelope} /> : initComponent());
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [provider, envelope]);
|
||||
useEffect(() => setComponent(initComponent()), deps);
|
||||
useEffect(() => {
|
||||
setComponent(envelope ? <SchemaConfigFields envelope={envelope} /> : initComponent());
|
||||
}, deps);
|
||||
|
||||
return component;
|
||||
};
|
||||
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input, Radio } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { isDomain } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const;
|
||||
const DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" as const;
|
||||
const DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" as const;
|
||||
|
||||
const BizDeployNodeConfigFieldsProviderQiniuCDN = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domainMatchPattern"]}
|
||||
initialValue={initialValues.domainMatchPattern}
|
||||
label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")}
|
||||
extra={
|
||||
fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? (
|
||||
<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_domain_match_pattern.option.exact.help.wildcard") }}></span>
|
||||
) : (
|
||||
void 0
|
||||
)
|
||||
}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Radio.Group
|
||||
options={[DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN].map((s) => ({
|
||||
label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldDomainMatchPattern !== DOMAIN_MATCH_PATTERN_CERTSAN}>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domain"]}
|
||||
initialValue={initialValues.domain}
|
||||
label={t("workflow_node.deploy.form.qiniu_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT,
|
||||
domain: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z
|
||||
.object({
|
||||
domainMatchPattern: z.string().nonempty().default(DOMAIN_MATCH_PATTERN_EXACT),
|
||||
domain: z.string().nullish(),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
if (values.domainMatchPattern) {
|
||||
switch (values.domainMatchPattern) {
|
||||
case DOMAIN_MATCH_PATTERN_EXACT:
|
||||
case DOMAIN_MATCH_PATTERN_WILDCARD:
|
||||
{
|
||||
const scDomain = z.string().refine((v) => isDomain(v, { allowWildcard: true }), t("common.errmsg.domain_invalid"));
|
||||
const spDomain = scDomain.safeParse(values.domain);
|
||||
if (!spDomain.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spDomain.error).errors.join(),
|
||||
path: ["domain"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(BizDeployNodeConfigFieldsProviderQiniuCDN, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { isDomain } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const BizDeployNodeConfigFieldsProviderQiniuKodo = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "bucket"]}
|
||||
initialValue={initialValues.domain}
|
||||
label={t("workflow_node.deploy.form.qiniu_kodo_bucket.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_kodo_bucket.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domain"]}
|
||||
initialValue={initialValues.domain}
|
||||
label={t("workflow_node.deploy.form.qiniu_kodo_domain.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_kodo_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
bucket: "",
|
||||
domain: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
bucket: z.string().nonempty(),
|
||||
domain: z.string().refine((v) => isDomain(v), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(BizDeployNodeConfigFieldsProviderQiniuKodo, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input, Radio } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { isDomain } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const;
|
||||
const DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" as const;
|
||||
|
||||
const BizDeployNodeConfigFieldsProviderQiniuPili = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "hub"]}
|
||||
initialValue={initialValues.hub}
|
||||
label={t("workflow_node.deploy.form.qiniu_pili_hub.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.qiniu_pili_hub.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_pili_hub.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domainMatchPattern"]}
|
||||
initialValue={initialValues.domainMatchPattern}
|
||||
label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Radio.Group
|
||||
options={[DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_CERTSAN].map((s) => ({
|
||||
label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldDomainMatchPattern !== DOMAIN_MATCH_PATTERN_CERTSAN}>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domain"]}
|
||||
initialValue={initialValues.domain}
|
||||
label={t("workflow_node.deploy.form.qiniu_pili_domain.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_pili_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
hub: "",
|
||||
domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT,
|
||||
domain: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z
|
||||
.object({
|
||||
hub: z.string().nonempty(),
|
||||
domainMatchPattern: z.string().nonempty().default(DOMAIN_MATCH_PATTERN_EXACT),
|
||||
domain: z.string().nullish(),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
if (values.domainMatchPattern) {
|
||||
switch (values.domainMatchPattern) {
|
||||
case DOMAIN_MATCH_PATTERN_EXACT:
|
||||
{
|
||||
const scDomain = z.string().refine((v) => isDomain(v), t("common.errmsg.domain_invalid"));
|
||||
const spDomain = scDomain.safeParse(values.domain);
|
||||
if (!spDomain.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spDomain.error).errors.join(),
|
||||
path: ["domain"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(BizDeployNodeConfigFieldsProviderQiniuPili, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { IconBulb } from "@tabler/icons-react";
|
||||
import { Button, Form, Input, Popover } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import CodeTextInput from "@/components/CodeTextInput";
|
||||
import { isJsonObject } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const BizDeployNodeConfigFieldsProviderWebhook = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const handleWebhookDataBlur = () => {
|
||||
const value = formInst.getFieldValue([parentNamePath, "webhookData"]);
|
||||
try {
|
||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||
formInst.setFieldValue([parentNamePath, "webhookData"], json);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t("workflow_node.deploy.form.webhook_data.label")} extra={t("workflow_node.deploy.form.webhook_data.help")}>
|
||||
<div className="absolute -top-1.5 right-0 -translate-y-full">
|
||||
<Popover content={<div dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.vartips") }} />} mouseEnterDelay={1}>
|
||||
<Button color="default" size="small" variant="link">
|
||||
<IconBulb size="1.25em" />
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
<Form.Item name={[parentNamePath, "webhookData"]} initialValue={initialValues.webhookData} noStyle rules={[formRule]}>
|
||||
<CodeTextInput
|
||||
lineWrapping={false}
|
||||
height="auto"
|
||||
minHeight="64px"
|
||||
maxHeight="256px"
|
||||
language="json"
|
||||
placeholder={t("workflow_node.deploy.form.webhook_data.placeholder")}
|
||||
onBlur={handleWebhookDataBlur}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={[parentNamePath, "timeout"]} label={t("workflow_node.deploy.form.webhook_timeout.label")} rules={[formRule]}>
|
||||
<Input
|
||||
type="number"
|
||||
allowClear
|
||||
min={0}
|
||||
max={3600}
|
||||
placeholder={t("workflow_node.deploy.form.webhook_timeout.placeholder")}
|
||||
suffix={t("workflow_node.deploy.form.webhook_timeout.unit")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
webhookData: z
|
||||
.string()
|
||||
.nullish()
|
||||
.refine((v) => {
|
||||
if (!v) return true;
|
||||
return isJsonObject(v);
|
||||
}, t("common.errmsg.json_invalid")),
|
||||
timeout: z.coerce.number().int().gt(0).or(z.literal("")).nullish(),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(BizDeployNodeConfigFieldsProviderWebhook, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ProviderSchemaEnvelope, SchemaColumn, SchemaCondition, SchemaValidationRule } from "@/api/providerschema";
|
||||
import i18n from "@/i18n";
|
||||
import {
|
||||
isCron,
|
||||
isDomain,
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
const isEmpty = (v: unknown) => v == null || v === "";
|
||||
|
||||
export const matchCondition = (cond: SchemaCondition, value: unknown): boolean => {
|
||||
const present = cond.values.includes(String(value));
|
||||
const present = (cond.values ?? []).includes(String(value));
|
||||
switch (cond.op) {
|
||||
case "eq":
|
||||
case "in":
|
||||
@@ -98,7 +99,7 @@ const fieldBaseType = (col: SchemaColumn): z.ZodType => {
|
||||
const applyRules = (schema: z.ZodType, rules: SchemaValidationRule[], allowEmpty: boolean): z.ZodType => {
|
||||
for (const rule of rules) {
|
||||
const ok = (v: unknown) => (allowEmpty && isEmpty(v)) || runValidator(rule.validator, rule.params, v);
|
||||
schema = schema.refine(ok, { message: `common.errmsg.${rule.validator}_invalid` });
|
||||
schema = schema.refine(ok, { message: i18n.t(`common.errmsg.${rule.validator}_invalid`) });
|
||||
}
|
||||
return schema;
|
||||
};
|
||||
@@ -159,13 +160,13 @@ const buildPlainObject = (columns: SchemaColumn[]): z.ZodType => {
|
||||
for (const cond of col.requiredWhen ?? []) {
|
||||
if (!matchCondition(cond, record[cond.field])) continue;
|
||||
if (isEmpty(value)) {
|
||||
ctx.addIssue({ code: "custom", message: `common.errmsg.required`, path: [col.name] });
|
||||
ctx.addIssue({ code: "custom", message: i18n.t("common.errmsg.required"), path: [col.name] });
|
||||
}
|
||||
}
|
||||
for (const rule of col.validateWhen ?? []) {
|
||||
if (!matchCondition(rule, record[rule.field])) continue;
|
||||
if (!isEmpty(value) && !runValidator(rule.validator, rule.params, value)) {
|
||||
ctx.addIssue({ code: "custom", message: `common.errmsg.${rule.validator}_invalid`, path: [col.name] });
|
||||
ctx.addIssue({ code: "custom", message: i18n.t(`common.errmsg.${rule.validator}_invalid`), path: [col.name] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
POWERDNS: "powerdns",
|
||||
PROXMOXVE: "proxmoxve",
|
||||
QINGCLOUD: "qingcloud",
|
||||
QINIU: "qiniu",
|
||||
RAINYUN: "rainyun",
|
||||
RATPANEL: "ratpanel",
|
||||
REGRU: "regru",
|
||||
@@ -201,7 +200,6 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.KSYUN, "provider.ksyun", "/imgs/providers/ksyun.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.WANGSU, "provider.wangsu", "/imgs/providers/wangsu.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.MOHUA, "provider.mohua", "/imgs/providers/mohua.png", [ACCESS_USAGES.HOSTING]],
|
||||
@@ -704,9 +702,6 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
|
||||
ORACLECLOUD_CERTIFICATESMGMT: `${ACCESS_PROVIDERS.ORACLECLOUD}-certificatesmgmt`,
|
||||
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
|
||||
QINGCLOUD_LB: `${ACCESS_PROVIDERS.QINGCLOUD}-lb`,
|
||||
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
|
||||
QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`,
|
||||
QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`,
|
||||
RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`,
|
||||
RAINYUN_SSLCENTER: `${ACCESS_PROVIDERS.RAINYUN}-sslcenter`,
|
||||
RATPANEL: `${ACCESS_PROVIDERS.RATPANEL}`,
|
||||
@@ -757,6 +752,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
|
||||
WANGSU_CDN: `${ACCESS_PROVIDERS.WANGSU}-cdn`,
|
||||
WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`,
|
||||
WANGSU_CERTIFICATE: `${ACCESS_PROVIDERS.WANGSU}-certificate`,
|
||||
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
|
||||
ZENLAYER_CDN: `${ACCESS_PROVIDERS.ZENLAYER}-cdn`,
|
||||
ZENLAYER_GA: `${ACCESS_PROVIDERS.ZENLAYER}-ga`,
|
||||
} as const);
|
||||
@@ -795,6 +791,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
|
||||
[DEPLOYMENT_PROVIDERS.SSH, "provider.ssh", DEPLOYMENT_CATEGORIES.OTHER],
|
||||
[DEPLOYMENT_PROVIDERS.FTP, "provider.ftp", DEPLOYMENT_CATEGORIES.OTHER],
|
||||
[DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes_secret", DEPLOYMENT_CATEGORIES.OTHER],
|
||||
[DEPLOYMENT_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOYMENT_CATEGORIES.OTHER],
|
||||
[DEPLOYMENT_PROVIDERS.S3, "provider.s3_upload", DEPLOYMENT_CATEGORIES.STORAGE],
|
||||
[DEPLOYMENT_PROVIDERS.ALIYUN_OSS, "provider.aliyun_oss", DEPLOYMENT_CATEGORIES.STORAGE],
|
||||
[DEPLOYMENT_PROVIDERS.ALIYUN_CDN, "provider.aliyun_cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
@@ -909,9 +906,6 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
|
||||
[DEPLOYMENT_PROVIDERS.UCLOUD_UPATHX, "provider.ucloud_upathx", DEPLOYMENT_CATEGORIES.ACCELERATOR],
|
||||
[DEPLOYMENT_PROVIDERS.BAISHAN_CDN, "provider.baishan_cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud_cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_KODO, "provider.qiniu_kodo", DEPLOYMENT_CATEGORIES.STORAGE],
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_CDN, "provider.qiniu_cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.QINIU_PILI, "provider.qiniu_pili", DEPLOYMENT_CATEGORIES.AV],
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CDN, "provider.wangsu_cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO, "provider.wangsu_cdnpro", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE, "provider.wangsu_certificate_upload", DEPLOYMENT_CATEGORIES.SSL],
|
||||
|
||||
@@ -1128,16 +1128,6 @@
|
||||
"placeholder": "Please enter QingCloud SecretAccessKey",
|
||||
"tooltip": "For more information, see <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>"
|
||||
},
|
||||
"qiniu_access_key": {
|
||||
"label": "Qiniu AccessKey",
|
||||
"placeholder": "Please enter Qiniu AccessKey",
|
||||
"tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>"
|
||||
},
|
||||
"qiniu_secret_key": {
|
||||
"label": "Qiniu SecretKey",
|
||||
"placeholder": "Please enter Qiniu SecretKey",
|
||||
"tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>"
|
||||
},
|
||||
"rainyun_api_key": {
|
||||
"label": "Rain Yun API key",
|
||||
"placeholder": "Please enter Rain Yun API key",
|
||||
|
||||
@@ -224,10 +224,6 @@
|
||||
"qingcloud": "QingCloud",
|
||||
"qingcloud_dns": "QingCloud - DNS",
|
||||
"qingcloud_lb": "QingCloud - LBC (Load Balancer Cluster)",
|
||||
"qiniu": "Qiniu",
|
||||
"qiniu_cdn": "Qiniu - CDN (Content Delivery Network)",
|
||||
"qiniu_kodo": "Qiniu - Kodo",
|
||||
"qiniu_pili": "Qiniu - Pili",
|
||||
"rainyun": "Rain Yun",
|
||||
"rainyun_rcdn": "Rain Yun - RCDN (Content Delivery Network)",
|
||||
"rainyun_sslcenter_upload": "Rain Yun - Upload to SSL Certificate Center",
|
||||
|
||||
@@ -457,6 +457,17 @@
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"webhook_data": {
|
||||
"label": "Webhook data (Optional)",
|
||||
"placeholder": "Please enter Webhook data",
|
||||
"help": "Notes: Leave it blank to use the default Webhook data provided by the selected credential.",
|
||||
"vartips": "Supported variables: <br><ol style=\"list-style: disc;\"><li><strong>${CERTIMATE_DEPLOYER_COMMONNAME}</strong>: <br>The primary domain or IP address of the certificate.</li><li><strong>${CERTIMATE_DEPLOYER_SUBJECTALTNAMES}</strong>: <br>The domains or IP addresses of the certificate, separated by semicolons.</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE}</strong>: <br>The PEM format content of the certificate file.</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE_SERVER}</strong>: <br>The PEM format content of the server certificate file.</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE_INTERMEDIA}</strong>: <br>The PEM format content of the intermediate CA certificate file.</li><li><strong>${CERTIMATE_DEPLOYER_PRIVATEKEY}</strong>: <br>The PEM format content of the private key file.</li></ol>"
|
||||
},
|
||||
"webhook_timeout": {
|
||||
"label": "Webhook timeout (Optional)",
|
||||
"placeholder": "Please enter Webhook timeout",
|
||||
"unit": "seconds"
|
||||
},
|
||||
"certificate_output_node_id": {
|
||||
"label": "Certificate to deploy",
|
||||
"placeholder": "Please select certificate to deploy",
|
||||
@@ -2049,27 +2060,6 @@
|
||||
"placeholder": "Please enter QingCloud LBC listener ID",
|
||||
"tooltip": "For more information, see <a href=\"https://console.qingcloud.com/pek3a/loadbalancers/\" target=\"_blank\">https://console.qingcloud.com/pek3a/loadbalancers/</a>"
|
||||
},
|
||||
"qiniu_cdn_domain": {
|
||||
"label": "Qiniu CDN domain",
|
||||
"placeholder": "Please enter Qiniu CDN domain name"
|
||||
},
|
||||
"qiniu_kodo_bucket": {
|
||||
"label": "Qiniu Kodo bucket",
|
||||
"placeholder": "Please enter Qiniu Kodo bucket name"
|
||||
},
|
||||
"qiniu_kodo_domain": {
|
||||
"label": "Qiniu Kodo custom domain",
|
||||
"placeholder": "Please enter Qiniu Kodo bucket custom domain name"
|
||||
},
|
||||
"qiniu_pili_hub": {
|
||||
"label": "Qiniu Pili hub",
|
||||
"placeholder": "Please enter Qiniu Pili hub name",
|
||||
"tooltip": "For more information, see <a href=\"https://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>"
|
||||
},
|
||||
"qiniu_pili_domain": {
|
||||
"label": "Qiniu Pili streaming domain",
|
||||
"placeholder": "Please enter Qiniu Pili streaming domain name"
|
||||
},
|
||||
"rainyun_rcdn_instance_id": {
|
||||
"label": "Rain Yun RCDN instance ID",
|
||||
"placeholder": "Please enter Rain Yun RCDN instance ID",
|
||||
|
||||
@@ -1128,16 +1128,6 @@
|
||||
"placeholder": "请输入青云 SecretAccessKey",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>"
|
||||
},
|
||||
"qiniu_access_key": {
|
||||
"label": "七牛云 AccessKey",
|
||||
"placeholder": "请输入七牛云 AccessKey",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>"
|
||||
},
|
||||
"qiniu_secret_key": {
|
||||
"label": "七牛云 SecretKey",
|
||||
"placeholder": "请输入七牛云 SecretKey",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>"
|
||||
},
|
||||
"rainyun_api_key": {
|
||||
"label": "雨云 API 密钥",
|
||||
"placeholder": "请输入雨云 API 密钥",
|
||||
|
||||
@@ -224,10 +224,6 @@
|
||||
"qingcloud": "青云",
|
||||
"qingcloud_dns": "青云 - 云解析 DNS",
|
||||
"qingcloud_lb": "青云 - 负载均衡 LB",
|
||||
"qiniu": "七牛云",
|
||||
"qiniu_cdn": "七牛云 - 内容分发网络 CDN",
|
||||
"qiniu_kodo": "七牛云 - 对象存储 Kodo",
|
||||
"qiniu_pili": "七牛云 - 视频直播 Pili",
|
||||
"rainyun": "雨云",
|
||||
"rainyun_rcdn": "雨云 - 雨盾 CDN",
|
||||
"rainyun_sslcenter_upload": "雨云 - 上传到 SSL 证书中心",
|
||||
|
||||
@@ -456,6 +456,17 @@
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"webhook_data": {
|
||||
"label": "Webhook 回调数据(可选)",
|
||||
"placeholder": "请输入 Webhook 回调数据以覆盖默认值",
|
||||
"help": "提示:不填写时,将使用所选提供商授权的默认 Webhook 回调数据。",
|
||||
"vartips": "支持的变量:<br><ol style=\"list-style: disc;\"><li><strong>${CERTIMATE_DEPLOYER_COMMONNAME}</strong>:<br>证书的主域名或 IP。</li><li><strong>${CERTIMATE_DEPLOYER_SUBJECTALTNAMES}</strong>:<br>证书的多域名或 IP,以半角分号隔开。</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE}</strong>:<br>证书文件 PEM 格式内容。</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE_SERVER}</strong>:<br>证书文件(仅含服务器证书)PEM 格式内容。</li><li><strong>${CERTIMATE_DEPLOYER_CERTIFICATE_INTERMEDIA}</strong>:<br>证书文件(仅含中间证书)PEM 格式内容。</li><li><strong>${CERTIMATE_DEPLOYER_PRIVATEKEY}</strong>:<br>私钥文件 PEM 格式内容。</li></ol>"
|
||||
},
|
||||
"webhook_timeout": {
|
||||
"label": "Webhook 超时时间(可选)",
|
||||
"placeholder": "请输入 Webhook 超时时间",
|
||||
"unit": "秒"
|
||||
},
|
||||
"certificate_output_node_id": {
|
||||
"label": "待部署证书",
|
||||
"placeholder": "请选择待部署证书",
|
||||
@@ -2048,27 +2059,6 @@
|
||||
"placeholder": "请输入青云 LB 监听器 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://console.qingcloud.com/pek3a/loadbalancers/\" target=\"_blank\">https://console.qingcloud.com/pek3a/loadbalancers/</a>"
|
||||
},
|
||||
"qiniu_cdn_domain": {
|
||||
"label": "七牛云 CDN 加速域名",
|
||||
"placeholder": "请输入七牛云 CDN 加速域名"
|
||||
},
|
||||
"qiniu_kodo_bucket": {
|
||||
"label": "七牛云对象存储桶名",
|
||||
"placeholder": "请输入七牛云对象存储桶名"
|
||||
},
|
||||
"qiniu_kodo_domain": {
|
||||
"label": "七牛云对象存储自定义域名",
|
||||
"placeholder": "请输入七牛云对象存储自定义域名"
|
||||
},
|
||||
"qiniu_pili_hub": {
|
||||
"label": "七牛云视频直播空间名",
|
||||
"placeholder": "请输入七牛云视频直播空间名",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>"
|
||||
},
|
||||
"qiniu_pili_domain": {
|
||||
"label": "七牛云视频直播流域名",
|
||||
"placeholder": "请输入七牛云视频直播流域名"
|
||||
},
|
||||
"rainyun_rcdn_instance_id": {
|
||||
"label": "雨云 RCDN 实例 ID",
|
||||
"placeholder": "请输入雨云 RCDN 实例 ID",
|
||||
|
||||
Reference in New Issue
Block a user