mirror of
https://github.com/certimate-go/certimate.git
synced 2026-10-16 15:16:05 +08:00
Merge branch 'main' into 'next'
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
name: "Issue Manager"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Daily at 01:30 UTC (09:30 Beijing time)
|
||||
- cron: "30 1 * * *"
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
issue-manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Manage issues with the "answered" label
|
||||
uses: tiangolo/issue-manager@0.8.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
{
|
||||
"answered": {
|
||||
"delay": 604800,
|
||||
"message": "This issue has been automatically closed because there has been no further reply from you for over 7 days since our last response. If your problem is not resolved, please feel free to reopen this issue or open a new one with more details.\n\n---\n\n此 Issue 已被自动关闭。在我们最后一次回复后已超过 7 天未收到您的进一步回复。如果您的问题仍未解决,欢迎重新打开此 Issue 或创建新的 Issue 并提供更多详细信息。"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ An open-source and free self-hosted SSL certificates ACME tool, automates the fu
|
||||
- **Self-hosted**: Private deployment. All data is stored locally, to ensure data privacy and security.
|
||||
- **Cross Platforms**: Compatible with various operating systems, including Windows/Linux/macOS.
|
||||
- **Zero Dependencies**: No need to install databases, runtimes, or any frameworks. Ready to use out of the box.
|
||||
- **Low Resource Usage**: Extremely lightweight, requiring only ~16 MB of memory.
|
||||
- **Low Resource Usage**: Extremely lightweight, requiring only ~20 MB of memory.
|
||||
- **Easy to Use**: Automates certificate management with a visual workflow — all with just a few simple configurations.
|
||||
|
||||
## 💡 Features
|
||||
@@ -50,7 +50,7 @@ An open-source and free self-hosted SSL certificates ACME tool, automates the fu
|
||||
- Supports DNS-01 challenge and HTTP-01 challenge both.
|
||||
- Supports various certificate formats such as PEM, PFX, JKS.
|
||||
- Supports more than 70+ domain registrars (e.g., AWS, Cloudflare, GoDaddy, Alibaba Cloud, Tencent Cloud, etc. [Check out full providers](https://docs.certimate.me/en-US/docs/reference/providers#supported-dns-providers)).
|
||||
- Supports more than 150+ deployment destinations (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out full providers](https://docs.certimate.me/en-US/docs/reference/providers#supported-hosting-providers)).
|
||||
- Supports more than 160+ deployment destinations (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out full providers](https://docs.certimate.me/en-US/docs/reference/providers#supported-hosting-providers)).
|
||||
- Supports multiple notification channels including email, Discord, Slack, Telegram, DingTalk, Feishu, WeCom, and more.
|
||||
- Supports multiple ACME CAs including Let's Encrypt, Actalis, Google Trust Services, SSL.com, ZeroSSL, and more.
|
||||
- More features waiting to be discovered.
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
<a href="https://trendshift.io/repositories/23513?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-23513" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/23513/daily" alt="certimate-go%2Fcertimate | Trendshift" width="250" height="55"/></a>
|
||||
<a href="https://trendshift.io/repositories/23513?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-23513" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/23513/weekly?language=Go" alt="certimate-go%2Fcertimate | Trendshift" width="250" height="55"/></a>
|
||||
<a href="https://trendshift.io/repositories/23513?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-23513" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/23513/monthly?language=Go" alt="certimate-go%2Fcertimate | Trendshift" width="250" height="55"/></a>
|
||||
@@ -40,7 +40,7 @@
|
||||
- **自托管**:私有化安装,所有数据本地化存储,掌控数据的隐私与安全。
|
||||
- **跨平台**:Windows/Linux/macOS,全平台多架构可运行。
|
||||
- **零依赖**:无需安装数据库、运行时或复杂框架,一键启动,开箱即用。
|
||||
- **低占用**:超轻量的资源开销,仅需 ~16 MB 内存。
|
||||
- **低占用**:超轻量的资源开销,仅需 ~20 MB 常驻内存。
|
||||
- **易操作**:图形化界面,通过简单配置即可完成证书的自动化管理。
|
||||
|
||||
## 💡 功能特性
|
||||
@@ -50,7 +50,7 @@
|
||||
- 支持 DNS-01(即基于域名解析验证)、HTTP-01(即基于文件验证)两种质询方式。
|
||||
- 支持 PEM、PFX、JKS 等多种格式输出证书。
|
||||
- 支持 70+ 域名托管商(如阿里云、腾讯云、AWS、Cloudflare、GoDaddy 等,[点此查看完整清单](https://docs.certimate.me/zh-CN/docs/reference/providers#supported-dns-providers))。
|
||||
- 支持 150+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/zh-CN/docs/reference/providers#supported-hosting-providers))。
|
||||
- 支持 160+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/zh-CN/docs/reference/providers#supported-hosting-providers))。
|
||||
- 支持邮件、钉钉、飞书、企业微信、Discord、Slack、Telegram 等多种通知渠道。
|
||||
- 支持 Let's Encrypt、Actalis、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构。
|
||||
- 更多特性等待探索。
|
||||
|
||||
@@ -66,6 +66,7 @@ require (
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/samber/lo v1.53.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.3.131
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.3.116
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.3.142
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.145
|
||||
|
||||
@@ -857,6 +857,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.3.131 h1:rfaTxEAxIaVWHLIu9IzvBUKTW1PHBU3GWgYwj63pqFc=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.3.131/go.mod h1:cLvVHLUt/aoJ3TLNQlNDFj2E1XWTcC+7ck7Lt+nA8pA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.3.116 h1:pnWXFgtIgCIN+1OB5a/8+FX5/Yydg78DYQGooDmL32k=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.3.116/go.mod h1:/DjlZsmhK9WYAV4ByLrEdgzTuQLlOZISvA8a7wnsEh8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.3.142 h1:3t++yffGVnGbLMg/ixQFYGBASOGXKTUHV+8kqSrywew=
|
||||
@@ -867,6 +869,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.38/go.mod h
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.101/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.105/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.116/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.131/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.133/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.140/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.142/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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/asiaispcdn"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeAsiaISPCDN, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForAsiaISPCDN{}
|
||||
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{
|
||||
AccessKeyId: credentials.AccessKeyId,
|
||||
AccessKeySecret: credentials.AccessKeySecret,
|
||||
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -20,8 +20,10 @@ func init() {
|
||||
ServerUrl: credentials.ServerUrl,
|
||||
ApiVersion: credentials.ApiVersion,
|
||||
ApiRole: credentials.ApiRole,
|
||||
AuthMethod: credentials.AuthMethod,
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
ApiKey: credentials.ApiKey,
|
||||
AllowInsecureConnections: credentials.AllowInsecureConnections,
|
||||
DeployTarget: xmaps.GetString(options.ProviderExtendedConfig, "deployTarget"),
|
||||
CertificateId: xmaps.GetInt64(options.ProviderExtendedConfig, "certificateId"),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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/tencentcloud-ddospro"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Registries.MustRegister(domain.DeploymentProviderTypeTencentCloudDDoSPro, func(options *ProviderFactoryOptions) (core.Deployer, error) {
|
||||
credentials := domain.AccessConfigForTencentCloud{}
|
||||
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{
|
||||
SecretId: credentials.SecretId,
|
||||
SecretKey: credentials.SecretKey,
|
||||
ProjectId: credentials.ProjectId,
|
||||
Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"),
|
||||
InstanceId: xmaps.GetString(options.ProviderExtendedConfig, "instanceId"),
|
||||
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -24,6 +24,7 @@ func init() {
|
||||
DeployTarget: xmaps.GetString(options.ProviderExtendedConfig, "deployTarget"),
|
||||
LoadbalancerId: xmaps.GetString(options.ProviderExtendedConfig, "loadbalancerId"),
|
||||
ListenerId: xmaps.GetString(options.ProviderExtendedConfig, "listenerId"),
|
||||
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
|
||||
@@ -81,6 +81,11 @@ type AccessConfigForArvanCloud struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
type AccessConfigForAsiaISPCDN struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type AccessConfigForAWS struct {
|
||||
AuthMethod string `json:"authMethod"`
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
@@ -379,10 +384,12 @@ type AccessConfigForLarkBot struct {
|
||||
|
||||
type AccessConfigForLeCDN struct {
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
AuthMethod string `json:"authMethod"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
ApiRole string `json:"apiRole"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
ApiKey string `json:"apiKey,omitempty"`
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
AccessProviderTypeAliyun = AccessProviderType("aliyun")
|
||||
AccessProviderTypeAPISIX = AccessProviderType("apisix")
|
||||
AccessProviderTypeArvanCloud = AccessProviderType("arvancloud")
|
||||
AccessProviderTypeAsiaISPCDN = AccessProviderType("asiaispcdn")
|
||||
AccessProviderTypeAWS = AccessProviderType("aws")
|
||||
AccessProviderTypeAxisNow = AccessProviderType("axisnow")
|
||||
AccessProviderTypeAzure = AccessProviderType("azure")
|
||||
@@ -329,6 +330,7 @@ const (
|
||||
DeploymentProviderTypeAliyunVOD = DeploymentProviderType(AccessProviderTypeAliyun + "-vod")
|
||||
DeploymentProviderTypeAliyunWAF = DeploymentProviderType(AccessProviderTypeAliyun + "-waf")
|
||||
DeploymentProviderTypeAPISIX = DeploymentProviderType(AccessProviderTypeAPISIX)
|
||||
DeploymentProviderTypeAsiaISPCDN = DeploymentProviderType(AccessProviderTypeAsiaISPCDN)
|
||||
DeploymentProviderTypeAWSACM = DeploymentProviderType(AccessProviderTypeAWS + "-acm")
|
||||
DeploymentProviderTypeAWSALB = DeploymentProviderType(AccessProviderTypeAWS + "-alb")
|
||||
DeploymentProviderTypeAWSAmplify = DeploymentProviderType(AccessProviderTypeAWS + "-amplify")
|
||||
@@ -419,6 +421,7 @@ const (
|
||||
DeploymentProviderTypeTencentCloudCLB = DeploymentProviderType(AccessProviderTypeTencentCloud + "-clb")
|
||||
DeploymentProviderTypeTencentCloudCOS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cos")
|
||||
DeploymentProviderTypeTencentCloudCSS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-css")
|
||||
DeploymentProviderTypeTencentCloudDDoSPro = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ddospro")
|
||||
DeploymentProviderTypeTencentCloudECDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ecdn")
|
||||
DeploymentProviderTypeTencentCloudEO = DeploymentProviderType(AccessProviderTypeTencentCloud + "-eo")
|
||||
DeploymentProviderTypeTencentCloudEOMakers = DeploymentProviderType(AccessProviderTypeTencentCloud + "-eomakers")
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
tracer := NewTracer("v0.4.33")
|
||||
tracer.Printf("go ...")
|
||||
|
||||
// update collection `access`
|
||||
// - modify field `config` schema
|
||||
{
|
||||
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err := app.FindAllRecords(collection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
changed := false
|
||||
|
||||
provider := record.GetString("provider")
|
||||
config := make(map[string]any)
|
||||
if err := record.UnmarshalJSONField("config", &config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case "lecdn":
|
||||
{
|
||||
if _, ok := config["authMethod"]; !ok {
|
||||
config["authMethod"] = "password"
|
||||
record.Set("config", config)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := app.Save(record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracer.Printf("done")
|
||||
return nil
|
||||
}, func(app core.App) error {
|
||||
return errors.ErrUnsupported
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//go:build tester
|
||||
|
||||
package asiaispcdn_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tester "github.com/certimate-go/certimate/pkg/core/certmgr/providers-tester"
|
||||
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/asiaispcdn"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.InitArgs("ASIAISPCDN_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
|
||||
fp.DefineString(&fAccessKeySecret, "ACCESSKEYSECRET")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -tags=tester -v ./asiaispcdn_test.go -args \
|
||||
--ASIAISPCDN_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--ASIAISPCDN_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--ASIAISPCDN_ACCESSKEYID="your-access-key-id" \
|
||||
--ASIAISPCDN_ACCESSKEYSECRET="your-access-key-secret"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tester.Upload(t, provider, tester.UploadInput{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -286,33 +286,41 @@ const (
|
||||
)
|
||||
|
||||
func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool, nodeName string) (any, error) {
|
||||
if apiVersion == sdkVersionV1 {
|
||||
client, err := onepanelsdk.NewClient(serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch apiVersion {
|
||||
case sdkVersionV1:
|
||||
{
|
||||
client, err := onepanelsdk.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
case sdkVersionV2:
|
||||
{
|
||||
client, err := onepanelsdk2.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
onepanelsdk2.WithNode(nodeName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
} else if apiVersion == sdkVersionV2 {
|
||||
client, err := onepanelsdk2.NewClient(serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
onepanelsdk2.WithNode(nodeName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("1panel: invalid api version")
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
asiaispcdnsdk "github.com/certimate-go/certimate/pkg/sdk3rd/asiaispcdn"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Certmgr
|
||||
UploadResult = core.CertmgrUploadResult
|
||||
ReplaceResult = core.CertmgrReplaceResult
|
||||
)
|
||||
|
||||
type CertmgrConfig struct {
|
||||
// 橙域网络 CDN AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 橙域网络 CDN AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type Certmgr struct {
|
||||
config *CertmgrConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *asiaispcdnsdk.Client
|
||||
}
|
||||
|
||||
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.AccessKeyId, config.AccessKeySecret)
|
||||
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) {
|
||||
// 避免重复上传
|
||||
if upres, upok, err := c.tryGetResultIfCertExists(ctx, certPEM, privkeyPEM); err != nil {
|
||||
return nil, err
|
||||
} else if upok {
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return upres, nil
|
||||
}
|
||||
|
||||
// 生成新证书名(需符合橙域网络 CDN 命名规则)
|
||||
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
|
||||
|
||||
// 上传证书
|
||||
certificateUploadReq := &asiaispcdnsdk.CertificateUploadRequest{
|
||||
PublicKey: lo.ToPtr(certPEM),
|
||||
PrivateKey: lo.ToPtr(privkeyPEM),
|
||||
Name: lo.ToPtr(certName),
|
||||
}
|
||||
certificateUploadResp, err := c.sdkClient.CertificateUploadWithContext(ctx, certificateUploadReq)
|
||||
c.logger.Debug("sdk request 'certificateUpload'", slog.Any("request", certificateUploadReq), slog.Any("response", certificateUploadResp))
|
||||
if err != nil {
|
||||
if certificateUploadResp != nil && certificateUploadResp.GetCode() == 80003 {
|
||||
if upres, upok, err := c.tryGetResultIfCertExists(ctx, certPEM, privkeyPEM); err != nil {
|
||||
return nil, err
|
||||
} else if !upok {
|
||||
return nil, fmt.Errorf("could not find ssl certificate, may be upload failed")
|
||||
} else {
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return upres, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'certificateUpload': %w", err)
|
||||
}
|
||||
|
||||
certId, err := certificateUploadResp.Data.Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("received invalid certificate id: %w", err)
|
||||
}
|
||||
|
||||
return &UploadResult{
|
||||
CertId: fmt.Sprintf("%d", certId),
|
||||
CertName: certName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
|
||||
return nil, core.ErrUnsupported
|
||||
}
|
||||
|
||||
func (c *Certmgr) tryGetResultIfCertExists(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, bool, error) {
|
||||
// 查询证书列表
|
||||
certificateQueryListResp, err := c.sdkClient.CertificateQueryListWithContext(ctx)
|
||||
c.logger.Debug("sdk request 'certificateQueryList'", slog.Any("response", certificateQueryListResp))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to execute sdk request 'certificateQueryList': %w", err)
|
||||
} else {
|
||||
for _, certItem := range certificateQueryListResp.Data {
|
||||
certificateQueryResp, err := c.sdkClient.CertificateQueryWithContext(ctx, certItem.CertId)
|
||||
c.logger.Debug("sdk request 'certificateQuery'", slog.String("params.certId", certItem.CertId), slog.Any("response", certificateQueryResp))
|
||||
if err != nil {
|
||||
if certificateQueryResp != nil && certificateQueryResp.GetCode() == 80012 {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("failed to execute sdk request 'certificateQuery': %w", err)
|
||||
}
|
||||
|
||||
if certificateQueryResp.Data.PublicKey == certPEM && certificateQueryResp.Data.PrivateKey == privkeyPEM {
|
||||
// 如果已存在相同证书,直接返回
|
||||
return &UploadResult{
|
||||
CertId: certItem.CertId,
|
||||
CertName: certItem.Name,
|
||||
}, true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, accessKeySecret string) (*asiaispcdnsdk.Client, error) {
|
||||
client, err := asiaispcdnsdk.NewClient(
|
||||
asiaispcdnsdk.WithAkSk(accessKeyId, accessKeySecret),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -163,18 +163,11 @@ func createSDKClient(serverUrl, authMethod, username, password, apiToken string,
|
||||
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
{
|
||||
client, err = npmsdk.NewClient(serverUrl,
|
||||
npmsdk.WithLogins(username, password),
|
||||
)
|
||||
}
|
||||
|
||||
client, err = npmsdk.NewClient(serverUrl, npmsdk.WithLogins(username, password))
|
||||
case AUTH_METHOD_TOKEN:
|
||||
{
|
||||
client, err = npmsdk.NewClient(serverUrl,
|
||||
npmsdk.WithJwtToken(apiToken),
|
||||
)
|
||||
}
|
||||
client, err = npmsdk.NewClient(serverUrl, npmsdk.WithJwtToken(apiToken))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//go:build tester
|
||||
|
||||
package asiaispcdn_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/providers-tester"
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/asiaispcdn"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.InitArgs("ASIAISPCDN_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
|
||||
fp.DefineString(&fAccessKeySecret, "ACCESSKEYSECRET")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -tags=tester -v ./asiaispcdn_test.go -args \
|
||||
--ASIAISPCDN_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--ASIAISPCDN_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--ASIAISPCDN_ACCESSKEYID="your-access-key-id" \
|
||||
--ASIAISPCDN_ACCESSKEYSECRET="your-access-key-secret" \
|
||||
--ASIAISPCDN_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
DomainMatchPattern: impl.DOMAIN_MATCH_PATTERN_EXACT,
|
||||
Domain: fDomain,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tester.Deploy(t, provider, tester.DeployInput{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fServerUrl, "SERVERURL")
|
||||
fp.DefineString(&fApiVersion, "APIVERSION", "v3")
|
||||
fp.DefineString(&fApiVersion, "APIVERSION", "v4")
|
||||
fp.DefineString(&fUsername, "USERNAME")
|
||||
fp.DefineString(&fPassword, "PASSWORD")
|
||||
fp.DefineInt64(&fCertificateId, "CERTIFICATEID")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//go:build tester
|
||||
|
||||
package tencentcloudddospro_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tester "github.com/certimate-go/certimate/pkg/core/deployer/providers-tester"
|
||||
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/tencentcloud-ddospro"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.InitArgs("TENCENTCLOUDDDOSPRO_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fSecretId string
|
||||
fSecretKey string
|
||||
fInstanceId string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fSecretId, "SECRETID")
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
fp.DefineString(&fInstanceId, "INSTANCEID")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -tags=tester -v ./tencentcloud_ddospro_test.go -args \
|
||||
--TENCENTCLOUDDDOSPRO_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--TENCENTCLOUDDDOSPRO_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--TENCENTCLOUDDDOSPRO_SECRETID="your-secret-id" \
|
||||
--TENCENTCLOUDDDOSPRO_SECRETKEY="your-secret-key" \
|
||||
--TENCENTCLOUDDDOSPRO_INSTANCEID="your-instance-id" \
|
||||
--TENCENTCLOUDDDOSPRO_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
SecretId: fSecretId,
|
||||
SecretKey: fSecretKey,
|
||||
InstanceId: fInstanceId,
|
||||
DomainMatchPattern: impl.DOMAIN_MATCH_PATTERN_EXACT,
|
||||
Domain: fDomain,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tester.Deploy(t, provider, tester.DeployInput{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -19,6 +19,7 @@ var (
|
||||
fSecretAccessKey string
|
||||
fRegion string
|
||||
fListenerId string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -28,6 +29,7 @@ func init() {
|
||||
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
|
||||
fp.DefineString(&fRegion, "REGION")
|
||||
fp.DefineString(&fListenerId, "LISTENERID")
|
||||
fp.DefineString(&fDomain, "DOMAIN")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -39,7 +41,8 @@ Shell command to run this test:
|
||||
--VOLCENGINECLB_ACCESSKEYID="your-access-key-id" \
|
||||
--VOLCENGINECLB_SECRETACCESSKEY="your-secret-access-key" \
|
||||
--VOLCENGINECLB_REGION="cn-beijing" \
|
||||
--VOLCENGINECLB_LISTENERID="your-listener-id"
|
||||
--VOLCENGINECLB_LISTENERID="your-listener-id" \
|
||||
--VOLCENGINECLB_DOMAIN="example.com"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
@@ -51,6 +54,7 @@ func TestProvider(t *testing.T) {
|
||||
Region: fRegion,
|
||||
DeployTarget: impl.DEPLOY_TARGET_LISTENER,
|
||||
ListenerId: fListenerId,
|
||||
Domain: fDomain,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -112,32 +112,40 @@ const (
|
||||
)
|
||||
|
||||
func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) (any, error) {
|
||||
if apiVersion == sdkVersionV1 {
|
||||
client, err := onepanelsdk.NewClient(serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch apiVersion {
|
||||
case sdkVersionV1:
|
||||
{
|
||||
client, err := onepanelsdk.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
case sdkVersionV2:
|
||||
{
|
||||
client, err := onepanelsdk2.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
} else if apiVersion == sdkVersionV2 {
|
||||
client, err := onepanelsdk2.NewClient(serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("1panel: invalid api version")
|
||||
|
||||
@@ -391,33 +391,41 @@ const (
|
||||
)
|
||||
|
||||
func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool, nodeName string) (any, error) {
|
||||
if apiVersion == sdkVersionV1 {
|
||||
client, err := onepanelsdk.NewClient(serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch apiVersion {
|
||||
case sdkVersionV1:
|
||||
{
|
||||
client, err := onepanelsdk.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk.WithApiKey(apiKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
case sdkVersionV2:
|
||||
{
|
||||
client, err := onepanelsdk2.NewClient(
|
||||
serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
onepanelsdk2.WithNode(nodeName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
} else if apiVersion == sdkVersionV2 {
|
||||
client, err := onepanelsdk2.NewClient(serverUrl,
|
||||
onepanelsdk2.WithApiKey(apiKey),
|
||||
onepanelsdk2.WithNode(nodeName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("1panel: invalid api version")
|
||||
|
||||
@@ -225,6 +225,7 @@ func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudLoadbalan
|
||||
d.logger.Info("no need to deploy clb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateListenerDefaultCertificate(ctx, cloudLoadbalancerId, cloudListenerPort, cloudCertId)
|
||||
} else {
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
|
||||
@@ -240,6 +240,7 @@ func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudAccelerat
|
||||
d.logger.Info("no need to deploy ga listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateListenerDefaultCertificate(ctx, cloudListenerId, cloudCertId)
|
||||
} else {
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/asiaispcdn"
|
||||
asiaispcdnsdk "github.com/certimate-go/certimate/pkg/sdk3rd/asiaispcdn"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
|
||||
xwait "github.com/certimate-go/certimate/pkg/utils/wait"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 橙域网络 CDN AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 橙域网络 CDN AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
// 域名匹配模式。
|
||||
// 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。
|
||||
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *asiaispcdnsdk.Client
|
||||
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, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKeyId: config.AccessKeyId,
|
||||
AccessKeySecret: config.AccessKeySecret,
|
||||
})
|
||||
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:
|
||||
{
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
domains = []string{d.config.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 {
|
||||
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
|
||||
return d.updateDomainCertificate(ctx, domain, certId)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) {
|
||||
domains := make([]string, 0)
|
||||
|
||||
// 查询域名信息列表
|
||||
domainQueryListReq := &asiaispcdnsdk.DomainQueryListRequest{}
|
||||
domainQueryListResp, err := d.sdkClient.DomainQueryListWithContext(ctx, domainQueryListReq)
|
||||
d.logger.Debug("sdk request 'domainQueryList'", slog.Any("request", domainQueryListReq), slog.Any("response", domainQueryListResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'domainQueryList': %w", err)
|
||||
}
|
||||
|
||||
for _, domainItem := range domainQueryListResp.Data {
|
||||
if domainItem.DomainStatus == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
domains = append(domains, domainItem.Domain)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, domain string, cloudCertId int64) error {
|
||||
// 查询域名配置
|
||||
domainQueryListReq := &asiaispcdnsdk.DomainQueryListRequest{
|
||||
Domain: lo.ToPtr(domain),
|
||||
}
|
||||
domainQueryListResp, err := d.sdkClient.DomainQueryListWithContext(ctx, domainQueryListReq)
|
||||
d.logger.Debug("sdk request 'domainQueryList'", slog.Any("request", domainQueryListReq), slog.Any("response", domainQueryListResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'domainQueryList': %w", err)
|
||||
} else {
|
||||
|
||||
domainEntry, _ := lo.Find(domainQueryListResp.Data, func(domainInfo *asiaispcdnsdk.Domain) bool {
|
||||
return domainInfo.Domain == domain
|
||||
})
|
||||
if domainEntry == nil {
|
||||
return fmt.Errorf("could not find domain '%s'", domain)
|
||||
}
|
||||
|
||||
if domainEntry.CertId == cloudCertId {
|
||||
// 已部署过,直接返回
|
||||
return nil
|
||||
}
|
||||
|
||||
if domainEntry.DomainStatus == 2 {
|
||||
if err := d.waitForDomainReady(ctx, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改域名配置
|
||||
domainModifyReq := &asiaispcdnsdk.DomainModifyRequest{
|
||||
Domain: lo.ToPtr(domain),
|
||||
Protocol: lo.ToPtr("https"),
|
||||
CertId: lo.ToPtr(cloudCertId),
|
||||
}
|
||||
domainModifyResp, err := d.sdkClient.DomainModifyWithContext(ctx, domainModifyReq)
|
||||
d.logger.Debug("sdk request 'domainModify'", slog.Any("request", domainModifyReq), slog.Any("response", domainModifyResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'domainModify': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Deployer) waitForDomainReady(ctx context.Context, domain string) error {
|
||||
// 查询域名信息,直到操作状态不再为 "2"
|
||||
if _, err := xwait.UntilWithContext(ctx, func(_ context.Context, _ int) (bool, error) {
|
||||
domainQueryListReq := &asiaispcdnsdk.DomainQueryListRequest{
|
||||
Domain: lo.ToPtr(domain),
|
||||
}
|
||||
domainQueryListResp, err := d.sdkClient.DomainQueryListWithContext(ctx, domainQueryListReq)
|
||||
d.logger.Debug("sdk request 'domainQueryList'", slog.Any("request", domainQueryListReq), slog.Any("response", domainQueryListResp))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to execute sdk request 'domainQueryList': %w", err)
|
||||
}
|
||||
|
||||
domainEntry, _ := lo.Find(domainQueryListResp.Data, func(domainInfo *asiaispcdnsdk.Domain) bool {
|
||||
return domainInfo.Domain == domain
|
||||
})
|
||||
if domainEntry == nil {
|
||||
return false, fmt.Errorf("could not find domain '%s'", domain)
|
||||
}
|
||||
|
||||
if domainEntry.DomainStatus != 2 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
d.logger.Info("waiting for domain's status to not be 'Operating' ...")
|
||||
return false, nil
|
||||
}, 10*time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, accessKeySecret string) (*asiaispcdnsdk.Client, error) {
|
||||
client, err := asiaispcdnsdk.NewClient(
|
||||
asiaispcdnsdk.WithAkSk(accessKeyId, accessKeySecret),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package asiaispcdn
|
||||
|
||||
const (
|
||||
// 匹配模式:精确匹配。
|
||||
DOMAIN_MATCH_PATTERN_EXACT = "exact"
|
||||
// 匹配模式:通配符匹配。
|
||||
DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard"
|
||||
// 匹配模式:证书 SAN 匹配。
|
||||
DOMAIN_MATCH_PATTERN_CERTSAN = "certsan"
|
||||
)
|
||||
@@ -240,6 +240,7 @@ func (d *Deployer) updateHttpsListenerCertificate(ctx context.Context, cloudLoad
|
||||
d.logger.Info("no need to deploy appblb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateHttpsListenerDefaultCertificate(ctx, cloudLoadbalancerId, &listenerInfo, cloudCertId)
|
||||
} else {
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
|
||||
@@ -240,6 +240,7 @@ func (d *Deployer) updateHttpsListenerCertificate(ctx context.Context, cloudLoad
|
||||
d.logger.Info("no need to deploy blb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateHttpsListenerDefaultCertificate(ctx, cloudLoadbalancerId, &listenerInfo, cloudCertId)
|
||||
} else {
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
|
||||
@@ -213,6 +213,7 @@ func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudListenerI
|
||||
d.logger.Info("no need to deploy alb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateListenerDefaultCertificate(ctx, *describeListenerAttributesResp, cloudCertId)
|
||||
} else {
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
@@ -252,7 +253,8 @@ func (d *Deployer) updateListenerSniCertificate(ctx context.Context, cloudListen
|
||||
CertCenterCertificateId: bp.String(cloudCertId),
|
||||
Action: bp.String("modify"),
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
}
|
||||
modifyListenerAttributesResp, err := d.sdkClient.ModifyListenerAttributesWithContext(ctx, modifyListenerAttributesReq)
|
||||
d.logger.Debug("sdk request 'alb.ModifyListenerAttributes'", slog.Any("request", modifyListenerAttributesReq), slog.Any("response", modifyListenerAttributesResp))
|
||||
|
||||
@@ -112,6 +112,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
}
|
||||
|
||||
domains = domainCandidates
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
} else {
|
||||
domains = []string{d.config.Domain}
|
||||
}
|
||||
@@ -125,6 +128,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
}
|
||||
|
||||
domains = domainCandidates
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -216,12 +222,6 @@ func (d *Deployer) getMatchedDomainsByCertId(ctx context.Context, cloudCertId st
|
||||
}
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
if len(describeCertConfigResp.Result.SpecifiedCertConfig) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package lecdn
|
||||
|
||||
const (
|
||||
AUTH_METHOD_PASSWORD = "password"
|
||||
AUTH_METHOD_APIKEY = "apikey"
|
||||
)
|
||||
|
||||
const (
|
||||
// 部署目标:替换指定证书。
|
||||
DEPLOY_TARGET_CERTIFICATE = "certificate"
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
lecdnclientv3 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/v3/client"
|
||||
lecdnmasterv3 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/v3/master"
|
||||
lecdnclientsdkv3 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/client/v3"
|
||||
lecdnclientsdkv4 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/client/v4"
|
||||
lecdnmastersdkv3 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/master/v3"
|
||||
lecdnmastersdkv4 "github.com/certimate-go/certimate/pkg/sdk3rd/lecdn/master/v4"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -21,15 +23,21 @@ type DeployerConfig struct {
|
||||
// LeCDN 服务地址。
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
// LeCDN 版本。
|
||||
// 可取值 "v3"。
|
||||
// 可取值 "v3"、"v4"。
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
// LeCDN 用户角色。
|
||||
// 可取值 "client"、"master"。
|
||||
ApiRole string `json:"apiRole"`
|
||||
// LeCDN API 认证方式。
|
||||
// 可取值 "password"、"apikey"。
|
||||
// 零值时默认值 [AUTH_METHOD_PASSWORD]。
|
||||
AuthMethod string `json:"authMethod,omitempty"`
|
||||
// LeCDN 用户名。
|
||||
Username string `json:"username"`
|
||||
Username string `json:"username,omitempty"`
|
||||
// LeCDN 用户密码。
|
||||
Password string `json:"password"`
|
||||
Password string `json:"password,omitempty"`
|
||||
// LeCDN API Key。
|
||||
ApiKey string `json:"apiKey,omitempty"`
|
||||
// 是否允许不安全的连接。
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
// 部署目标。
|
||||
@@ -55,7 +63,7 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.ServerUrl, config.ApiVersion, config.ApiRole, config.Username, config.Password, config.AllowInsecureConnections)
|
||||
client, err := createSDKClient(config.ServerUrl, config.ApiVersion, config.ApiRole, config.AuthMethod, config.Username, config.Password, config.ApiKey, config.AllowInsecureConnections)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
@@ -95,14 +103,18 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
return fmt.Errorf("config `certificateId` is required")
|
||||
}
|
||||
|
||||
// 生成新证书名(需符合 LeCDN 命名规则)
|
||||
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||
certDesc := "upload from Certimate"
|
||||
|
||||
// 修改证书
|
||||
// REF: https://wdk0pwf8ul.feishu.cn/wiki/YE1XwCRIHiLYeKkPupgcXrlgnDd
|
||||
switch sdkClient := d.sdkClient.(type) {
|
||||
case *lecdnclientv3.Client:
|
||||
case *lecdnclientsdkv3.Client:
|
||||
{
|
||||
updateSSLCertReq := &lecdnclientv3.UpdateCertificateRequest{
|
||||
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
||||
Description: "upload from Certimate",
|
||||
updateSSLCertReq := &lecdnclientsdkv3.UpdateCertificateRequest{
|
||||
Name: certName,
|
||||
Description: certDesc,
|
||||
Type: "upload",
|
||||
SSLPEM: certPEM,
|
||||
SSLKey: privkeyPEM,
|
||||
@@ -115,12 +127,12 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
}
|
||||
}
|
||||
|
||||
case *lecdnmasterv3.Client:
|
||||
case *lecdnmastersdkv3.Client:
|
||||
{
|
||||
updateSSLCertReq := &lecdnmasterv3.UpdateCertificateRequest{
|
||||
updateSSLCertReq := &lecdnmastersdkv3.UpdateCertificateRequest{
|
||||
ClientId: d.config.ClientId,
|
||||
Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
||||
Description: "upload from Certimate",
|
||||
Name: certName,
|
||||
Description: certDesc,
|
||||
Type: "upload",
|
||||
SSLPEM: certPEM,
|
||||
SSLKey: privkeyPEM,
|
||||
@@ -133,6 +145,38 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
}
|
||||
}
|
||||
|
||||
case *lecdnclientsdkv4.Client:
|
||||
{
|
||||
updateSSLCertReq := &lecdnclientsdkv4.UpdateCertificateRequest{
|
||||
Name: certName,
|
||||
Description: certDesc,
|
||||
SSLPEM: certPEM,
|
||||
SSLKey: privkeyPEM,
|
||||
AutoRenewal: false,
|
||||
}
|
||||
updateSSLCertResp, err := sdkClient.UpdateCertificateWithContext(ctx, d.config.CertificateId, updateSSLCertReq)
|
||||
d.logger.Debug("sdk request 'UpdateCertificate'", slog.Int64("params.certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'UpdateCertificate': %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case *lecdnmastersdkv4.Client:
|
||||
{
|
||||
updateSSLCertReq := &lecdnmastersdkv4.UpdateCertificateRequest{
|
||||
Name: certName,
|
||||
Description: certDesc,
|
||||
SSLPEM: certPEM,
|
||||
SSLKey: privkeyPEM,
|
||||
AutoRenewal: false,
|
||||
}
|
||||
updateSSLCertResp, err := sdkClient.UpdateCertificateWithContext(ctx, d.config.CertificateId, updateSSLCertReq)
|
||||
d.logger.Debug("sdk request 'UpdateCertificate'", slog.Int64("params.certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'UpdateCertificate': %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -142,40 +186,127 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
|
||||
const (
|
||||
sdkVersionV3 = "v3"
|
||||
sdkVersionV4 = "v4"
|
||||
|
||||
sdkRoleClient = "client"
|
||||
sdkRoleMaster = "master"
|
||||
)
|
||||
|
||||
func createSDKClient(serverUrl, apiVersion, apiRole, username, password string, skipTlsVerify bool) (any, error) {
|
||||
if apiVersion == sdkVersionV3 && apiRole == sdkRoleClient {
|
||||
// v3 版客户端
|
||||
client, err := lecdnclientv3.NewClient(serverUrl,
|
||||
lecdnclientv3.WithLogins(username, password),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func createSDKClient(serverUrl, apiVersion, apiRole, authMethod, username, password, apiKey string, skipTlsVerify bool) (any, error) {
|
||||
switch apiVersion {
|
||||
case sdkVersionV3:
|
||||
{
|
||||
switch apiRole {
|
||||
case sdkRoleClient:
|
||||
{
|
||||
// v3 版客户端
|
||||
var client *lecdnclientsdkv3.Client
|
||||
var err error
|
||||
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
client, err = lecdnclientsdkv3.NewClient(serverUrl, lecdnclientsdkv3.WithLogins(username, password))
|
||||
case AUTH_METHOD_APIKEY:
|
||||
client, err = lecdnclientsdkv3.NewClient(serverUrl, lecdnclientsdkv3.WithLogins(username, password))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
case sdkRoleMaster:
|
||||
{
|
||||
// v3 版主控端
|
||||
var client *lecdnmastersdkv3.Client
|
||||
var err error
|
||||
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
client, err = lecdnmastersdkv3.NewClient(serverUrl, lecdnmastersdkv3.WithLogins(username, password))
|
||||
case AUTH_METHOD_APIKEY:
|
||||
client, err = lecdnmastersdkv3.NewClient(serverUrl, lecdnmastersdkv3.WithLogins(username, password))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
case sdkVersionV4:
|
||||
{
|
||||
switch apiRole {
|
||||
case sdkRoleClient:
|
||||
{
|
||||
// v4 版客户端
|
||||
var client *lecdnclientsdkv4.Client
|
||||
var err error
|
||||
|
||||
return client, nil
|
||||
} else if apiVersion == sdkVersionV3 && apiRole == sdkRoleMaster {
|
||||
// v3 版主控端
|
||||
client, err := lecdnmasterv3.NewClient(serverUrl,
|
||||
lecdnmasterv3.WithLogins(username, password),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
client, err = lecdnclientsdkv4.NewClient(serverUrl, lecdnclientsdkv4.WithLogins(username, password))
|
||||
case AUTH_METHOD_APIKEY:
|
||||
client, err = lecdnclientsdkv4.NewClient(serverUrl, lecdnclientsdkv4.WithLogins(username, password))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
case sdkRoleMaster:
|
||||
{
|
||||
// v4 版主控端
|
||||
var client *lecdnmastersdkv4.Client
|
||||
var err error
|
||||
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
client, err = lecdnmastersdkv4.NewClient(serverUrl, lecdnmastersdkv4.WithLogins(username, password))
|
||||
case AUTH_METHOD_APIKEY:
|
||||
client, err = lecdnmastersdkv4.NewClient(serverUrl, lecdnmastersdkv4.WithLogins(username, password))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipTlsVerify {
|
||||
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("lecdn: invalid api version or user role")
|
||||
|
||||
@@ -382,20 +382,11 @@ func createSDKClient(serverUrl, authMethod, username, password, apiToken string,
|
||||
|
||||
switch authMethod {
|
||||
case "", AUTH_METHOD_PASSWORD:
|
||||
{
|
||||
client, err = npmsdk.NewClient(
|
||||
serverUrl,
|
||||
npmsdk.WithLogins(username, password),
|
||||
)
|
||||
}
|
||||
|
||||
client, err = npmsdk.NewClient(serverUrl, npmsdk.WithLogins(username, password))
|
||||
case AUTH_METHOD_TOKEN:
|
||||
{
|
||||
client, err = npmsdk.NewClient(
|
||||
serverUrl,
|
||||
npmsdk.WithJwtToken(apiToken),
|
||||
)
|
||||
}
|
||||
client, err = npmsdk.NewClient(serverUrl, npmsdk.WithJwtToken(apiToken))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package tencentcloudddospro
|
||||
|
||||
const (
|
||||
// 匹配模式:精确匹配。
|
||||
DOMAIN_MATCH_PATTERN_EXACT = "exact"
|
||||
// 匹配模式:通配符匹配。
|
||||
DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard"
|
||||
// 匹配模式:证书 SAN 匹配。
|
||||
DOMAIN_MATCH_PATTERN_CERTSAN = "certsan"
|
||||
)
|
||||
@@ -0,0 +1,289 @@
|
||||
package tencentcloudddospro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
|
||||
tcantiddos "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos/v20200309"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-ssl"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
|
||||
xtencentcloud "github.com/certimate-go/certimate/pkg/utils/third-party/tencentcloud"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Deployer
|
||||
DeployResult = core.DeployerDeployResult
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 腾讯云 SecretId。
|
||||
SecretId string `json:"secretId"`
|
||||
// 腾讯云 SecretKey。
|
||||
SecretKey string `json:"secretKey"`
|
||||
// 腾讯云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
// 腾讯云接口端点。
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// DDoS 高防实例 ID。
|
||||
InstanceId string `json:"instanceId"`
|
||||
// 域名匹配模式。
|
||||
// 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。
|
||||
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
|
||||
// 网站域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *tcantiddos.Client
|
||||
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, err := createSDKClient(config.SecretId, config.SecretKey, config.Endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
SecretId: config.SecretId,
|
||||
SecretKey: config.SecretKey,
|
||||
ProjectId: config.ProjectId,
|
||||
Endpoint: lo.Ternary(xtencentcloud.IsIntlAPIEndpoint(config.Endpoint), "ssl.intl.tencentcloudapi.com", ""),
|
||||
})
|
||||
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.InstanceId == "" {
|
||||
return nil, fmt.Errorf("config `instanceId` is required")
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
// 获取待部署的域名列表
|
||||
domains := make([]string, 0)
|
||||
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_WILDCARD:
|
||||
{
|
||||
if d.config.Domain == "" {
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||
domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId)
|
||||
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.getMatchedDomainsByCertId(ctx, upres.CertId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains = domainCandidates
|
||||
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 anti-ddos domains to deploy")
|
||||
} else {
|
||||
d.logger.Info("found anti-ddos 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) getMatchedDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) {
|
||||
domains := make([]string, 0)
|
||||
|
||||
// 获取指定证书可关联的域名
|
||||
// REF: https://cloud.tencent.com/document/api/297/95348
|
||||
describeL7RulesBySSLCertIdReq := tcantiddos.NewDescribeL7RulesBySSLCertIdRequest()
|
||||
describeL7RulesBySSLCertIdReq.Status = common.StringPtr("all")
|
||||
describeL7RulesBySSLCertIdReq.CertIds = []*string{common.StringPtr(cloudCertId)}
|
||||
describeL7RulesBySSLCertIdResp, err := d.sdkClient.DescribeL7RulesBySSLCertIdWithContext(ctx, describeL7RulesBySSLCertIdReq)
|
||||
d.logger.Debug("sdk request 'antiddos.DescribeL7RulesBySSLCertId'", slog.Any("request", describeL7RulesBySSLCertIdReq), slog.Any("response", describeL7RulesBySSLCertIdResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'antiddos.DescribeL7RulesBySSLCertId': %w", err)
|
||||
} else if len(describeL7RulesBySSLCertIdResp.Response.CertSet) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
|
||||
certEntry := describeL7RulesBySSLCertIdResp.Response.CertSet[0]
|
||||
for i := range certEntry.L7Rules {
|
||||
if lo.FromPtr(certEntry.L7Rules[i].InsId) == d.config.InstanceId {
|
||||
domains = append(domains, lo.FromPtr(certEntry.L7Rules[i].Domain))
|
||||
}
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, domain, cloudCertId string) error {
|
||||
// 高防 IP 获取 7 层规则
|
||||
// REF: https://cloud.tencent.com/document/api/297/95344
|
||||
var ruleEntry *tcantiddos.NewL7RuleEntry
|
||||
describeNewL7RulesOffset := 0
|
||||
describeNewL7RulesLimit := 100
|
||||
for {
|
||||
describeNewL7RulesReq := tcantiddos.NewDescribeNewL7RulesRequest()
|
||||
describeNewL7RulesReq.Business = common.StringPtr("bgpip")
|
||||
describeNewL7RulesReq.Domain = common.StringPtr(domain)
|
||||
describeNewL7RulesReq.Offset = common.Uint64Ptr(uint64(describeNewL7RulesOffset))
|
||||
describeNewL7RulesReq.Limit = common.Uint64Ptr(uint64(describeNewL7RulesLimit))
|
||||
describeNewL7RulesResp, err := d.sdkClient.DescribeNewL7RulesWithContext(ctx, describeNewL7RulesReq)
|
||||
d.logger.Debug("sdk request 'antiddos.DescribeNewL7Rules'", slog.Any("request", describeNewL7RulesReq), slog.Any("response", describeNewL7RulesResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'antiddos.DescribeNewL7Rules': %w", err)
|
||||
}
|
||||
|
||||
for _, ruleItem := range describeNewL7RulesResp.Response.Rules {
|
||||
if lo.FromPtr(ruleItem.Domain) == domain {
|
||||
ruleEntry = ruleItem
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(describeNewL7RulesResp.Response.Rules) < describeNewL7RulesLimit {
|
||||
break
|
||||
}
|
||||
|
||||
describeNewL7RulesOffset += describeNewL7RulesLimit
|
||||
}
|
||||
if ruleEntry == nil {
|
||||
return fmt.Errorf("could not find domain '%s'", domain)
|
||||
} else if lo.FromPtr(ruleEntry.CertType) == 2 && lo.FromPtr(ruleEntry.SSLId) == cloudCertId {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改 7 层转发规则
|
||||
modifyNewDomainRulesReq := tcantiddos.NewModifyNewDomainRulesRequest()
|
||||
modifyNewDomainRulesReq.Business = common.StringPtr("bgpip")
|
||||
modifyNewDomainRulesReq.Id = common.StringPtr(d.config.InstanceId)
|
||||
modifyNewDomainRulesReq.Rule = &tcantiddos.NewL7RuleEntry{
|
||||
Protocol: ruleEntry.Protocol,
|
||||
LbType: ruleEntry.LbType,
|
||||
Domain: ruleEntry.Domain,
|
||||
KeepEnable: ruleEntry.KeepEnable,
|
||||
KeepTime: ruleEntry.KeepTime,
|
||||
SourceType: ruleEntry.SourceType,
|
||||
SourceList: ruleEntry.SourceList,
|
||||
Region: ruleEntry.Region,
|
||||
Id: ruleEntry.Id,
|
||||
Ip: ruleEntry.Ip,
|
||||
RuleId: ruleEntry.RuleId,
|
||||
RuleName: ruleEntry.RuleName,
|
||||
CertType: common.Uint64Ptr(2),
|
||||
SSLId: common.StringPtr(cloudCertId),
|
||||
CCStatus: ruleEntry.CCStatus,
|
||||
CCEnable: ruleEntry.CCEnable,
|
||||
CCThresholdNew: ruleEntry.CCThresholdNew,
|
||||
CCLevel: ruleEntry.CCLevel,
|
||||
HttpsToHttpEnable: ruleEntry.HttpsToHttpEnable,
|
||||
VirtualPort: ruleEntry.VirtualPort,
|
||||
RewriteHttps: ruleEntry.RewriteHttps,
|
||||
}
|
||||
modifyNewDomainRulesResp, err := d.sdkClient.ModifyNewDomainRulesWithContext(ctx, modifyNewDomainRulesReq)
|
||||
d.logger.Debug("sdk request 'antiddos.ModifyNewDomainRules'", slog.Any("request", modifyNewDomainRulesReq), slog.Any("response", modifyNewDomainRulesResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'antiddos.ModifyNewDomainRules': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSDKClient(secretId, secretKey, endpoint string) (*tcantiddos.Client, error) {
|
||||
credential := common.NewCredential(secretId, secretKey)
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
if endpoint != "" {
|
||||
cpf.HttpProfile.Endpoint = endpoint
|
||||
}
|
||||
|
||||
client, err := tcantiddos.NewClient(credential, "", cpf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -216,8 +216,16 @@ func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudListenerI
|
||||
d.logger.Info("no need to deploy alb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateListenerDefaultCertificate(ctx, *describeListenerAttributesResp, cloudCertId)
|
||||
} else {
|
||||
if lo.SomeBy(describeListenerAttributesResp.DomainExtensions, func(domainExtension *vealb.DomainExtensionForDescribeListenerAttributesOutput) bool {
|
||||
return ve.StringValue(domainExtension.Domain) == d.config.Domain && ve.StringValue(domainExtension.CertCenterCertificateId) == cloudCertId
|
||||
}) {
|
||||
d.logger.Info("no need to deploy alb listener sni certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
return d.updateListenerSniCertificate(ctx, *describeListenerAttributesResp, cloudCertId)
|
||||
}
|
||||
@@ -247,7 +255,7 @@ func (d *Deployer) updateListenerSniCertificate(ctx context.Context, cloudListen
|
||||
ListenerId: cloudListenerInfo.ListenerId,
|
||||
DomainExtensions: lo.Map(
|
||||
lo.Filter(cloudListenerInfo.DomainExtensions, func(domain *vealb.DomainExtensionForDescribeListenerAttributesOutput, _ int) bool {
|
||||
return *domain.Domain == d.config.Domain
|
||||
return ve.StringValue(domain.Domain) == d.config.Domain
|
||||
}),
|
||||
func(domain *vealb.DomainExtensionForDescribeListenerAttributesOutput, _ int) *vealb.DomainExtensionForModifyListenerAttributesInput {
|
||||
return &vealb.DomainExtensionForModifyListenerAttributesInput{
|
||||
@@ -257,7 +265,8 @@ func (d *Deployer) updateListenerSniCertificate(ctx context.Context, cloudListen
|
||||
CertCenterCertificateId: ve.String(cloudCertId),
|
||||
Action: ve.String("modify"),
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
}
|
||||
modifyListenerAttributesResp, err := d.sdkClient.ModifyListenerAttributesWithContext(ctx, modifyListenerAttributesReq)
|
||||
d.logger.Debug("sdk request 'alb.ModifyListenerAttributes'", slog.Any("request", modifyListenerAttributesReq), slog.Any("response", modifyListenerAttributesResp))
|
||||
|
||||
@@ -117,6 +117,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
}
|
||||
|
||||
domains = domainCandidates
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
} else {
|
||||
domains = []string{d.config.Domain}
|
||||
}
|
||||
@@ -130,6 +133,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
}
|
||||
|
||||
domains = domainCandidates
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -192,10 +198,6 @@ func (d *Deployer) getMatchedDomainsByWildcard(ctx context.Context, wildcardDoma
|
||||
listCdnDomainsPageSize++
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
@@ -225,12 +227,6 @@ func (d *Deployer) getMatchedDomainsByCertId(ctx context.Context, cloudCertId st
|
||||
}
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
if len(describeCertConfigResp.SpecifiedCertConfig) == 0 {
|
||||
return nil, fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/samber/lo"
|
||||
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
|
||||
vesession "github.com/volcengine/volcengine-go-sdk/volcengine/session"
|
||||
|
||||
@@ -37,6 +38,9 @@ type DeployerConfig struct {
|
||||
// 负载均衡监听器 ID。
|
||||
// 部署目标为 [DEPLOY_TARGET_LISTENER] 时必填。
|
||||
ListenerId string `json:"listenerId,omitempty"`
|
||||
// SNI 域名(支持泛域名)。
|
||||
// 部署目标为 [DEPLOY_TARGET_LOADBALANCER]、[DEPLOY_TARGET_LISTENER] 时选填。
|
||||
Domain string `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
@@ -194,6 +198,42 @@ func (d *Deployer) deployToListener(ctx context.Context, cloudCertId string) err
|
||||
}
|
||||
|
||||
func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error {
|
||||
// 查询指定监听器的详细信息
|
||||
// REF: https://www.volcengine.com/docs/6406/71778
|
||||
describeListenerAttributesReq := &veclb.DescribeListenerAttributesInput{
|
||||
ListenerId: ve.String(cloudListenerId),
|
||||
}
|
||||
describeListenerAttributesResp, err := d.sdkClient.DescribeListenerAttributesWithContext(ctx, describeListenerAttributesReq)
|
||||
d.logger.Debug("sdk request 'clb.DescribeListenerAttributes'", slog.Any("request", describeListenerAttributesReq), slog.Any("response", describeListenerAttributesResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'clb.DescribeListenerAttributes': %w", err)
|
||||
}
|
||||
if describeListenerAttributesResp == nil {
|
||||
return fmt.Errorf("could not find clb listener '%s'", cloudListenerId)
|
||||
}
|
||||
|
||||
if d.config.Domain == "" {
|
||||
// 未指定 SNI,只需部署到监听器
|
||||
if ve.StringValue(describeListenerAttributesResp.CertCenterCertificateId) == cloudCertId {
|
||||
d.logger.Info("no need to deploy clb listener default certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateListenerDefaultCertificate(ctx, cloudListenerId, cloudCertId)
|
||||
} else {
|
||||
if lo.SomeBy(describeListenerAttributesResp.DomainExtensions, func(domainExtension *veclb.DomainExtensionForDescribeListenerAttributesOutput) bool {
|
||||
return ve.StringValue(domainExtension.Domain) == d.config.Domain && ve.StringValue(domainExtension.CertCenterCertificateId) == cloudCertId
|
||||
}) {
|
||||
d.logger.Info("no need to deploy clb listener sni certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 指定 SNI,需部署到扩展域名
|
||||
return d.updateListenerSniCertificate(ctx, describeListenerAttributesResp, cloudCertId)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployer) updateListenerDefaultCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error {
|
||||
// 修改指定监听器
|
||||
// REF: https://www.volcengine.com/docs/6406/71775
|
||||
modifyListenerAttributesReq := &veclb.ModifyListenerAttributesInput{
|
||||
@@ -210,6 +250,35 @@ func (d *Deployer) updateListenerCertificate(ctx context.Context, cloudListenerI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Deployer) updateListenerSniCertificate(ctx context.Context, cloudListenerInfo *veclb.DescribeListenerAttributesOutput, cloudCertId string) error {
|
||||
domainExtension, _ := lo.Find(cloudListenerInfo.DomainExtensions, func(domainExtension *veclb.DomainExtensionForDescribeListenerAttributesOutput) bool {
|
||||
return d.config.Domain == ve.StringValue(domainExtension.Domain)
|
||||
})
|
||||
if domainExtension == nil {
|
||||
return fmt.Errorf("could not find clb listener domain extension '%s' for listener '%s'", d.config.Domain, ve.StringValue(cloudListenerInfo.ListenerId))
|
||||
}
|
||||
|
||||
// 修改指定监听器的扩展域名证书
|
||||
// REF: https://www.volcengine.com/docs/6406/2193110
|
||||
modifyListenerDomainExtensionsReq := &veclb.ModifyListenerDomainExtensionsInput{
|
||||
ListenerId: cloudListenerInfo.ListenerId,
|
||||
ModifyDomainExtensions: []*veclb.ModifyDomainExtensionForModifyListenerDomainExtensionsInput{
|
||||
{
|
||||
DomainExtensionId: domainExtension.DomainExtensionId,
|
||||
CertificateSource: ve.String("cert_center"),
|
||||
CertCenterCertificateId: ve.String(cloudCertId),
|
||||
},
|
||||
},
|
||||
}
|
||||
modifyListenerDomainExtensionsResp, err := d.sdkClient.ModifyListenerDomainExtensionsWithContext(ctx, modifyListenerDomainExtensionsReq)
|
||||
d.logger.Debug("sdk request 'clb.ModifyListenerDomainExtensions'", slog.Any("request", modifyListenerDomainExtensionsReq), slog.Any("response", modifyListenerDomainExtensionsResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'clb.ModifyListenerDomainExtensions': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey, region string) (*veclb.CLB, error) {
|
||||
config := ve.NewConfig().
|
||||
WithAkSk(accessKeyId, secretAccessKey).
|
||||
|
||||
+1
-1
Submodule pkg/sdk3rd-forked updated: a0af860d1f...baf25c1f3c
+99
@@ -0,0 +1,99 @@
|
||||
package v20200309
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
antiddos "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos/v20200309"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
)
|
||||
|
||||
const APIVersion = antiddos.APIVersion
|
||||
|
||||
type Client struct {
|
||||
common.Client
|
||||
}
|
||||
|
||||
func NewClient(credential common.CredentialIface, region string, clientProfile *profile.ClientProfile) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
client.Init(region).
|
||||
WithCredential(credential).
|
||||
WithProfile(clientProfile)
|
||||
return
|
||||
}
|
||||
|
||||
func NewDescribeL7RulesBySSLCertIdRequest() (request *DescribeL7RulesBySSLCertIdRequest) {
|
||||
return antiddos.NewDescribeL7RulesBySSLCertIdRequest()
|
||||
}
|
||||
|
||||
func NewDescribeL7RulesBySSLCertIdResponse() (response *DescribeL7RulesBySSLCertIdResponse) {
|
||||
return antiddos.NewDescribeL7RulesBySSLCertIdResponse()
|
||||
}
|
||||
|
||||
func (c *Client) DescribeL7RulesBySSLCertIdWithContext(ctx context.Context, request *DescribeL7RulesBySSLCertIdRequest) (response *DescribeL7RulesBySSLCertIdResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewDescribeL7RulesBySSLCertIdRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "antiddos", APIVersion, "DescribeL7RulesBySSLCertId")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("DescribeL7RulesBySSLCertId require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewDescribeL7RulesBySSLCertIdResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
func NewDescribeNewL7RulesRequest() (request *DescribeNewL7RulesRequest) {
|
||||
return antiddos.NewDescribeNewL7RulesRequest()
|
||||
}
|
||||
|
||||
func NewDescribeNewL7RulesResponse() (response *DescribeNewL7RulesResponse) {
|
||||
return antiddos.NewDescribeNewL7RulesResponse()
|
||||
}
|
||||
|
||||
func (c *Client) DescribeNewL7RulesWithContext(ctx context.Context, request *DescribeNewL7RulesRequest) (response *DescribeNewL7RulesResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewDescribeNewL7RulesRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "antiddos", APIVersion, "DescribeNewL7Rules")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("DescribeNewL7Rules require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewDescribeNewL7RulesResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
func NewModifyNewDomainRulesRequest() (request *ModifyNewDomainRulesRequest) {
|
||||
return antiddos.NewModifyNewDomainRulesRequest()
|
||||
}
|
||||
|
||||
func NewModifyNewDomainRulesResponse() (response *ModifyNewDomainRulesResponse) {
|
||||
return antiddos.NewModifyNewDomainRulesResponse()
|
||||
}
|
||||
|
||||
func (c *Client) ModifyNewDomainRulesWithContext(ctx context.Context, request *ModifyNewDomainRulesRequest) (response *ModifyNewDomainRulesResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewModifyNewDomainRulesRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "antiddos", APIVersion, "ModifyNewDomainRules")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("ModifyNewDomainRules require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewModifyNewDomainRulesResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package v20200309
|
||||
|
||||
import (
|
||||
antiddos "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos/v20200309"
|
||||
)
|
||||
|
||||
type (
|
||||
NewL7RuleEntry = antiddos.NewL7RuleEntry
|
||||
)
|
||||
|
||||
type DescribeL7RulesBySSLCertIdRequest = antiddos.DescribeL7RulesBySSLCertIdRequest
|
||||
|
||||
type DescribeL7RulesBySSLCertIdResponse = antiddos.DescribeL7RulesBySSLCertIdResponse
|
||||
|
||||
type DescribeNewL7RulesRequest = antiddos.DescribeNewL7RulesRequest
|
||||
|
||||
type DescribeNewL7RulesResponse = antiddos.DescribeNewL7RulesResponse
|
||||
|
||||
type ModifyNewDomainRulesRequest = antiddos.ModifyNewDomainRulesRequest
|
||||
|
||||
type ModifyNewDomainRulesResponse = antiddos.ModifyNewDomainRulesResponse
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package clb
|
||||
|
||||
import (
|
||||
"github.com/volcengine/volcengine-go-sdk/service/clb"
|
||||
"github.com/volcengine/volcengine-go-sdk/volcengine"
|
||||
"github.com/volcengine/volcengine-go-sdk/volcengine/request"
|
||||
)
|
||||
|
||||
const opDescribeListenerAttributes = "DescribeListenerAttributes"
|
||||
|
||||
func (c *CLB) DescribeListenerAttributesRequest(input *DescribeListenerAttributesInput) (req *request.Request, output *DescribeListenerAttributesOutput) {
|
||||
op := &request.Operation{
|
||||
Name: opDescribeListenerAttributes,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: "/",
|
||||
}
|
||||
|
||||
if input == nil {
|
||||
input = &DescribeListenerAttributesInput{}
|
||||
}
|
||||
|
||||
output = &DescribeListenerAttributesOutput{}
|
||||
req = c.newRequest(op, input, output)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CLB) DescribeListenerAttributesWithContext(ctx volcengine.Context, input *DescribeListenerAttributesInput, opts ...request.Option) (*DescribeListenerAttributesOutput, error) {
|
||||
req, out := c.DescribeListenerAttributesRequest(input)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return out, req.Send()
|
||||
}
|
||||
|
||||
type DescribeListenerAttributesInput = clb.DescribeListenerAttributesInput
|
||||
|
||||
type DescribeListenerAttributesOutput = clb.DescribeListenerAttributesOutput
|
||||
|
||||
type DomainExtensionForDescribeListenerAttributesOutput = clb.DomainExtensionForDescribeListenerAttributesOutput
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package clb
|
||||
|
||||
import (
|
||||
"github.com/volcengine/volcengine-go-sdk/service/clb"
|
||||
"github.com/volcengine/volcengine-go-sdk/volcengine"
|
||||
"github.com/volcengine/volcengine-go-sdk/volcengine/request"
|
||||
)
|
||||
|
||||
const opModifyListenerDomainExtensions = "ModifyListenerDomainExtensions"
|
||||
|
||||
func (c *CLB) ModifyListenerDomainExtensionsRequest(input *ModifyListenerDomainExtensionsInput) (req *request.Request, output *ModifyListenerDomainExtensionsOutput) {
|
||||
op := &request.Operation{
|
||||
Name: opModifyListenerDomainExtensions,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: "/",
|
||||
}
|
||||
|
||||
if input == nil {
|
||||
input = &ModifyListenerDomainExtensionsInput{}
|
||||
}
|
||||
|
||||
output = &ModifyListenerDomainExtensionsOutput{}
|
||||
req = c.newRequest(op, input, output)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CLB) ModifyListenerDomainExtensionsWithContext(ctx volcengine.Context, input *ModifyListenerDomainExtensionsInput, opts ...request.Option) (*ModifyListenerDomainExtensionsOutput, error) {
|
||||
req, out := c.ModifyListenerDomainExtensionsRequest(input)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return out, req.Send()
|
||||
}
|
||||
|
||||
type ModifyListenerDomainExtensionsInput = clb.ModifyListenerDomainExtensionsInput
|
||||
|
||||
type ModifyListenerDomainExtensionsOutput = clb.ModifyListenerDomainExtensionsOutput
|
||||
|
||||
type ModifyDomainExtensionForModifyListenerDomainExtensionsInput = clb.ModifyDomainExtensionForModifyListenerDomainExtensionsInput
|
||||
@@ -0,0 +1,38 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CertificateQueryResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Data *CertificateDetail `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CertificateQuery(certId string) (*CertificateQueryResponse, error) {
|
||||
return c.CertificateQueryWithContext(context.Background(), certId)
|
||||
}
|
||||
|
||||
func (c *Client) CertificateQueryWithContext(ctx context.Context, certId string) (*CertificateQueryResponse, error) {
|
||||
if certId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset certId")
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodGet, nil, "certificateQuery")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetQueryParam("certId", certId)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &CertificateQueryResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CertificateQueryListResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Data []*Certificate `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CertificateQueryList() (*CertificateQueryListResponse, error) {
|
||||
return c.CertificateQueryListWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (c *Client) CertificateQueryListWithContext(ctx context.Context) (*CertificateQueryListResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodGet, nil, "certificateQueryList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &CertificateQueryListResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CertificateUploadRequest struct {
|
||||
PublicKey *string `json:"publicKey,omitempty"`
|
||||
PrivateKey *string `json:"privateKey,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type CertificateUploadResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Data json.Number `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CertificateUpload(req *CertificateUploadRequest) (*CertificateUploadResponse, error) {
|
||||
return c.CertificateUploadWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) CertificateUploadWithContext(ctx context.Context, req *CertificateUploadRequest) (*CertificateUploadResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodPost, req, "certificateUpload")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &CertificateUploadResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DomainModifyRequest struct {
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
Protocol *string `json:"protocol,omitempty"`
|
||||
CertId *int64 `json:"certId,omitempty"`
|
||||
}
|
||||
|
||||
type DomainModifyResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Data int64 `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) DomainModify(req *DomainModifyRequest) (*DomainModifyResponse, error) {
|
||||
return c.DomainModifyWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DomainModifyWithContext(ctx context.Context, req *DomainModifyRequest) (*DomainModifyResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodPut, req, "domainModify")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DomainModifyResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
qs "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
type DomainQueryListRequest struct {
|
||||
Domain *string `json:"domain,omitempty" url:"domain,omitempty"`
|
||||
SubDomain *bool `json:"subDomain,omitempty" url:"subDomain,omitempty"`
|
||||
LiveDomain *bool `json:"liveDomain,omitempty" url:"liveDomain,omitempty"`
|
||||
}
|
||||
|
||||
type DomainQueryListResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Data []*Domain `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) DomainQueryList(req *DomainQueryListRequest) (*DomainQueryListResponse, error) {
|
||||
return c.DomainQueryListWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DomainQueryListWithContext(ctx context.Context, req *DomainQueryListRequest) (*DomainQueryListResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodGet, nil, "domainQueryList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
values, err := qs.Values(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq.SetQueryParamsFromValues(values)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DomainQueryListResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// A simple SDK client for Asia-ISP CDN.
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
opts := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
if opts.AccessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
|
||||
}
|
||||
if opts.AccessKeySecret == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeySecret")
|
||||
}
|
||||
|
||||
signer := &signer{
|
||||
accessKeyId: opts.AccessKeyId,
|
||||
accessKeySecret: opts.AccessKeySecret,
|
||||
}
|
||||
httper := resty.New().
|
||||
SetBaseURL("https://api.asia-isp.com/openapi/v3/stat").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if err := signer.Sign(req); err != nil {
|
||||
return fmt.Errorf("sdkerr: sign error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{rc: httper}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
|
||||
c.rc.SetTLSClientConfig(config)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, params any, xAction string) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if xAction == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset action")
|
||||
}
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.SetQueryParam("action", xAction)
|
||||
req.SetBody(params)
|
||||
|
||||
// WARN:
|
||||
// DO NOT CALL `req.SetBody` or `req.SetFormData` AGAIN! USE `newRequest` INSTEAD.
|
||||
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
if rCode := res.GetCode(); rCode != 0 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', msg='%s'", rCode, res.GetMsg())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package asiaispcdn
|
||||
|
||||
type Domain struct {
|
||||
Domain string `json:"domain"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
Scope string `json:"scope"`
|
||||
Cname string `json:"cname"`
|
||||
ICP string `json:"icp"`
|
||||
Protocol string `json:"protocol"`
|
||||
CertId int64 `json:"certId"`
|
||||
OriginProtocol string `json:"originProtocol"`
|
||||
OriginHost string `json:"originHost"`
|
||||
OriginType string `json:"originType"`
|
||||
OriginAddr string `json:"originAddr"`
|
||||
OriginBackAddr string `json:"originBackAddr"`
|
||||
DomainStatus int32 `json:"domainStatus"`
|
||||
OperatingStatus int32 `json:"operatingStatus"`
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
CertId string `json:"certId"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CertificateDetail struct {
|
||||
Certificate
|
||||
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
SubjectName string `json:"subjectName"`
|
||||
Brand string `json:"brand"`
|
||||
IssueTime string `json:"issueTime"`
|
||||
ExpireTime string `json:"expireTime"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package asiaispcdn
|
||||
|
||||
type Options struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithAkSk(ak, sk string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.AccessKeyId = ak
|
||||
o.AccessKeySecret = sk
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package asiaispcdn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type signer struct {
|
||||
accessKeyId string
|
||||
accessKeySecret string
|
||||
}
|
||||
|
||||
func (s *signer) Sign(req *http.Request) error {
|
||||
method := strings.ToUpper(req.Method)
|
||||
|
||||
pathStr := "/"
|
||||
queryStr := ""
|
||||
if req.URL != nil {
|
||||
pathStr = req.URL.Path
|
||||
queryStr = req.URL.Query().Encode()
|
||||
}
|
||||
|
||||
payloadStr := ""
|
||||
if method != http.MethodGet && method != http.MethodDelete && req.Body != nil {
|
||||
payloadb, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloadStr = string(payloadb)
|
||||
req.Body = io.NopCloser(bytes.NewReader(payloadb))
|
||||
}
|
||||
|
||||
nonce := 10000000 + rand.Intn(90000000)
|
||||
timestamp := time.Now().UnixMilli()
|
||||
|
||||
stringToSign := ""
|
||||
if method == http.MethodGet || method == http.MethodDelete {
|
||||
stringToSign = fmt.Sprintf("accessKeySecret=%s&method=%s&nonce=%d&queryString=%s×tamp=%d&uri=%s", s.accessKeySecret, method, nonce, queryStr, timestamp, pathStr)
|
||||
} else {
|
||||
stringToSign = fmt.Sprintf("accessKeySecret=%s&body=%s&method=%s&nonce=%d&queryString=%s×tamp=%d&uri=%s", s.accessKeySecret, payloadStr, method, nonce, queryStr, timestamp, pathStr)
|
||||
}
|
||||
|
||||
h := hmac.New(sha1.New, []byte(s.accessKeySecret))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
req.Header.Set("accessKeyId", s.accessKeyId)
|
||||
req.Header.Set("nonce", fmt.Sprintf("%d", nonce))
|
||||
req.Header.Set("timestamp", fmt.Sprintf("%d", timestamp))
|
||||
req.Header.Set("signature", signature)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package asiaispcdn
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
GetMsg() string
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
Code *json.Number `json:"code,omitempty"`
|
||||
Msg *string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetCode() int {
|
||||
if r.Code.String() == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
code, err := r.Code.Int64()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(code)
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetMsg() string {
|
||||
if r.Msg == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.Msg
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -22,11 +22,11 @@ func (c *Client) CreateDomainWithCert(req *CreateDomainWithCertRequest) (*Create
|
||||
}
|
||||
|
||||
func (c *Client) CreateDomainWithCertWithContext(ctx context.Context, req *CreateDomainWithCertRequest) (*CreateDomainWithCertResponse, error) {
|
||||
if err := c.ensureApiUserToken(ctx); err != nil {
|
||||
if err := c.ensureConsoleToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &CreateDomainWithCertResponse{}
|
||||
err := c.sendRequestWithResult(ctx, http.MethodPost, "/host/create-domain-with-cert", req, resp)
|
||||
err := c.sendConsoleRequestWithResult(ctx, http.MethodPost, "/host/create-domain-with-cert", req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -5,8 +5,14 @@ import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -19,6 +25,7 @@ import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
xcrypto "github.com/certimate-go/certimate/pkg/utils/crypto"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
@@ -26,15 +33,15 @@ type Client struct {
|
||||
username string
|
||||
password string
|
||||
|
||||
serverlessToken string
|
||||
serverlessTokenAt time.Time
|
||||
serverlessTokenMu sync.Mutex
|
||||
identityToken string
|
||||
identityTokenAt time.Time
|
||||
identityTokenMu sync.Mutex
|
||||
|
||||
apiUserToken string
|
||||
apiUserTokenMu sync.Mutex
|
||||
consoleToken string
|
||||
consoleTokenMu sync.Mutex
|
||||
|
||||
rcForServerless *resty.Client
|
||||
rcForApiUser *resty.Client
|
||||
rcForIdentity *resty.Client
|
||||
rcForConsole *resty.Client
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -46,6 +53,11 @@ const (
|
||||
uniConsoleClientSecret = "4c1f7fbf-c732-42b0-ab10-4634a8bbe834"
|
||||
uniConsoleAppId = "__UNI__unicloud_console"
|
||||
uniConsoleSpaceId = "dc-6nfabcn6ada8d3dd"
|
||||
uniPlatform = "web"
|
||||
)
|
||||
|
||||
const (
|
||||
challengeAlgRSAOAEP256 = "RSA-OAEP-256+A256GCM"
|
||||
)
|
||||
|
||||
func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
@@ -65,18 +77,18 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
username: opts.Username,
|
||||
password: opts.Password,
|
||||
}
|
||||
client.rcForServerless = resty.New().
|
||||
client.rcForIdentity = resty.New().
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent)
|
||||
client.rcForApiUser = resty.New().
|
||||
client.rcForConsole = resty.New().
|
||||
SetBaseURL("https://unicloud-api.dcloud.net.cn/unicloud/api").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if client.apiUserToken != "" {
|
||||
req.Header.Set("Token", client.apiUserToken)
|
||||
if client.consoleToken != "" {
|
||||
req.Header.Set("Token", client.consoleToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -86,101 +98,37 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rcForServerless.SetTimeout(timeout)
|
||||
c.rcForIdentity.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) buildServerlessClientInfo(appId string) (_clientInfo map[string]any, _err error) {
|
||||
return map[string]any{
|
||||
"PLATFORM": "web",
|
||||
"OS": strings.ToUpper(runtime.GOOS),
|
||||
"APPID": appId,
|
||||
"DEVICEID": app.AppName,
|
||||
"LOCALE": "zh-Hans",
|
||||
"osName": runtime.GOOS,
|
||||
"appId": appId,
|
||||
"appName": "uniCloud",
|
||||
"deviceId": app.AppName,
|
||||
"deviceType": "pc",
|
||||
"uniPlatform": "web",
|
||||
"uniCompilerVersion": "4.45",
|
||||
"uniRuntimeVersion": "4.45",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) buildServerlessPayloadInfo(appId, spaceId, target, method, action string, params, data any) (map[string]any, error) {
|
||||
clientInfo, err := c.buildServerlessClientInfo(appId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
functionArgsParams := make([]any, 0)
|
||||
if params != nil {
|
||||
functionArgsParams = append(functionArgsParams, params)
|
||||
}
|
||||
|
||||
functionArgs := map[string]any{
|
||||
"clientInfo": clientInfo,
|
||||
"uniIdToken": c.serverlessToken,
|
||||
}
|
||||
if method != "" {
|
||||
functionArgs["method"] = method
|
||||
functionArgs["params"] = make([]any, 0)
|
||||
}
|
||||
if action != "" {
|
||||
type _obj struct{}
|
||||
functionArgs["action"] = action
|
||||
functionArgs["data"] = &_obj{}
|
||||
}
|
||||
if params != nil {
|
||||
functionArgs["params"] = []any{params}
|
||||
}
|
||||
if data != nil {
|
||||
functionArgs["data"] = data
|
||||
}
|
||||
|
||||
jsonb, err := json.Marshal(map[string]any{
|
||||
"functionTarget": target,
|
||||
"functionArgs": functionArgs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := map[string]any{
|
||||
"method": "serverless.function.runtime.invoke",
|
||||
"params": string(jsonb),
|
||||
"spaceId": spaceId,
|
||||
"timestamp": time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (c *Client) invokeServerless(ctx context.Context, endpoint, clientSecret, appId, spaceId, target, method, action string, params, data any) (*resty.Response, error) {
|
||||
func (c *Client) sendIdentityRequest(ctx context.Context, endpoint, clientSecret, appId, spaceId, functionTarget, functionMethod, functionAction string, functionParams, functionData any) (*resty.Response, error) {
|
||||
if endpoint == "" {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: endpoint cannot be empty")
|
||||
}
|
||||
|
||||
payload, err := c.buildServerlessPayloadInfo(appId, spaceId, target, method, action, params, data)
|
||||
payloadInfo, clientInfo, err := buildServerlessPayloadInfo(appId, spaceId, functionTarget, functionMethod, functionAction, functionParams, functionData, c.identityToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: failed to build request: %w", err)
|
||||
}
|
||||
|
||||
clientInfo, _ := c.buildServerlessClientInfo(appId)
|
||||
clientInfoJsonb, _ := json.Marshal(clientInfo)
|
||||
sign := generateSignature(payloadInfo, clientSecret)
|
||||
|
||||
sign := generateSignature(payload, clientSecret)
|
||||
|
||||
req := c.rcForServerless.R().
|
||||
req := c.rcForIdentity.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Origin", "https://unicloud.dcloud.net.cn").
|
||||
SetHeader("Referer", "https://unicloud.dcloud.net.cn").
|
||||
SetHeader("X-Client-Info", string(clientInfoJsonb)).
|
||||
SetHeader("X-Client-Token", c.serverlessToken).
|
||||
SetHeader("Origin", "https://account.dcloud.net.cn").
|
||||
SetHeader("Referer", "https://account.dcloud.net.cn").
|
||||
SetHeader("X-Serverless-Sign", sign).
|
||||
SetBody(payload).
|
||||
SetBody(payloadInfo).
|
||||
SetContext(ctx)
|
||||
if clientInfo != nil {
|
||||
clientInfoBytes, _ := json.Marshal(clientInfo)
|
||||
req.SetHeader("X-Client-Info", string(clientInfoBytes))
|
||||
}
|
||||
if c.identityToken != "" {
|
||||
req.SetHeader("X-Client-Token", c.identityToken)
|
||||
}
|
||||
|
||||
resp, err := req.Post(endpoint)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
@@ -191,8 +139,8 @@ func (c *Client) invokeServerless(ctx context.Context, endpoint, clientSecret, a
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) invokeServerlessWithResult(ctx context.Context, endpoint, clientSecret, appId, spaceId, target, method, action string, params, data any, result sdkResponse) error {
|
||||
resp, err := c.invokeServerless(ctx, endpoint, clientSecret, appId, spaceId, target, method, action, params, data)
|
||||
func (c *Client) sendIdentityRequestWithResult(ctx context.Context, endpoint, clientSecret, appId, spaceId, functionTarget, functionMethod, functionAction string, functionParams, functionData any, result sdkResponse) error {
|
||||
resp, err := c.sendIdentityRequest(ctx, endpoint, clientSecret, appId, spaceId, functionTarget, functionMethod, functionAction, functionParams, functionData)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &result)
|
||||
@@ -209,8 +157,8 @@ func (c *Client) invokeServerlessWithResult(ctx context.Context, endpoint, clien
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(ctx context.Context, method string, path string, params any) (*resty.Response, error) {
|
||||
req := c.rcForApiUser.R().
|
||||
func (c *Client) sendConsoleRequest(ctx context.Context, method string, path string, params any) (*resty.Response, error) {
|
||||
req := c.rcForConsole.R().
|
||||
SetContext(ctx)
|
||||
if strings.EqualFold(method, http.MethodGet) {
|
||||
qs := make(map[string]string)
|
||||
@@ -240,8 +188,8 @@ func (c *Client) sendRequest(ctx context.Context, method string, path string, pa
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) sendRequestWithResult(ctx context.Context, method string, path string, params any, result sdkResponse) error {
|
||||
resp, err := c.sendRequest(ctx, method, path, params)
|
||||
func (c *Client) sendConsoleRequestWithResult(ctx context.Context, method string, path string, params any, result sdkResponse) error {
|
||||
resp, err := c.sendConsoleRequest(ctx, method, path, params)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &result)
|
||||
@@ -252,73 +200,146 @@ func (c *Client) sendRequestWithResult(ctx context.Context, method string, path
|
||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||
return fmt.Errorf("sdkerr: failed to unmarshal response: %w", err)
|
||||
} else if rReturnCode := result.GetReturnCode(); rReturnCode != 0 {
|
||||
return fmt.Errorf("sdkerr: ret='%d', desc='%s'", rReturnCode, result.GetReturnDesc())
|
||||
return fmt.Errorf("sdkerr: api error: ret='%d', desc='%s'", rReturnCode, result.GetReturnDesc())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ensureServerlessToken(ctx context.Context) error {
|
||||
c.serverlessTokenMu.Lock()
|
||||
defer c.serverlessTokenMu.Unlock()
|
||||
if c.serverlessToken != "" && c.serverlessTokenAt.After(time.Now()) {
|
||||
func (c *Client) ensureIdentityToken(ctx context.Context) error {
|
||||
c.identityTokenMu.Lock()
|
||||
defer c.identityTokenMu.Unlock()
|
||||
if c.identityToken != "" && c.identityTokenAt.After(time.Now()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"password": "password",
|
||||
}
|
||||
if regexp.MustCompile(`^1\d{10}$`).MatchString(c.username) {
|
||||
params["mobile"] = c.username
|
||||
} else if regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`).MatchString(c.username) {
|
||||
params["email"] = c.username
|
||||
} else {
|
||||
params["username"] = c.username
|
||||
}
|
||||
challangeId := ""
|
||||
challengeVer := 0
|
||||
challengeAlg := ""
|
||||
challengeKey := ""
|
||||
challengeKeyId := ""
|
||||
|
||||
type loginResponse struct {
|
||||
sdkResponseBase
|
||||
Data *struct {
|
||||
Code int32 `json:"errCode"`
|
||||
UID string `json:"uid"`
|
||||
NewToken *struct {
|
||||
Token string `json:"token"`
|
||||
TokenExpired int64 `json:"tokenExpired"`
|
||||
} `json:"newToken,omitempty"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
resp := &loginResponse{}
|
||||
if err := c.invokeServerlessWithResult(ctx,
|
||||
uniIdentityEndpoint, uniIdentityClientSecret, uniIdentityAppId, uniIdentitySpaceId,
|
||||
"uni-id-co", "login", "", params, nil,
|
||||
resp); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.Data == nil || resp.Data.NewToken == nil || resp.Data.NewToken.Token == "" {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty token")
|
||||
// 生成密码挑战
|
||||
{
|
||||
createPasswordChallengeParams := map[string]any{
|
||||
"action": "login",
|
||||
"resetAppId": uniConsoleAppId,
|
||||
"resetUniPlatform": uniPlatform,
|
||||
}
|
||||
|
||||
tokenAt := time.UnixMilli(resp.Data.NewToken.TokenExpired)
|
||||
if tokenAt.IsZero() {
|
||||
return fmt.Errorf("sdkerr: auth error: received invalid token expiration")
|
||||
createPasswordChallengeResp := &struct {
|
||||
sdkResponseBase
|
||||
Data *struct {
|
||||
ErrCode json.RawMessage `json:"errCode,omitempty"`
|
||||
ErrMsg string `json:"errMsg,omitempty"`
|
||||
Version int `json:"version"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
KeyId string `json:"keyId"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
ChallengeId string `json:"challengeId"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
} `json:"data,omitempty"`
|
||||
}{}
|
||||
|
||||
if err := c.sendIdentityRequestWithResult(ctx,
|
||||
uniIdentityEndpoint, uniIdentityClientSecret, uniIdentityAppId, uniIdentitySpaceId,
|
||||
"uni-id-co", "createPasswordChallenge", "", createPasswordChallengeParams, nil,
|
||||
createPasswordChallengeResp); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if createPasswordChallengeResp.Data != nil && createPasswordChallengeResp.Data.ErrCode != nil {
|
||||
respErrCode := strings.Trim(string(createPasswordChallengeResp.Data.ErrCode), "\"")
|
||||
if respErrCode != "" && respErrCode != "0" {
|
||||
return fmt.Errorf("sdkerr: auth error: errCode='%s', errMsg='%s'", respErrCode, createPasswordChallengeResp.Data.ErrMsg)
|
||||
}
|
||||
}
|
||||
|
||||
if createPasswordChallengeResp.Data == nil || createPasswordChallengeResp.Data.ChallengeId == "" {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty challenge")
|
||||
}
|
||||
|
||||
challangeId = createPasswordChallengeResp.Data.ChallengeId
|
||||
challengeVer = createPasswordChallengeResp.Data.Version
|
||||
challengeAlg = createPasswordChallengeResp.Data.Algorithm
|
||||
challengeKey = createPasswordChallengeResp.Data.PublicKey
|
||||
challengeKeyId = createPasswordChallengeResp.Data.KeyId
|
||||
}
|
||||
}
|
||||
|
||||
// 登录
|
||||
{
|
||||
envelope := make(map[string]any)
|
||||
|
||||
switch challengeAlg {
|
||||
case challengeAlgRSAOAEP256:
|
||||
envelopeInfo, err := buildPasswordEnvelopeWithRSAOAEP256AES256GCM(c.username, c.password, challangeId, challengeVer, challengeKey, challengeKeyId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sdkerr: auth error: failed to build password envelope: %w", err)
|
||||
}
|
||||
envelope = envelopeInfo
|
||||
default:
|
||||
return fmt.Errorf("sdkerr: auth error: unsupported challenge algorithm: %s", challengeAlg)
|
||||
}
|
||||
|
||||
c.serverlessToken = resp.Data.NewToken.Token
|
||||
c.serverlessTokenAt = tokenAt
|
||||
loginParams := map[string]any{
|
||||
"captcha": "",
|
||||
"resetAppId": uniConsoleAppId,
|
||||
"resetUniPlatform": uniPlatform,
|
||||
"isReturnToken": false,
|
||||
"passwordEnvelope": envelope,
|
||||
}
|
||||
|
||||
loginResp := &struct {
|
||||
sdkResponseBase
|
||||
Data *struct {
|
||||
ErrCode json.RawMessage `json:"errCode,omitempty"`
|
||||
ErrMsg string `json:"errMsg,omitempty"`
|
||||
UID string `json:"uid"`
|
||||
NewToken *struct {
|
||||
Token string `json:"token"`
|
||||
TokenExpired int64 `json:"tokenExpired"`
|
||||
} `json:"newToken,omitempty"`
|
||||
} `json:"data,omitempty"`
|
||||
}{}
|
||||
|
||||
if err := c.sendIdentityRequestWithResult(ctx,
|
||||
uniIdentityEndpoint, uniIdentityClientSecret, uniIdentityAppId, uniIdentitySpaceId,
|
||||
"uni-id-co", "login", "", loginParams, nil,
|
||||
loginResp); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if loginResp.Data != nil && loginResp.Data.ErrCode != nil {
|
||||
respErrCode := strings.Trim(string(loginResp.Data.ErrCode), "\"")
|
||||
if respErrCode != "" && respErrCode != "0" {
|
||||
return fmt.Errorf("sdkerr: auth error: errCode='%s', errMsg='%s'", respErrCode, loginResp.Data.ErrMsg)
|
||||
}
|
||||
}
|
||||
|
||||
if loginResp.Data == nil || loginResp.Data.NewToken == nil || loginResp.Data.NewToken.Token == "" {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty token")
|
||||
}
|
||||
|
||||
tokenAt := time.UnixMilli(loginResp.Data.NewToken.TokenExpired)
|
||||
if tokenAt.IsZero() {
|
||||
return fmt.Errorf("sdkerr: auth error: received invalid token expiration")
|
||||
}
|
||||
|
||||
c.identityToken = loginResp.Data.NewToken.Token
|
||||
c.identityTokenAt = tokenAt
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ensureApiUserToken(ctx context.Context) error {
|
||||
if err := c.ensureServerlessToken(ctx); err != nil {
|
||||
func (c *Client) ensureConsoleToken(ctx context.Context) error {
|
||||
if err := c.ensureIdentityToken(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.apiUserTokenMu.Lock()
|
||||
defer c.apiUserTokenMu.Unlock()
|
||||
if c.apiUserToken != "" {
|
||||
c.consoleTokenMu.Lock()
|
||||
defer c.consoleTokenMu.Unlock()
|
||||
if c.consoleToken != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -338,7 +359,7 @@ func (c *Client) ensureApiUserToken(ctx context.Context) error {
|
||||
}
|
||||
|
||||
resp := &getUserTokenResponse{}
|
||||
if err := c.invokeServerlessWithResult(ctx,
|
||||
if err := c.sendIdentityRequestWithResult(ctx,
|
||||
uniConsoleEndpoint, uniConsoleClientSecret, uniConsoleAppId, uniConsoleSpaceId,
|
||||
"uni-cloud-kernel", "", "user/getUserToken", nil, map[string]any{"isLogin": true},
|
||||
resp); err != nil {
|
||||
@@ -348,12 +369,167 @@ func (c *Client) ensureApiUserToken(ctx context.Context) error {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty token")
|
||||
}
|
||||
|
||||
c.apiUserToken = resp.Data.Data.Data.Token
|
||||
c.consoleToken = resp.Data.Data.Data.Token
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildServerlessClientInfo(appId string) (map[string]any, error) {
|
||||
deviceId := strings.ToLower(app.AppName)
|
||||
return map[string]any{
|
||||
"APPID": appId,
|
||||
"DEVICEID": deviceId,
|
||||
"LOCALE": "zh-Hans",
|
||||
"OS": strings.ToLower(runtime.GOOS),
|
||||
"PLATFORM": uniPlatform,
|
||||
"appId": appId,
|
||||
"appLanguage": "zh-Hans",
|
||||
"appName": "uniCloud",
|
||||
"appVersion": "1.0.0",
|
||||
"appVersionCode": "100",
|
||||
"deviceId": deviceId,
|
||||
"deviceModel": "PC",
|
||||
"deviceType": "pc",
|
||||
"locale": "zh-Hans",
|
||||
"osName": runtime.GOOS,
|
||||
"osVersion": runtime.GOARCH,
|
||||
"scene": 1001,
|
||||
"uniPlatform": uniPlatform,
|
||||
"uniCompilerVersion": "5.23",
|
||||
"uniRuntimeVersion": "5.23",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildServerlessPayloadInfo(appId, spaceId, functionTarget, functionMethod, functionAction string, functionParams, functionData any, uniIdToken string) (map[string]any, map[string]any, error) {
|
||||
clientInfo, err := buildServerlessClientInfo(appId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
functionArgsParams := make([]any, 0)
|
||||
if functionParams != nil {
|
||||
functionArgsParams = append(functionArgsParams, functionParams)
|
||||
}
|
||||
|
||||
functionArgs := map[string]any{
|
||||
"clientInfo": clientInfo,
|
||||
}
|
||||
if functionMethod != "" {
|
||||
functionArgs["method"] = functionMethod
|
||||
functionArgs["params"] = make([]any, 0)
|
||||
}
|
||||
if functionAction != "" {
|
||||
type _obj struct{}
|
||||
functionArgs["action"] = functionAction
|
||||
functionArgs["data"] = &_obj{}
|
||||
}
|
||||
if functionParams != nil {
|
||||
functionArgs["params"] = []any{functionParams}
|
||||
}
|
||||
if functionData != nil {
|
||||
functionArgs["data"] = functionData
|
||||
}
|
||||
if uniIdToken != "" {
|
||||
functionArgs["uniIdToken"] = uniIdToken
|
||||
}
|
||||
|
||||
jsonb, err := json.Marshal(map[string]any{
|
||||
"functionTarget": functionTarget,
|
||||
"functionArgs": functionArgs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
payloadInfo := map[string]any{
|
||||
"method": "serverless.function.runtime.invoke",
|
||||
"params": string(jsonb),
|
||||
"spaceId": spaceId,
|
||||
"timestamp": time.Now().UnixMilli(),
|
||||
}
|
||||
return payloadInfo, clientInfo, nil
|
||||
}
|
||||
|
||||
func buildPasswordEnvelopeWithRSAOAEP256AES256GCM(username, password string, challengeId string, challengeVer int, challengeKey string, challengeKeyId string) (map[string]any, error) {
|
||||
plain := map[string]any{
|
||||
"action": "login",
|
||||
"challengeId": challengeId,
|
||||
"issuedAt": time.Now().UnixMilli(),
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
if regexp.MustCompile(`^1\d{10}$`).MatchString(username) {
|
||||
plain["mobile"] = username
|
||||
delete(plain, "username")
|
||||
} else if regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`).MatchString(username) {
|
||||
plain["email"] = username
|
||||
delete(plain, "username")
|
||||
}
|
||||
|
||||
plainBytes, err := json.Marshal(plain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(keyBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ivBytes := make([]byte, 12)
|
||||
if _, err := rand.Read(ivBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aad := map[string]any{
|
||||
"version": challengeVer,
|
||||
"algorithm": challengeAlgRSAOAEP256,
|
||||
"keyId": challengeKeyId,
|
||||
"challengeId": challengeId,
|
||||
}
|
||||
// 务必是固定字段顺序,否则会登录报错。因此这里暂时写死 JSON 字符串拼接。
|
||||
// aadBytes, err := json.Marshal(aad)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
aadBytes := fmt.Appendf(nil, `{"version":%d,"algorithm":"%s","keyId":"%s","challengeId":"%s"}`, challengeVer, challengeAlgRSAOAEP256, challengeKeyId, challengeId)
|
||||
|
||||
cryptor := xcrypto.NewAESCryptor(keyBytes)
|
||||
cipherBytes, err := cryptor.GCMEncrypt(plainBytes, ivBytes, aadBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const tagSize int = 16
|
||||
tagBytes := cipherBytes[len(cipherBytes)-tagSize:]
|
||||
cipherBytes = cipherBytes[:len(cipherBytes)-tagSize]
|
||||
|
||||
rsaPubKeyPemBlock, _ := pem.Decode([]byte(challengeKey))
|
||||
if rsaPubKeyPemBlock == nil || (rsaPubKeyPemBlock.Type != "PUBLIC KEY") {
|
||||
return nil, fmt.Errorf("invalid public key")
|
||||
}
|
||||
|
||||
rsaPubKey, err := x509.ParsePKIXPublicKey(rsaPubKeyPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encKeyBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPubKey.(*rsa.PublicKey), keyBytes, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envelope := map[string]any{
|
||||
"encryptedKey": base64.RawStdEncoding.EncodeToString(encKeyBytes),
|
||||
"ciphertext": base64.RawStdEncoding.EncodeToString(cipherBytes),
|
||||
"iv": base64.RawStdEncoding.EncodeToString(ivBytes),
|
||||
"tag": base64.RawStdEncoding.EncodeToString(tagBytes),
|
||||
}
|
||||
xmaps.CopyTo(aad, envelope)
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
func generateSignature(params map[string]any, secret string) string {
|
||||
keys := xmaps.Keys(params)
|
||||
sort.Strings(keys)
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -28,8 +28,10 @@ func (c *Client) UpdateCertificateWithContext(ctx context.Context, certId int64,
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset certId")
|
||||
}
|
||||
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
if !c.isUseApiKey() {
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPut, fmt.Sprintf("/certificate/%d", certId))
|
||||
@@ -1,5 +1,5 @@
|
||||
// A simple SDK client for LeCDN v3 as user.
|
||||
package client
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
type Client struct {
|
||||
username string
|
||||
password string
|
||||
apiKey string
|
||||
|
||||
token string
|
||||
tokenMu sync.Mutex
|
||||
@@ -39,22 +40,32 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
if _, err := url.Parse(serverUrl); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
|
||||
}
|
||||
if opts.Username == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset username")
|
||||
if opts.ApiKey == "" && (opts.Username == "" || opts.Password == "") {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiKey or username/password")
|
||||
}
|
||||
if opts.Password == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset password")
|
||||
if opts.ApiKey != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("sdkerr: cannot set both apiKey and username/password")
|
||||
}
|
||||
|
||||
baseUrl := strings.TrimSuffix(serverUrl, "/")
|
||||
if opts.ApiKey != "" {
|
||||
baseUrl += "/api/client"
|
||||
} else {
|
||||
baseUrl += "/prod-api"
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
username: opts.Username,
|
||||
password: opts.Password,
|
||||
apiKey: opts.ApiKey,
|
||||
}
|
||||
client.rc = resty.New().
|
||||
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/prod-api").
|
||||
SetBaseURL(baseUrl).
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if client.token != "" {
|
||||
if client.apiKey != "" {
|
||||
req.Header.Set("Authorization", client.apiKey)
|
||||
} else if client.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+client.token)
|
||||
}
|
||||
|
||||
@@ -123,7 +134,7 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
if rCode := res.GetCode(); rCode != 200 {
|
||||
if rCode := res.GetCode(); rCode != 0 && rCode != 200 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", rCode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
@@ -173,3 +184,7 @@ func (c *Client) ensureToken(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) isUseApiKey() bool {
|
||||
return c.apiKey != ""
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package master
|
||||
package v3
|
||||
|
||||
type Options struct {
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
@@ -11,5 +12,14 @@ func WithLogins(username, password string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Username = username
|
||||
o.Password = password
|
||||
o.ApiKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func WithApiKey(apiKey string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.ApiKey = apiKey
|
||||
o.Username = ""
|
||||
o.Password = ""
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package v3
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
@@ -0,0 +1,50 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateCertificateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SSLPEM string `json:"ssl_pem"`
|
||||
SSLKey string `json:"ssl_key"`
|
||||
AutoRenewal bool `json:"auto_renewal"`
|
||||
}
|
||||
|
||||
type UpdateCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
return c.UpdateCertificateWithContext(context.Background(), certId, req)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCertificateWithContext(ctx context.Context, certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
if certId == 0 {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset certId")
|
||||
}
|
||||
|
||||
if !c.isUseApiKey() {
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPut, fmt.Sprintf("/certificate/%d", certId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &UpdateCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// A simple SDK client for LeCDN v4 as user.
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
username string
|
||||
password string
|
||||
apiKey string
|
||||
|
||||
token string
|
||||
tokenMu sync.Mutex
|
||||
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
opts := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
if serverUrl == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset serverUrl")
|
||||
}
|
||||
if _, err := url.Parse(serverUrl); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
|
||||
}
|
||||
if opts.ApiKey == "" && (opts.Username == "" || opts.Password == "") {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiKey or username/password")
|
||||
}
|
||||
if opts.ApiKey != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("sdkerr: cannot set both apiKey and username/password")
|
||||
}
|
||||
|
||||
baseUrl := strings.TrimSuffix(serverUrl, "/")
|
||||
if opts.ApiKey != "" {
|
||||
baseUrl += "/api/client"
|
||||
} else {
|
||||
baseUrl += "/prod-api"
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
username: opts.Username,
|
||||
password: opts.Password,
|
||||
apiKey: opts.ApiKey,
|
||||
}
|
||||
client.rc = resty.New().
|
||||
SetBaseURL(baseUrl).
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if client.apiKey != "" {
|
||||
req.Header.Set("Authorization", client.apiKey)
|
||||
} else if client.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+client.token)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
|
||||
c.rc.SetTLSClientConfig(config)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
|
||||
// WARN:
|
||||
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
if rCode := res.GetCode(); rCode != 0 && rCode != 200 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", rCode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) ensureToken(ctx context.Context) error {
|
||||
c.tokenMu.Lock()
|
||||
defer c.tokenMu.Unlock()
|
||||
if c.token != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/auth/login")
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
httpreq.SetBody(map[string]string{
|
||||
"email": c.username,
|
||||
"username": c.username,
|
||||
"password": c.password,
|
||||
})
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
sdkResponseBase
|
||||
Data *struct {
|
||||
UserId int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
result := &loginResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if result.Data == nil || result.Data.Token == "" {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty token")
|
||||
}
|
||||
|
||||
c.token = result.Data.Token
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) isUseApiKey() bool {
|
||||
return c.apiKey != ""
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package client
|
||||
package v4
|
||||
|
||||
type Options struct {
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
@@ -11,5 +12,14 @@ func WithLogins(username, password string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Username = username
|
||||
o.Password = password
|
||||
o.ApiKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func WithApiKey(apiKey string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.ApiKey = apiKey
|
||||
o.Username = ""
|
||||
o.Password = ""
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package master
|
||||
package v4
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
package master
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -29,8 +29,10 @@ func (c *Client) UpdateCertificateWithContext(ctx context.Context, certId int64,
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset certId")
|
||||
}
|
||||
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
if !c.isUseApiKey() {
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPut, fmt.Sprintf("/certificate/%d", certId))
|
||||
@@ -1,5 +1,5 @@
|
||||
// A simple SDK client for LeCDN v3 as admin.
|
||||
package master
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
type Client struct {
|
||||
username string
|
||||
password string
|
||||
apiKey string
|
||||
|
||||
token string
|
||||
tokenMu sync.Mutex
|
||||
@@ -39,24 +40,34 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
if _, err := url.Parse(serverUrl); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
|
||||
}
|
||||
if opts.Username == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset username")
|
||||
if opts.ApiKey == "" && (opts.Username == "" || opts.Password == "") {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiKey or username/password")
|
||||
}
|
||||
if opts.Password == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset password")
|
||||
if opts.ApiKey != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("sdkerr: cannot set both apiKey and username/password")
|
||||
}
|
||||
|
||||
baseUrl := strings.TrimSuffix(serverUrl, "/")
|
||||
if opts.ApiKey != "" {
|
||||
baseUrl += "/api/admin"
|
||||
} else {
|
||||
baseUrl += "/prod-api"
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
username: opts.Username,
|
||||
password: opts.Password,
|
||||
apiKey: opts.ApiKey,
|
||||
}
|
||||
client.rc = resty.New().
|
||||
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/prod-api").
|
||||
SetBaseURL(baseUrl).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if client.token != "" {
|
||||
if client.apiKey != "" {
|
||||
req.Header.Set("Authorization", client.apiKey)
|
||||
} else if client.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+client.token)
|
||||
}
|
||||
|
||||
@@ -125,7 +136,7 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
if rCode := res.GetCode(); rCode != 200 {
|
||||
if rCode := res.GetCode(); rCode != 0 && rCode != 200 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", rCode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
@@ -174,3 +185,7 @@ func (c *Client) ensureToken(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) isUseApiKey() bool {
|
||||
return c.apiKey != ""
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package v3
|
||||
|
||||
type Options struct {
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithLogins(username, password string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Username = username
|
||||
o.Password = password
|
||||
o.ApiKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func WithApiKey(apiKey string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.ApiKey = apiKey
|
||||
o.Username = ""
|
||||
o.Password = ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package v3
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
GetMessage() string
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetCode() int {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetMessage() string {
|
||||
return r.Message
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -0,0 +1,50 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateCertificateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SSLPEM string `json:"ssl_pem"`
|
||||
SSLKey string `json:"ssl_key"`
|
||||
AutoRenewal bool `json:"auto_renewal"`
|
||||
}
|
||||
|
||||
type UpdateCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
return c.UpdateCertificateWithContext(context.Background(), certId, req)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCertificateWithContext(ctx context.Context, certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
if certId == 0 {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset certId")
|
||||
}
|
||||
|
||||
if !c.isUseApiKey() {
|
||||
if err := c.ensureToken(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPut, fmt.Sprintf("/certificate/%d", certId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &UpdateCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// A simple SDK client for LeCDN v4 as admin.
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
username string
|
||||
password string
|
||||
apiKey string
|
||||
|
||||
token string
|
||||
tokenMu sync.Mutex
|
||||
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
opts := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
if serverUrl == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset serverUrl")
|
||||
}
|
||||
if _, err := url.Parse(serverUrl); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
|
||||
}
|
||||
if opts.ApiKey == "" && (opts.Username == "" || opts.Password == "") {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiKey or username/password")
|
||||
}
|
||||
if opts.ApiKey != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("sdkerr: cannot set both apiKey and username/password")
|
||||
}
|
||||
|
||||
baseUrl := strings.TrimSuffix(serverUrl, "/")
|
||||
if opts.ApiKey != "" {
|
||||
baseUrl += "/api/admin"
|
||||
} else {
|
||||
baseUrl += "/prod-api"
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
username: opts.Username,
|
||||
password: opts.Password,
|
||||
apiKey: opts.ApiKey,
|
||||
}
|
||||
client.rc = resty.New().
|
||||
SetBaseURL(baseUrl).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if client.apiKey != "" {
|
||||
req.Header.Set("Authorization", client.apiKey)
|
||||
} else if client.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+client.token)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
|
||||
c.rc.SetTLSClientConfig(config)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
|
||||
// WARN:
|
||||
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
if rCode := res.GetCode(); rCode != 0 && rCode != 200 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", rCode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) ensureToken(ctx context.Context) error {
|
||||
c.tokenMu.Lock()
|
||||
defer c.tokenMu.Unlock()
|
||||
if c.token != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/auth/login")
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
httpreq.SetBody(map[string]string{
|
||||
"username": c.username,
|
||||
"password": c.password,
|
||||
})
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
sdkResponseBase
|
||||
Data *struct {
|
||||
UserId int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
result := &loginResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if result.Data == nil || result.Data.Token == "" {
|
||||
return fmt.Errorf("sdkerr: auth error: received empty token")
|
||||
}
|
||||
|
||||
c.token = result.Data.Token
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) isUseApiKey() bool {
|
||||
return c.apiKey != ""
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package v4
|
||||
|
||||
type Options struct {
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithLogins(username, password string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Username = username
|
||||
o.Password = password
|
||||
o.ApiKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func WithApiKey(apiKey string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.ApiKey = apiKey
|
||||
o.Username = ""
|
||||
o.Password = ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package v4
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
GetMessage() string
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetCode() int {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetMessage() string {
|
||||
return r.Message
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -41,7 +41,10 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
|
||||
}
|
||||
if opts.JwtToken == "" && (opts.Username == "" || opts.Password == "") {
|
||||
return nil, fmt.Errorf("sdkerr: unset password or jwtToken")
|
||||
return nil, fmt.Errorf("sdkerr: unset jwtToken or username/password")
|
||||
}
|
||||
if opts.JwtToken != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("sdkerr: cannot set both jwtToken and username/password")
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
|
||||
@@ -12,11 +12,14 @@ func WithLogins(username, password string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Username = username
|
||||
o.Password = password
|
||||
o.JwtToken = ""
|
||||
}
|
||||
}
|
||||
|
||||
func WithJwtToken(jwtToken string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.JwtToken = jwtToken
|
||||
o.Username = ""
|
||||
o.Password = ""
|
||||
}
|
||||
}
|
||||
|
||||
+45
-4
@@ -10,21 +10,23 @@ import (
|
||||
)
|
||||
|
||||
type AESCryptor interface {
|
||||
CBCEncrypt(data []byte) ([]byte, error)
|
||||
CBCDecrypt(cipher []byte) ([]byte, error)
|
||||
CBCEncrypt(plaintext []byte) ([]byte, error)
|
||||
CBCDecrypt(ciphertext []byte) ([]byte, error)
|
||||
GCMEncrypt(plaintext, iv, aad []byte) ([]byte, error)
|
||||
GCMDecrypt(ciphertext, iv, aad []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type aesCryptor struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (c *aesCryptor) CBCEncrypt(data []byte) ([]byte, error) {
|
||||
func (c *aesCryptor) CBCEncrypt(plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paddedData := c.pkcs7Padding(data, aes.BlockSize)
|
||||
paddedData := c.pkcs7Padding(plaintext, aes.BlockSize)
|
||||
ciphertext := make([]byte, aes.BlockSize+len(paddedData))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
@@ -60,6 +62,45 @@ func (c *aesCryptor) CBCDecrypt(ciphertext []byte) ([]byte, error) {
|
||||
return c.pkcs7Unpadding(ciphertext), nil
|
||||
}
|
||||
|
||||
func (c *aesCryptor) GCMEncrypt(plaintext, iv, aad []byte) ([]byte, error) {
|
||||
const gcmStandardNonceSize int = 12
|
||||
if len(iv) != gcmStandardNonceSize {
|
||||
return nil, fmt.Errorf("the iv length must be %d bytes for GCM", gcmStandardNonceSize)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesGCM, err := cipher.NewGCMWithNonceSize(block, len(iv))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := aesGCM.Seal(nil, iv, plaintext, aad)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (c *aesCryptor) GCMDecrypt(ciphertext, iv, aad []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plaintext, err := aesGCM.Open(nil, iv, ciphertext, aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (c *aesCryptor) pkcs7Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - (len(data) % blockSize)
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -14,6 +14,7 @@ import AccessConfigFieldsProviderActalisSSL from "./AccessConfigFieldsProviderAc
|
||||
import AccessConfigFieldsProviderAkamai from "./AccessConfigFieldsProviderAkamai";
|
||||
import AccessConfigFieldsProviderAPISIX from "./AccessConfigFieldsProviderAPISIX";
|
||||
import AccessConfigFieldsProviderArvanCloud from "./AccessConfigFieldsProviderArvanCloud";
|
||||
import AccessConfigFieldsProviderAsiaISPCDN from "./AccessConfigFieldsProviderAsiaISPCDN";
|
||||
import AccessConfigFieldsProviderAWS from "./AccessConfigFieldsProviderAWS";
|
||||
import AccessConfigFieldsProviderAxisNow from "./AccessConfigFieldsProviderAxisNow";
|
||||
import AccessConfigFieldsProviderAzure from "./AccessConfigFieldsProviderAzure";
|
||||
@@ -135,6 +136,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.AKAMAI]: AccessConfigFieldsProviderAkamai,
|
||||
[ACCESS_PROVIDERS.APISIX]: AccessConfigFieldsProviderAPISIX,
|
||||
[ACCESS_PROVIDERS.ARVANCLOUD]: AccessConfigFieldsProviderArvanCloud,
|
||||
[ACCESS_PROVIDERS.ASIAISPCDN]: AccessConfigFieldsProviderAsiaISPCDN,
|
||||
[ACCESS_PROVIDERS.AWS]: AccessConfigFieldsProviderAWS,
|
||||
[ACCESS_PROVIDERS.AXISNOW]: AccessConfigFieldsProviderAxisNow,
|
||||
[ACCESS_PROVIDERS.AZURE]: AccessConfigFieldsProviderAzure,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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 AccessConfigFormFieldsProviderAsiaISPCDN = () => {
|
||||
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, "accessKeyId"]}
|
||||
initialValue={initialValues.accessKeyId}
|
||||
label={t("access.form.asiaispcdn_access_key_id.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.asiaispcdn_access_key_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "accessKeySecret"]}
|
||||
initialValue={initialValues.accessKeySecret}
|
||||
label={t("access.form.asiaispcdn_access_key_secret.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.asiaispcdn_access_key_secret.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
accessKeyId: "",
|
||||
accessKeySecret: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
|
||||
return z.object({
|
||||
accessKeyId: z.string().nonempty(),
|
||||
accessKeySecret: z.string().nonempty(),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderAsiaISPCDN, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -3,8 +3,13 @@ import { Form, Input, Radio, Select, Switch } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AUTH_METHOD_PASSWORD = "password" as const;
|
||||
const AUTH_METHOD_APIKEY = "apikey" as const;
|
||||
|
||||
const AccessConfigFormFieldsProviderLeCDN = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
@@ -13,8 +18,11 @@ const AccessConfigFormFieldsProviderLeCDN = () => {
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const fieldAuthMethod = Form.useWatch<string>([parentNamePath, "authMethod"], formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name={[parentNamePath, "serverUrl"]} initialValue={initialValues.serverUrl} label={t("access.form.lecdn_server_url.label")} rules={[formRule]}>
|
||||
@@ -27,7 +35,7 @@ const AccessConfigFormFieldsProviderLeCDN = () => {
|
||||
label={t("access.form.lecdn_api_version.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select options={["v3"].map((s) => ({ label: s, value: s }))} placeholder={t("access.form.lecdn_api_version.placeholder")} />
|
||||
<Select options={["v3", "v4"].map((s) => ({ label: s, value: s }))} placeholder={t("access.form.lecdn_api_version.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={[parentNamePath, "apiRole"]} initialValue={initialValues.apiRole} label={t("access.form.lecdn_api_role.label")} rules={[formRule]}>
|
||||
@@ -39,13 +47,33 @@ const AccessConfigFormFieldsProviderLeCDN = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={[parentNamePath, "username"]} initialValue={initialValues.username} label={t("access.form.lecdn_username.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.lecdn_username.placeholder")} />
|
||||
<Form.Item
|
||||
name={[parentNamePath, "authMethod"]}
|
||||
initialValue={initialValues.authMethod}
|
||||
label={t("access.form.lecdn_auth_method.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Radio.Group block>
|
||||
<Radio.Button value={AUTH_METHOD_PASSWORD}>{t("access.form.lecdn_auth_method.option.password.label")}</Radio.Button>
|
||||
<Radio.Button value={AUTH_METHOD_APIKEY}>{t("access.form.lecdn_auth_method.option.apikey.label")}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={[parentNamePath, "password"]} initialValue={initialValues.password} label={t("access.form.lecdn_password.label")} rules={[formRule]}>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.lecdn_password.placeholder")} />
|
||||
</Form.Item>
|
||||
<Show when={fieldAuthMethod === AUTH_METHOD_PASSWORD}>
|
||||
<Form.Item name={[parentNamePath, "username"]} initialValue={initialValues.username} label={t("access.form.lecdn_username.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.lecdn_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={[parentNamePath, "password"]} initialValue={initialValues.password} label={t("access.form.lecdn_password.label")} rules={[formRule]}>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.lecdn_password.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldAuthMethod === AUTH_METHOD_APIKEY}>
|
||||
<Form.Item name={[parentNamePath, "apiKey"]} initialValue={initialValues.apiKey} label={t("access.form.lecdn_api_key.label")} rules={[formRule]}>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.lecdn_api_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
@@ -62,24 +90,67 @@ const AccessConfigFormFieldsProviderLeCDN = () => {
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
serverUrl: "http://<your-host-addr>:5090/",
|
||||
apiVersion: "v3",
|
||||
apiVersion: "v4",
|
||||
apiRole: "client",
|
||||
username: "",
|
||||
password: "",
|
||||
authMethod: AUTH_METHOD_PASSWORD,
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
|
||||
return z.object({
|
||||
serverUrl: z.url({ protocol: z.core.regexes.httpProtocol }),
|
||||
apiVersion: z.enum(["v3"]),
|
||||
apiRole: z.enum(["client", "master"]),
|
||||
username: z.string().nonempty(),
|
||||
password: z.string().nonempty(),
|
||||
allowInsecureConnections: z.boolean().nullish(),
|
||||
});
|
||||
return z
|
||||
.object({
|
||||
serverUrl: z.url({ protocol: z.core.regexes.httpProtocol }),
|
||||
apiVersion: z.enum(["v3", "v4"]),
|
||||
apiRole: z.enum(["client", "master"]),
|
||||
authMethod: z.enum([AUTH_METHOD_PASSWORD, AUTH_METHOD_APIKEY]),
|
||||
username: z.string().nullish(),
|
||||
password: z.string().nullish(),
|
||||
apiKey: z.string().nullish(),
|
||||
allowInsecureConnections: z.boolean().nullish(),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
switch (values.authMethod) {
|
||||
case AUTH_METHOD_PASSWORD:
|
||||
{
|
||||
const scUsername = z.string().nonempty();
|
||||
const spUsername = scUsername.safeParse(values.username);
|
||||
if (!spUsername.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spUsername.error).errors.join(),
|
||||
path: ["username"],
|
||||
});
|
||||
}
|
||||
|
||||
const scPassword = z.string().nonempty();
|
||||
const spPassword = scPassword.safeParse(values.password);
|
||||
if (!spPassword.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spPassword.error).errors.join(),
|
||||
path: ["password"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AUTH_METHOD_APIKEY:
|
||||
{
|
||||
const scApiKey = z.string().nonempty();
|
||||
const spApiKey = scApiKey.safeParse(values.apiKey);
|
||||
if (!spApiKey.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spApiKey.error).errors.join(),
|
||||
path: ["apiKey"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderLeCDN, {
|
||||
|
||||
@@ -20,8 +20,8 @@ import { isTouchSupported } from "@/utils/browser";
|
||||
import { DegisnerContextProvider } from "./_context";
|
||||
import { getAllElements } from "./elements";
|
||||
import NodeRender from "./NodeRender";
|
||||
import { getAllNodeRegistries } from "./nodes";
|
||||
import { BranchNode } from "./nodes/_shared";
|
||||
import { getAllNodeRegistries, isNodeTypeRegistered } from "./nodes";
|
||||
import { BaseNode } from "./nodes/_shared";
|
||||
import "./flowgram.css";
|
||||
|
||||
export interface DesignerProps {
|
||||
@@ -110,7 +110,7 @@ const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
defaultExpanded: true,
|
||||
},
|
||||
formMeta: {
|
||||
render: () => <BranchNode description={type} />,
|
||||
render: () => <BaseNode description={type} />,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -195,6 +195,12 @@ const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
if (typeof node === "string") {
|
||||
node = flowgramEditorRef.current!.document.getNode(node)!;
|
||||
}
|
||||
if (!node) {
|
||||
return Promise.reject(`Node '${node}' not found`);
|
||||
}
|
||||
if (!isNodeTypeRegistered(node.type)) {
|
||||
return Promise.reject(`Node type '${node.type}' is not registered`);
|
||||
}
|
||||
|
||||
const form = node.form;
|
||||
return form ? form.validate().then((res) => res && !form.state.invalid) : Promise.resolve(true);
|
||||
|
||||
@@ -22,6 +22,7 @@ import BizDeployNodeConfigFieldsProviderAliyunOSS from "./BizDeployNodeConfigFie
|
||||
import BizDeployNodeConfigFieldsProviderAliyunVOD from "./BizDeployNodeConfigFieldsProviderAliyunVOD";
|
||||
import BizDeployNodeConfigFieldsProviderAliyunWAF from "./BizDeployNodeConfigFieldsProviderAliyunWAF";
|
||||
import BizDeployNodeConfigFieldsProviderAPISIX from "./BizDeployNodeConfigFieldsProviderAPISIX";
|
||||
import BizDeployNodeConfigFieldsProviderAsiaISPCDN from "./BizDeployNodeConfigFieldsProviderAsiaISPCDN";
|
||||
import BizDeployNodeConfigFieldsProviderAWSACM from "./BizDeployNodeConfigFieldsProviderAWSACM";
|
||||
import BizDeployNodeConfigFieldsProviderAWSALB from "./BizDeployNodeConfigFieldsProviderAWSALB";
|
||||
import BizDeployNodeConfigFieldsProviderAWSAmplify from "./BizDeployNodeConfigFieldsProviderAWSAmplify";
|
||||
@@ -102,6 +103,7 @@ import BizDeployNodeConfigFieldsProviderTencentCloudCDN from "./BizDeployNodeCon
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudCLB from "./BizDeployNodeConfigFieldsProviderTencentCloudCLB";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudCOS from "./BizDeployNodeConfigFieldsProviderTencentCloudCOS";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudCSS from "./BizDeployNodeConfigFieldsProviderTencentCloudCSS";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudDDoSPro from "./BizDeployNodeConfigFieldsProviderTencentCloudDDoSPro";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudECDN from "./BizDeployNodeConfigFieldsProviderTencentCloudECDN";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudEO from "./BizDeployNodeConfigFieldsProviderTencentCloudEO";
|
||||
import BizDeployNodeConfigFieldsProviderTencentCloudEOMakers from "./BizDeployNodeConfigFieldsProviderTencentCloudEOMakers";
|
||||
@@ -160,6 +162,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
|
||||
[DEPLOYMENT_PROVIDERS.ALIYUN_VOD]: BizDeployNodeConfigFieldsProviderAliyunVOD,
|
||||
[DEPLOYMENT_PROVIDERS.ALIYUN_WAF]: BizDeployNodeConfigFieldsProviderAliyunWAF,
|
||||
[DEPLOYMENT_PROVIDERS.APISIX]: BizDeployNodeConfigFieldsProviderAPISIX,
|
||||
[DEPLOYMENT_PROVIDERS.ASIAISPCDN]: BizDeployNodeConfigFieldsProviderAsiaISPCDN,
|
||||
[DEPLOYMENT_PROVIDERS.AWS_ACM]: BizDeployNodeConfigFieldsProviderAWSACM,
|
||||
[DEPLOYMENT_PROVIDERS.AWS_ALB]: BizDeployNodeConfigFieldsProviderAWSALB,
|
||||
[DEPLOYMENT_PROVIDERS.AWS_AMPLIFY]: BizDeployNodeConfigFieldsProviderAWSAmplify,
|
||||
@@ -240,6 +243,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CLB]: BizDeployNodeConfigFieldsProviderTencentCloudCLB,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_COS]: BizDeployNodeConfigFieldsProviderTencentCloudCOS,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CSS]: BizDeployNodeConfigFieldsProviderTencentCloudCSS,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_DDOSPRO]: BizDeployNodeConfigFieldsProviderTencentCloudDDoSPro,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_ECDN]: BizDeployNodeConfigFieldsProviderTencentCloudECDN,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EO]: BizDeployNodeConfigFieldsProviderTencentCloudEO,
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EOMAKERS]: BizDeployNodeConfigFieldsProviderTencentCloudEOMakers,
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
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 BizDeployNodeConfigFieldsProviderAsiaISPCDN = () => {
|
||||
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.asiaispcdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.asiaispcdn_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(BizDeployNodeConfigFieldsProviderAsiaISPCDN, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
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 BizDeployNodeConfigFieldsProviderTencentCloudDDoSPro = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance<typeof formSchema>();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "endpoint"]}
|
||||
initialValue={initialValues.endpoint}
|
||||
label={t("workflow_node.deploy.form.tencentcloud_ddospro_endpoint.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_ddospro_endpoint.tooltip") }}></span>}
|
||||
>
|
||||
<Input allowClear placeholder={t("workflow_node.deploy.form.tencentcloud_ddospro_endpoint.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "instanceId"]}
|
||||
initialValue={initialValues.instanceId}
|
||||
label={t("workflow_node.deploy.form.tencentcloud_ddospro_instance_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_ddospro_instance_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_ddospro_instance_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<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.tencentcloud_ddospro_domain.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_ddospro_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
instanceId: "",
|
||||
domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT,
|
||||
domain: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z
|
||||
.object({
|
||||
endpoint: z.string().nullish(),
|
||||
instanceId: 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:
|
||||
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(BizDeployNodeConfigFieldsProviderTencentCloudDDoSPro, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
+21
-1
@@ -4,6 +4,7 @@ import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { isDomain } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
@@ -73,6 +74,18 @@ const BizDeployNodeConfigFieldsProviderVolcEngineCLB = () => {
|
||||
<Input placeholder={t("workflow_node.deploy.form.volcengine_clb_listener_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === DEPLOY_TARGET_LOADBALANCER || fieldResourceType === DEPLOY_TARGET_LISTENER}>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "domain"]}
|
||||
initialValue={initialValues.domain}
|
||||
label={t("workflow_node.deploy.form.volcengine_clb_snidomain.label")}
|
||||
extra={t("workflow_node.deploy.form.volcengine_clb_snidomain.help")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input allowClear placeholder={t("workflow_node.deploy.form.volcengine_clb_snidomain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -85,7 +98,7 @@ const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
const { t } = i18n;
|
||||
|
||||
return z
|
||||
.object({
|
||||
@@ -93,6 +106,13 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
deployTarget: z.enum([DEPLOY_TARGET_LOADBALANCER, DEPLOY_TARGET_LISTENER]),
|
||||
loadbalancerId: z.string().nullish(),
|
||||
listenerId: z.string().nullish(),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
.refine((v) => {
|
||||
if (!v) return true;
|
||||
return isDomain(v, { allowWildcard: true });
|
||||
}, t("common.errmsg.domain_invalid")),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
switch (values.deployTarget) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { type FlowNodeType } from "@flowgram.ai/fixed-layout-editor";
|
||||
|
||||
import { BizApplyNodeRegistry } from "./BizApplyNodeRegistry";
|
||||
import { BizDeployNodeRegistry } from "./BizDeployNodeRegistry";
|
||||
import { BizMonitorNodeRegistry } from "./BizMonitorNodeRegistry";
|
||||
import { BizNotifyNodeRegistry } from "./BizNotifyNodeRegistry";
|
||||
import { BizPurgeNodeRegistry } from "./BizPurgeNodeRegistry";
|
||||
import { BizUploadNodeRegistry } from "./BizUploadNodeRegistry";
|
||||
import { BranchBlockNodeRegistry, ConditionNodeRegistry } from "./ConditionNode";
|
||||
import { DelayNodeRegistry } from "./DelayNode";
|
||||
import { EndNodeRegistry } from "./EndNode";
|
||||
import { StartNodeRegistry } from "./StartNode";
|
||||
import { CatchBlockNodeRegistry, TryCatchNodeRegistry } from "./TryCatchNode";
|
||||
|
||||
const nodeRegistries = [
|
||||
StartNodeRegistry,
|
||||
EndNodeRegistry,
|
||||
DelayNodeRegistry,
|
||||
BizApplyNodeRegistry,
|
||||
BizUploadNodeRegistry,
|
||||
BizMonitorNodeRegistry,
|
||||
BizDeployNodeRegistry,
|
||||
BizPurgeNodeRegistry,
|
||||
BizNotifyNodeRegistry,
|
||||
ConditionNodeRegistry,
|
||||
BranchBlockNodeRegistry,
|
||||
TryCatchNodeRegistry,
|
||||
CatchBlockNodeRegistry,
|
||||
];
|
||||
|
||||
export const getAllNodeRegistries = () => {
|
||||
return [...nodeRegistries];
|
||||
};
|
||||
|
||||
export const isNodeTypeRegistered = (type: FlowNodeType) => {
|
||||
return nodeRegistries.some((registry) => registry.type === type);
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import { mergeCls } from "@/utils/css";
|
||||
import { type NodeJSON, type NodeRegistry } from "./typings";
|
||||
import { duplicateNodeJSON } from "../_util";
|
||||
import { useNodeRenderContext } from "../NodeRenderContext";
|
||||
import { isNodeTypeRegistered } from "./NodeRegistries";
|
||||
|
||||
const useInternalRenamingInput = ({ nodeRender }: { nodeRender: NodeRenderReturnType }) => {
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
@@ -98,11 +99,11 @@ const InternalNodeCard = ({
|
||||
|
||||
const isActivated = useMemo(() => nodeRenderData.activated || nodeRenderData.lineActivated, [nodeRenderData.activated, nodeRenderData.lineActivated]);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [isNodeInvalid, setIsNodeInvalid] = useState(false);
|
||||
const [isNodeInvalid, setIsNodeInvalid] = useState(!isNodeTypeRegistered(nodeRegistry.type));
|
||||
const isNodeDisabled = useWatchFormValueIn(nodeRender.node, "disabled");
|
||||
|
||||
const formState = useWatchFormState(nodeRender.node);
|
||||
useEffect(() => setIsNodeInvalid(!!formState?.invalid), [formState?.invalid]);
|
||||
useEffect(() => setIsNodeInvalid(!isNodeTypeRegistered(nodeRegistry.type) || !!formState?.invalid), [formState?.invalid]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
||||
@@ -1,31 +1,3 @@
|
||||
import { BizApplyNodeRegistry } from "./BizApplyNodeRegistry";
|
||||
import { BizDeployNodeRegistry } from "./BizDeployNodeRegistry";
|
||||
import { BizMonitorNodeRegistry } from "./BizMonitorNodeRegistry";
|
||||
import { BizNotifyNodeRegistry } from "./BizNotifyNodeRegistry";
|
||||
import { BizPurgeNodeRegistry } from "./BizPurgeNodeRegistry";
|
||||
import { BizUploadNodeRegistry } from "./BizUploadNodeRegistry";
|
||||
import { BranchBlockNodeRegistry, ConditionNodeRegistry } from "./ConditionNode";
|
||||
import { DelayNodeRegistry } from "./DelayNode";
|
||||
import { EndNodeRegistry } from "./EndNode";
|
||||
import { StartNodeRegistry } from "./StartNode";
|
||||
import { CatchBlockNodeRegistry, TryCatchNodeRegistry } from "./TryCatchNode";
|
||||
|
||||
export const getAllNodeRegistries = () => {
|
||||
return [
|
||||
StartNodeRegistry,
|
||||
EndNodeRegistry,
|
||||
DelayNodeRegistry,
|
||||
BizApplyNodeRegistry,
|
||||
BizUploadNodeRegistry,
|
||||
BizMonitorNodeRegistry,
|
||||
BizDeployNodeRegistry,
|
||||
BizPurgeNodeRegistry,
|
||||
BizNotifyNodeRegistry,
|
||||
ConditionNodeRegistry,
|
||||
BranchBlockNodeRegistry,
|
||||
TryCatchNodeRegistry,
|
||||
CatchBlockNodeRegistry,
|
||||
];
|
||||
};
|
||||
export { getAllNodeRegistries, isNodeTypeRegistered } from "./NodeRegistries";
|
||||
|
||||
export type * from "./typings";
|
||||
|
||||
+284
-274
@@ -15,131 +15,134 @@ interface BaseProviderWithAccess<P> extends BaseProvider<P> {
|
||||
}
|
||||
|
||||
// #region AccessProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const ACCESS_PROVIDERS = Object.freeze({
|
||||
["1PANEL"]: "1panel",
|
||||
["35CN"]: "35cn",
|
||||
["51DNSCOM"]: "51dnscom",
|
||||
ACMECA: "acmeca",
|
||||
ACMEDNS: "acmedns",
|
||||
ACMEHTTPREQ: "acmehttpreq",
|
||||
ACTALISSSL: "actalisssl",
|
||||
AKAMAI: "akamai",
|
||||
ALIYUN: "aliyun",
|
||||
APISIX: "apisix",
|
||||
ARVANCLOUD: "arvancloud",
|
||||
AWS: "aws",
|
||||
AXISNOW: "axisnow",
|
||||
AZURE: "azure",
|
||||
BAIDUCLOUD: "baiducloud",
|
||||
BAOTAPANEL: "baotapanel",
|
||||
BAOTAPANELGO: "baotapanelgo",
|
||||
BAOTAWAF: "baotawaf",
|
||||
BEGET: "beget",
|
||||
BOOKMYNAME: "bookmyname",
|
||||
BUNNY: "bunny",
|
||||
BYTEPLUS: "byteplus",
|
||||
CACHEFLY: "cachefly",
|
||||
CDNFLY: "cdnfly",
|
||||
CLOUDFLARE: "cloudflare",
|
||||
CLOUDNS: "cloudns",
|
||||
CMCCCLOUD: "cmcccloud",
|
||||
CONOHAVPS: "conohavps",
|
||||
CONSTELLIX: "constellix",
|
||||
CPANEL: "cpanel",
|
||||
CTCCCLOUD: "ctcccloud",
|
||||
DESEC: "desec",
|
||||
DIGICERT: "digicert",
|
||||
DIGITALOCEAN: "digitalocean",
|
||||
DINGTALKBOT: "dingtalkbot",
|
||||
DISCORDBOT: "discordbot",
|
||||
DNSEXIT: "dnsexit",
|
||||
DNSLA: "dnsla",
|
||||
DNSMADEEASY: "dnsmadeeasy",
|
||||
DOKPLOY: "dokploy",
|
||||
DUCKDNS: "duckdns",
|
||||
DYNADOT: "dynadot",
|
||||
DYNU: "dynu",
|
||||
DYNV6: "dynv6",
|
||||
EMAIL: "email",
|
||||
FLEXCDN: "flexcdn",
|
||||
FLYIO: "flyio",
|
||||
FTP: "ftp",
|
||||
GANDINET: "gandinet",
|
||||
GCORE: "gcore",
|
||||
GLOBALSIGNATLAS: "globalsignatlas",
|
||||
GNAME: "gname",
|
||||
GODADDY: "godaddy",
|
||||
GOEDGE: "goedge",
|
||||
GOOGLECLOUD: "googlecloud",
|
||||
GOOGLETRUSTSERVICES: "googletrustservices",
|
||||
HETZNER: "hetzner",
|
||||
HOSTINGDE: "hostingde",
|
||||
HOSTINGER: "hostinger",
|
||||
HUAWEICLOUD: "huaweicloud",
|
||||
HUAWEIIBMC: "huaweiibmc",
|
||||
INFOMANIAK: "infomaniak",
|
||||
IONOS: "ionos",
|
||||
JDCLOUD: "jdcloud",
|
||||
KONG: "kong",
|
||||
LARKBOT: "larkbot",
|
||||
LECDN: "lecdn",
|
||||
LETSENCRYPT: "letsencrypt",
|
||||
LETSENCRYPTSTAGING: "letsencryptstaging",
|
||||
LINODE: "linode",
|
||||
LITESSL: "litessl",
|
||||
LOCAL: "local",
|
||||
MATRIX: "matrix",
|
||||
MATTERMOST: "mattermost",
|
||||
MOHUA: "mohua",
|
||||
NAMECHEAP: "namecheap",
|
||||
NAMEDOTCOM: "namedotcom",
|
||||
NAMESILO: "namesilo",
|
||||
NETCUP: "netcup",
|
||||
NETLIFY: "netlify",
|
||||
NGINXPROXYMANAGER: "nginxproxymanager",
|
||||
NS1: "ns1",
|
||||
ORACLECLOUD: "oraclecloud",
|
||||
OVHCLOUD: "ovhcloud",
|
||||
PORKBUN: "porkbun",
|
||||
POWERDNS: "powerdns",
|
||||
PROXMOXBS: "proxmoxbs",
|
||||
PROXMOXVE: "proxmoxve",
|
||||
QINGCLOUD: "qingcloud",
|
||||
RAINYUN: "rainyun",
|
||||
RATPANEL: "ratpanel",
|
||||
REGRU: "regru",
|
||||
RFC2136: "rfc2136",
|
||||
RUCENTER: "rucenter",
|
||||
S3: "s3",
|
||||
SAFELINE: "safeline",
|
||||
SAMWAF: "samwaf",
|
||||
SECTIGO: "sectigo",
|
||||
SIMPLYCOM: "simplycom",
|
||||
SLACKBOT: "slackbot",
|
||||
SPACESHIP: "spaceship",
|
||||
SSH: "ssh",
|
||||
SSLCOM: "sslcom",
|
||||
SYNOLOGYDSM: "synologydsm",
|
||||
TECHNITIUMDNS: "technitiumdns",
|
||||
TELEGRAMBOT: "telegrambot",
|
||||
TENCENTCLOUD: "tencentcloud",
|
||||
TODAYNIC: "todaynic",
|
||||
UCLOUD: "ucloud",
|
||||
UNICLOUD: "unicloud",
|
||||
VERCEL: "vercel",
|
||||
VOLCENGINE: "volcengine",
|
||||
VULTR: "vultr",
|
||||
WEBHOOK: "webhook",
|
||||
WECOMBOT: "wecombot",
|
||||
WESTCN: "westcn",
|
||||
XINNET: "xinnet",
|
||||
YANDEXCLOUD: "yandexcloud",
|
||||
ZEROSSL: "zerossl",
|
||||
} as const);
|
||||
export const ACCESS_PROVIDERS = Object.freeze(
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
{
|
||||
["1PANEL"]: "1panel",
|
||||
["35CN"]: "35cn",
|
||||
["51DNSCOM"]: "51dnscom",
|
||||
ACMECA: "acmeca",
|
||||
ACMEDNS: "acmedns",
|
||||
ACMEHTTPREQ: "acmehttpreq",
|
||||
ACTALISSSL: "actalisssl",
|
||||
AKAMAI: "akamai",
|
||||
ALIYUN: "aliyun",
|
||||
APISIX: "apisix",
|
||||
ARVANCLOUD: "arvancloud",
|
||||
ASIAISPCDN: "asiaispcdn",
|
||||
AWS: "aws",
|
||||
AXISNOW: "axisnow",
|
||||
AZURE: "azure",
|
||||
BAIDUCLOUD: "baiducloud",
|
||||
BAOTAPANEL: "baotapanel",
|
||||
BAOTAPANELGO: "baotapanelgo",
|
||||
BAOTAWAF: "baotawaf",
|
||||
BEGET: "beget",
|
||||
BOOKMYNAME: "bookmyname",
|
||||
BUNNY: "bunny",
|
||||
BYTEPLUS: "byteplus",
|
||||
CACHEFLY: "cachefly",
|
||||
CDNFLY: "cdnfly",
|
||||
CLOUDFLARE: "cloudflare",
|
||||
CLOUDNS: "cloudns",
|
||||
CMCCCLOUD: "cmcccloud",
|
||||
CONOHAVPS: "conohavps",
|
||||
CONSTELLIX: "constellix",
|
||||
CPANEL: "cpanel",
|
||||
CTCCCLOUD: "ctcccloud",
|
||||
DESEC: "desec",
|
||||
DIGICERT: "digicert",
|
||||
DIGITALOCEAN: "digitalocean",
|
||||
DINGTALKBOT: "dingtalkbot",
|
||||
DISCORDBOT: "discordbot",
|
||||
DNSEXIT: "dnsexit",
|
||||
DNSLA: "dnsla",
|
||||
DNSMADEEASY: "dnsmadeeasy",
|
||||
DOKPLOY: "dokploy",
|
||||
DUCKDNS: "duckdns",
|
||||
DYNADOT: "dynadot",
|
||||
DYNU: "dynu",
|
||||
DYNV6: "dynv6",
|
||||
EMAIL: "email",
|
||||
FLEXCDN: "flexcdn",
|
||||
FLYIO: "flyio",
|
||||
FTP: "ftp",
|
||||
GANDINET: "gandinet",
|
||||
GCORE: "gcore",
|
||||
GLOBALSIGNATLAS: "globalsignatlas",
|
||||
GNAME: "gname",
|
||||
GODADDY: "godaddy",
|
||||
GOEDGE: "goedge",
|
||||
GOOGLECLOUD: "googlecloud",
|
||||
GOOGLETRUSTSERVICES: "googletrustservices",
|
||||
HETZNER: "hetzner",
|
||||
HOSTINGDE: "hostingde",
|
||||
HOSTINGER: "hostinger",
|
||||
HUAWEICLOUD: "huaweicloud",
|
||||
HUAWEIIBMC: "huaweiibmc",
|
||||
INFOMANIAK: "infomaniak",
|
||||
IONOS: "ionos",
|
||||
JDCLOUD: "jdcloud",
|
||||
KONG: "kong",
|
||||
LARKBOT: "larkbot",
|
||||
LECDN: "lecdn",
|
||||
LETSENCRYPT: "letsencrypt",
|
||||
LETSENCRYPTSTAGING: "letsencryptstaging",
|
||||
LINODE: "linode",
|
||||
LITESSL: "litessl",
|
||||
LOCAL: "local",
|
||||
MATRIX: "matrix",
|
||||
MATTERMOST: "mattermost",
|
||||
MOHUA: "mohua",
|
||||
NAMECHEAP: "namecheap",
|
||||
NAMEDOTCOM: "namedotcom",
|
||||
NAMESILO: "namesilo",
|
||||
NETCUP: "netcup",
|
||||
NETLIFY: "netlify",
|
||||
NGINXPROXYMANAGER: "nginxproxymanager",
|
||||
NS1: "ns1",
|
||||
ORACLECLOUD: "oraclecloud",
|
||||
OVHCLOUD: "ovhcloud",
|
||||
PORKBUN: "porkbun",
|
||||
POWERDNS: "powerdns",
|
||||
PROXMOXBS: "proxmoxbs",
|
||||
PROXMOXVE: "proxmoxve",
|
||||
QINGCLOUD: "qingcloud",
|
||||
RAINYUN: "rainyun",
|
||||
RATPANEL: "ratpanel",
|
||||
REGRU: "regru",
|
||||
RFC2136: "rfc2136",
|
||||
RUCENTER: "rucenter",
|
||||
S3: "s3",
|
||||
SAFELINE: "safeline",
|
||||
SAMWAF: "samwaf",
|
||||
SECTIGO: "sectigo",
|
||||
SIMPLYCOM: "simplycom",
|
||||
SLACKBOT: "slackbot",
|
||||
SPACESHIP: "spaceship",
|
||||
SSH: "ssh",
|
||||
SSLCOM: "sslcom",
|
||||
SYNOLOGYDSM: "synologydsm",
|
||||
TECHNITIUMDNS: "technitiumdns",
|
||||
TELEGRAMBOT: "telegrambot",
|
||||
TENCENTCLOUD: "tencentcloud",
|
||||
TODAYNIC: "todaynic",
|
||||
UCLOUD: "ucloud",
|
||||
UNICLOUD: "unicloud",
|
||||
VERCEL: "vercel",
|
||||
VOLCENGINE: "volcengine",
|
||||
VULTR: "vultr",
|
||||
WEBHOOK: "webhook",
|
||||
WECOMBOT: "wecombot",
|
||||
WESTCN: "westcn",
|
||||
XINNET: "xinnet",
|
||||
YANDEXCLOUD: "yandexcloud",
|
||||
ZEROSSL: "zerossl",
|
||||
} as const
|
||||
);
|
||||
|
||||
export type AccessProviderType = (typeof ACCESS_PROVIDERS)[keyof typeof ACCESS_PROVIDERS];
|
||||
|
||||
@@ -207,6 +210,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.BAOTAPANELGO, "provider.baotapanelgo", "/imgs/providers/baota.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BAOTAWAF, "provider.baotawaf", "/imgs/providers/baota.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.DOKPLOY, "provider.dokploy", "/imgs/providers/dokploy.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.ASIAISPCDN, "provider.asiaispcdn", "/imgs/providers/asiaispcdn.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.AXISNOW, "provider.axisnow", "/imgs/providers/axisnow.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.CDNFLY, "provider.cdnfly", "/imgs/providers/cdnfly.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.GOEDGE, "provider.goedge", "/imgs/providers/goedge.png", [ACCESS_USAGES.HOSTING]],
|
||||
@@ -603,155 +607,159 @@ export const acmeHttp01ProvidersMap: Map<ACMEHttp01Provider["type"] | string, AC
|
||||
// #endregion
|
||||
|
||||
// #region DeploymentProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const DEPLOYMENT_PROVIDERS = Object.freeze({
|
||||
["1PANEL"]: `${ACCESS_PROVIDERS["1PANEL"]}`,
|
||||
["1PANEL_CONSOLE"]: `${ACCESS_PROVIDERS["1PANEL"]}-console`,
|
||||
ALIYUN_ALB: `${ACCESS_PROVIDERS.ALIYUN}-alb`,
|
||||
ALIYUN_APIGW: `${ACCESS_PROVIDERS.ALIYUN}-apigw`,
|
||||
ALIYUN_CAS: `${ACCESS_PROVIDERS.ALIYUN}-cas`,
|
||||
ALIYUN_CAS_DEPLOY: `${ACCESS_PROVIDERS.ALIYUN}-casdeploy`,
|
||||
ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`,
|
||||
ALIYUN_CLB: `${ACCESS_PROVIDERS.ALIYUN}-clb`,
|
||||
ALIYUN_DCDN: `${ACCESS_PROVIDERS.ALIYUN}-dcdn`,
|
||||
ALIYUN_DDOSPRO: `${ACCESS_PROVIDERS.ALIYUN}-ddospro`,
|
||||
ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`,
|
||||
ALIYUN_ESA_SAAS: `${ACCESS_PROVIDERS.ALIYUN}-esasaas`,
|
||||
ALIYUN_FC: `${ACCESS_PROVIDERS.ALIYUN}-fc`,
|
||||
ALIYUN_GA: `${ACCESS_PROVIDERS.ALIYUN}-ga`,
|
||||
ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`,
|
||||
ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`,
|
||||
ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`,
|
||||
ALIYUN_VOD: `${ACCESS_PROVIDERS.ALIYUN}-vod`,
|
||||
ALIYUN_WAF: `${ACCESS_PROVIDERS.ALIYUN}-waf`,
|
||||
APISIX: `${ACCESS_PROVIDERS.APISIX}`,
|
||||
AWS_ACM: `${ACCESS_PROVIDERS.AWS}-acm`,
|
||||
AWS_ALB: `${ACCESS_PROVIDERS.AWS}-alb`,
|
||||
AWS_AMPLIFY: `${ACCESS_PROVIDERS.AWS}-amplify`,
|
||||
AWS_APIGATEWAY: `${ACCESS_PROVIDERS.AWS}-apigateway`,
|
||||
AWS_CLB: `${ACCESS_PROVIDERS.AWS}-clb`,
|
||||
AWS_CLOUDFRONT: `${ACCESS_PROVIDERS.AWS}-cloudfront`,
|
||||
AWS_IAM: `${ACCESS_PROVIDERS.AWS}-iam`,
|
||||
AWS_NLB: `${ACCESS_PROVIDERS.AWS}-nlb`,
|
||||
AXISNOW: `${ACCESS_PROVIDERS.AXISNOW}`,
|
||||
AZURE_KEYVAULT: `${ACCESS_PROVIDERS.AZURE}-keyvault`,
|
||||
BAIDUCLOUD_APPBLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-appblb`,
|
||||
BAIDUCLOUD_BLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-blb`,
|
||||
BAIDUCLOUD_CDN: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cdn`,
|
||||
BAIDUCLOUD_CERT: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cert`,
|
||||
BAOTAPANEL: `${ACCESS_PROVIDERS.BAOTAPANEL}`,
|
||||
BAOTAPANEL_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANEL}-console`,
|
||||
BAOTAPANELGO: `${ACCESS_PROVIDERS.BAOTAPANELGO}`,
|
||||
BAOTAPANELGO_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANELGO}-console`,
|
||||
BAOTAWAF: `${ACCESS_PROVIDERS.BAOTAWAF}`,
|
||||
BAOTAWAF_CONSOLE: `${ACCESS_PROVIDERS.BAOTAWAF}-console`,
|
||||
BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`,
|
||||
BYTEPLUS_ALB: `${ACCESS_PROVIDERS.BYTEPLUS}-alb`,
|
||||
BYTEPLUS_APIG: `${ACCESS_PROVIDERS.BYTEPLUS}-apig`,
|
||||
BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`,
|
||||
BYTEPLUS_CERTCENTER: `${ACCESS_PROVIDERS.BYTEPLUS}-certcenter`,
|
||||
BYTEPLUS_CLB: `${ACCESS_PROVIDERS.BYTEPLUS}-clb`,
|
||||
BYTEPLUS_MEDIALIVE: `${ACCESS_PROVIDERS.BYTEPLUS}-medialive`,
|
||||
BYTEPLUS_TOS: `${ACCESS_PROVIDERS.BYTEPLUS}-tos`,
|
||||
CACHEFLY: `${ACCESS_PROVIDERS.CACHEFLY}`,
|
||||
CDNFLY: `${ACCESS_PROVIDERS.CDNFLY}`,
|
||||
CLOUDFLARE_SSL: `${ACCESS_PROVIDERS.CLOUDFLARE}-ssl`,
|
||||
CMCCCLOUD_CDN: `${ACCESS_PROVIDERS.CMCCCLOUD}-cdn`,
|
||||
CMCCCLOUD_VLB: `${ACCESS_PROVIDERS.CMCCCLOUD}-vlb`,
|
||||
CPANEL: `${ACCESS_PROVIDERS.CPANEL}`,
|
||||
CTCCCLOUD_AO: `${ACCESS_PROVIDERS.CTCCCLOUD}-ao`,
|
||||
CTCCCLOUD_CDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-cdn`,
|
||||
CTCCCLOUD_CMS: `${ACCESS_PROVIDERS.CTCCCLOUD}-cms`,
|
||||
CTCCCLOUD_ELB: `${ACCESS_PROVIDERS.CTCCCLOUD}-elb`,
|
||||
CTCCCLOUD_FAAS: `${ACCESS_PROVIDERS.CTCCCLOUD}-faas`,
|
||||
CTCCCLOUD_ICDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-icdn`,
|
||||
CTCCCLOUD_LVDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-lvdn`,
|
||||
DIGITALOCEAN_CERTIFICATE: `${ACCESS_PROVIDERS.DIGITALOCEAN}-certificate`,
|
||||
DOKPLOY: `${ACCESS_PROVIDERS.DOKPLOY}`,
|
||||
FLEXCDN: `${ACCESS_PROVIDERS.FLEXCDN}`,
|
||||
FLYIO: `${ACCESS_PROVIDERS.FLYIO}`,
|
||||
FTP: `${ACCESS_PROVIDERS.FTP}`,
|
||||
GCORE_CDN: `${ACCESS_PROVIDERS.GCORE}-cdn`,
|
||||
GOEDGE: `${ACCESS_PROVIDERS.GOEDGE}`,
|
||||
GOOGLECLOUD_CERTIFICATEMANAGER: `${ACCESS_PROVIDERS.GOOGLECLOUD}-certificatemanager`,
|
||||
HUAWEICLOUD_APIG: `${ACCESS_PROVIDERS.HUAWEICLOUD}-apig`,
|
||||
HUAWEICLOUD_AAD: `${ACCESS_PROVIDERS.HUAWEICLOUD}-aad`,
|
||||
HUAWEICLOUD_CDN: `${ACCESS_PROVIDERS.HUAWEICLOUD}-cdn`,
|
||||
HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`,
|
||||
HUAWEICLOUD_LIVE: `${ACCESS_PROVIDERS.HUAWEICLOUD}-live`,
|
||||
HUAWEICLOUD_OBS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-obs`,
|
||||
HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`,
|
||||
HUAWEICLOUD_VOD: `${ACCESS_PROVIDERS.HUAWEICLOUD}-vod`,
|
||||
HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`,
|
||||
HUAWEIIBMC: `${ACCESS_PROVIDERS.HUAWEIIBMC}`,
|
||||
JDCLOUD_ALB: `${ACCESS_PROVIDERS.JDCLOUD}-alb`,
|
||||
JDCLOUD_CDN: `${ACCESS_PROVIDERS.JDCLOUD}-cdn`,
|
||||
JDCLOUD_LIVE: `${ACCESS_PROVIDERS.JDCLOUD}-live`,
|
||||
JDCLOUD_VOD: `${ACCESS_PROVIDERS.JDCLOUD}-vod`,
|
||||
JDCLOUD_SSL: `${ACCESS_PROVIDERS.JDCLOUD}-ssl`,
|
||||
JDCLOUD_WAF: `${ACCESS_PROVIDERS.JDCLOUD}-waf`,
|
||||
KONG: `${ACCESS_PROVIDERS.KONG}`,
|
||||
LECDN: `${ACCESS_PROVIDERS.LECDN}`,
|
||||
LINODE_LOS: `${ACCESS_PROVIDERS.LINODE}-los`,
|
||||
LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
|
||||
MOHUA_MVH: `${ACCESS_PROVIDERS.MOHUA}-mvh`,
|
||||
NETLIFY: `${ACCESS_PROVIDERS.NETLIFY}`,
|
||||
NGINXPROXYMANAGER: `${ACCESS_PROVIDERS.NGINXPROXYMANAGER}`,
|
||||
ORACLECLOUD_CERTIFICATESMGMT: `${ACCESS_PROVIDERS.ORACLECLOUD}-certificatesmgmt`,
|
||||
PROXMOXBS: `${ACCESS_PROVIDERS.PROXMOXBS}`,
|
||||
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
|
||||
QINGCLOUD_LB: `${ACCESS_PROVIDERS.QINGCLOUD}-lb`,
|
||||
RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`,
|
||||
RAINYUN_SSLCENTER: `${ACCESS_PROVIDERS.RAINYUN}-sslcenter`,
|
||||
RATPANEL: `${ACCESS_PROVIDERS.RATPANEL}`,
|
||||
RATPANEL_CONSOLE: `${ACCESS_PROVIDERS.RATPANEL}-console`,
|
||||
S3: `${ACCESS_PROVIDERS.S3}`,
|
||||
SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
|
||||
SAMWAF: `${ACCESS_PROVIDERS.SAMWAF}`,
|
||||
SAMWAF_CONSOLE: `${ACCESS_PROVIDERS.SAMWAF}-console`,
|
||||
SSH: `${ACCESS_PROVIDERS.SSH}`,
|
||||
SYNOLOGYDSM: `${ACCESS_PROVIDERS.SYNOLOGYDSM}`,
|
||||
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
|
||||
TENCENTCLOUD_CLB: `${ACCESS_PROVIDERS.TENCENTCLOUD}-clb`,
|
||||
TENCENTCLOUD_COS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cos`,
|
||||
TENCENTCLOUD_CSS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-css`,
|
||||
TENCENTCLOUD_ECDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ecdn`,
|
||||
TENCENTCLOUD_EO: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eo`,
|
||||
TENCENTCLOUD_EOMAKERS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eomakers`,
|
||||
TENCENTCLOUD_GAAP: `${ACCESS_PROVIDERS.TENCENTCLOUD}-gaap`,
|
||||
TENCENTCLOUD_GA2: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ga2`,
|
||||
TENCENTCLOUD_SCF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-scf`,
|
||||
TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`,
|
||||
TENCENTCLOUD_SSL_DEPLOY: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssldeploy`,
|
||||
TENCENTCLOUD_SSL_UPDATE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-sslupdate`,
|
||||
TENCENTCLOUD_TSE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-tse`,
|
||||
TENCENTCLOUD_VOD: `${ACCESS_PROVIDERS.TENCENTCLOUD}-vod`,
|
||||
TENCENTCLOUD_WAF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-waf`,
|
||||
UCLOUD_UALB: `${ACCESS_PROVIDERS.UCLOUD}-ualb`,
|
||||
UCLOUD_UCDN: `${ACCESS_PROVIDERS.UCLOUD}-ucdn`,
|
||||
UCLOUD_UCLB: `${ACCESS_PROVIDERS.UCLOUD}-uclb`,
|
||||
UCLOUD_UEWAF: `${ACCESS_PROVIDERS.UCLOUD}-uewaf`,
|
||||
UCLOUD_UPATHX: `${ACCESS_PROVIDERS.UCLOUD}-upathx`,
|
||||
UCLOUD_US3: `${ACCESS_PROVIDERS.UCLOUD}-us3`,
|
||||
UNICLOUD_WEBHOST: `${ACCESS_PROVIDERS.UNICLOUD}-webhost`,
|
||||
VERCEL: `${ACCESS_PROVIDERS.VERCEL}`,
|
||||
VOLCENGINE_ALB: `${ACCESS_PROVIDERS.VOLCENGINE}-alb`,
|
||||
VOLCENGINE_APIG: `${ACCESS_PROVIDERS.VOLCENGINE}-apig`,
|
||||
VOLCENGINE_CDN: `${ACCESS_PROVIDERS.VOLCENGINE}-cdn`,
|
||||
VOLCENGINE_CERTCENTER: `${ACCESS_PROVIDERS.VOLCENGINE}-certcenter`,
|
||||
VOLCENGINE_CLB: `${ACCESS_PROVIDERS.VOLCENGINE}-clb`,
|
||||
VOLCENGINE_DCDN: `${ACCESS_PROVIDERS.VOLCENGINE}-dcdn`,
|
||||
VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`,
|
||||
VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`,
|
||||
VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`,
|
||||
VOLCENGINE_VOD: `${ACCESS_PROVIDERS.VOLCENGINE}-vod`,
|
||||
VOLCENGINE_WAF: `${ACCESS_PROVIDERS.VOLCENGINE}-waf`,
|
||||
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
|
||||
YANDEXCLOUD_CERTIFICATEMANAGER: `${ACCESS_PROVIDERS.YANDEXCLOUD}-certificatemanager`,
|
||||
} as const);
|
||||
export const DEPLOYMENT_PROVIDERS = Object.freeze(
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
{
|
||||
["1PANEL"]: `${ACCESS_PROVIDERS["1PANEL"]}`,
|
||||
["1PANEL_CONSOLE"]: `${ACCESS_PROVIDERS["1PANEL"]}-console`,
|
||||
ALIYUN_ALB: `${ACCESS_PROVIDERS.ALIYUN}-alb`,
|
||||
ALIYUN_APIGW: `${ACCESS_PROVIDERS.ALIYUN}-apigw`,
|
||||
ALIYUN_CAS: `${ACCESS_PROVIDERS.ALIYUN}-cas`,
|
||||
ALIYUN_CAS_DEPLOY: `${ACCESS_PROVIDERS.ALIYUN}-casdeploy`,
|
||||
ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`,
|
||||
ALIYUN_CLB: `${ACCESS_PROVIDERS.ALIYUN}-clb`,
|
||||
ALIYUN_DCDN: `${ACCESS_PROVIDERS.ALIYUN}-dcdn`,
|
||||
ALIYUN_DDOSPRO: `${ACCESS_PROVIDERS.ALIYUN}-ddospro`,
|
||||
ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`,
|
||||
ALIYUN_ESA_SAAS: `${ACCESS_PROVIDERS.ALIYUN}-esasaas`,
|
||||
ALIYUN_FC: `${ACCESS_PROVIDERS.ALIYUN}-fc`,
|
||||
ALIYUN_GA: `${ACCESS_PROVIDERS.ALIYUN}-ga`,
|
||||
ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`,
|
||||
ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`,
|
||||
ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`,
|
||||
ALIYUN_VOD: `${ACCESS_PROVIDERS.ALIYUN}-vod`,
|
||||
ALIYUN_WAF: `${ACCESS_PROVIDERS.ALIYUN}-waf`,
|
||||
APISIX: `${ACCESS_PROVIDERS.APISIX}`,
|
||||
ASIAISPCDN: `${ACCESS_PROVIDERS.ASIAISPCDN}`,
|
||||
AWS_ACM: `${ACCESS_PROVIDERS.AWS}-acm`,
|
||||
AWS_ALB: `${ACCESS_PROVIDERS.AWS}-alb`,
|
||||
AWS_AMPLIFY: `${ACCESS_PROVIDERS.AWS}-amplify`,
|
||||
AWS_APIGATEWAY: `${ACCESS_PROVIDERS.AWS}-apigateway`,
|
||||
AWS_CLB: `${ACCESS_PROVIDERS.AWS}-clb`,
|
||||
AWS_CLOUDFRONT: `${ACCESS_PROVIDERS.AWS}-cloudfront`,
|
||||
AWS_IAM: `${ACCESS_PROVIDERS.AWS}-iam`,
|
||||
AWS_NLB: `${ACCESS_PROVIDERS.AWS}-nlb`,
|
||||
AXISNOW: `${ACCESS_PROVIDERS.AXISNOW}`,
|
||||
AZURE_KEYVAULT: `${ACCESS_PROVIDERS.AZURE}-keyvault`,
|
||||
BAIDUCLOUD_APPBLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-appblb`,
|
||||
BAIDUCLOUD_BLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-blb`,
|
||||
BAIDUCLOUD_CDN: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cdn`,
|
||||
BAIDUCLOUD_CERT: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cert`,
|
||||
BAOTAPANEL: `${ACCESS_PROVIDERS.BAOTAPANEL}`,
|
||||
BAOTAPANEL_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANEL}-console`,
|
||||
BAOTAPANELGO: `${ACCESS_PROVIDERS.BAOTAPANELGO}`,
|
||||
BAOTAPANELGO_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANELGO}-console`,
|
||||
BAOTAWAF: `${ACCESS_PROVIDERS.BAOTAWAF}`,
|
||||
BAOTAWAF_CONSOLE: `${ACCESS_PROVIDERS.BAOTAWAF}-console`,
|
||||
BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`,
|
||||
BYTEPLUS_ALB: `${ACCESS_PROVIDERS.BYTEPLUS}-alb`,
|
||||
BYTEPLUS_APIG: `${ACCESS_PROVIDERS.BYTEPLUS}-apig`,
|
||||
BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`,
|
||||
BYTEPLUS_CERTCENTER: `${ACCESS_PROVIDERS.BYTEPLUS}-certcenter`,
|
||||
BYTEPLUS_CLB: `${ACCESS_PROVIDERS.BYTEPLUS}-clb`,
|
||||
BYTEPLUS_MEDIALIVE: `${ACCESS_PROVIDERS.BYTEPLUS}-medialive`,
|
||||
BYTEPLUS_TOS: `${ACCESS_PROVIDERS.BYTEPLUS}-tos`,
|
||||
CACHEFLY: `${ACCESS_PROVIDERS.CACHEFLY}`,
|
||||
CDNFLY: `${ACCESS_PROVIDERS.CDNFLY}`,
|
||||
CLOUDFLARE_SSL: `${ACCESS_PROVIDERS.CLOUDFLARE}-ssl`,
|
||||
CMCCCLOUD_CDN: `${ACCESS_PROVIDERS.CMCCCLOUD}-cdn`,
|
||||
CMCCCLOUD_VLB: `${ACCESS_PROVIDERS.CMCCCLOUD}-vlb`,
|
||||
CPANEL: `${ACCESS_PROVIDERS.CPANEL}`,
|
||||
CTCCCLOUD_AO: `${ACCESS_PROVIDERS.CTCCCLOUD}-ao`,
|
||||
CTCCCLOUD_CDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-cdn`,
|
||||
CTCCCLOUD_CMS: `${ACCESS_PROVIDERS.CTCCCLOUD}-cms`,
|
||||
CTCCCLOUD_ELB: `${ACCESS_PROVIDERS.CTCCCLOUD}-elb`,
|
||||
CTCCCLOUD_FAAS: `${ACCESS_PROVIDERS.CTCCCLOUD}-faas`,
|
||||
CTCCCLOUD_ICDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-icdn`,
|
||||
CTCCCLOUD_LVDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-lvdn`,
|
||||
DIGITALOCEAN_CERTIFICATE: `${ACCESS_PROVIDERS.DIGITALOCEAN}-certificate`,
|
||||
DOKPLOY: `${ACCESS_PROVIDERS.DOKPLOY}`,
|
||||
FLEXCDN: `${ACCESS_PROVIDERS.FLEXCDN}`,
|
||||
FLYIO: `${ACCESS_PROVIDERS.FLYIO}`,
|
||||
FTP: `${ACCESS_PROVIDERS.FTP}`,
|
||||
GCORE_CDN: `${ACCESS_PROVIDERS.GCORE}-cdn`,
|
||||
GOEDGE: `${ACCESS_PROVIDERS.GOEDGE}`,
|
||||
GOOGLECLOUD_CERTIFICATEMANAGER: `${ACCESS_PROVIDERS.GOOGLECLOUD}-certificatemanager`,
|
||||
HUAWEICLOUD_APIG: `${ACCESS_PROVIDERS.HUAWEICLOUD}-apig`,
|
||||
HUAWEICLOUD_AAD: `${ACCESS_PROVIDERS.HUAWEICLOUD}-aad`,
|
||||
HUAWEICLOUD_CDN: `${ACCESS_PROVIDERS.HUAWEICLOUD}-cdn`,
|
||||
HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`,
|
||||
HUAWEICLOUD_LIVE: `${ACCESS_PROVIDERS.HUAWEICLOUD}-live`,
|
||||
HUAWEICLOUD_OBS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-obs`,
|
||||
HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`,
|
||||
HUAWEICLOUD_VOD: `${ACCESS_PROVIDERS.HUAWEICLOUD}-vod`,
|
||||
HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`,
|
||||
HUAWEIIBMC: `${ACCESS_PROVIDERS.HUAWEIIBMC}`,
|
||||
JDCLOUD_ALB: `${ACCESS_PROVIDERS.JDCLOUD}-alb`,
|
||||
JDCLOUD_CDN: `${ACCESS_PROVIDERS.JDCLOUD}-cdn`,
|
||||
JDCLOUD_LIVE: `${ACCESS_PROVIDERS.JDCLOUD}-live`,
|
||||
JDCLOUD_VOD: `${ACCESS_PROVIDERS.JDCLOUD}-vod`,
|
||||
JDCLOUD_SSL: `${ACCESS_PROVIDERS.JDCLOUD}-ssl`,
|
||||
JDCLOUD_WAF: `${ACCESS_PROVIDERS.JDCLOUD}-waf`,
|
||||
KONG: `${ACCESS_PROVIDERS.KONG}`,
|
||||
LECDN: `${ACCESS_PROVIDERS.LECDN}`,
|
||||
LINODE_LOS: `${ACCESS_PROVIDERS.LINODE}-los`,
|
||||
LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
|
||||
MOHUA_MVH: `${ACCESS_PROVIDERS.MOHUA}-mvh`,
|
||||
NETLIFY: `${ACCESS_PROVIDERS.NETLIFY}`,
|
||||
NGINXPROXYMANAGER: `${ACCESS_PROVIDERS.NGINXPROXYMANAGER}`,
|
||||
ORACLECLOUD_CERTIFICATESMGMT: `${ACCESS_PROVIDERS.ORACLECLOUD}-certificatesmgmt`,
|
||||
PROXMOXBS: `${ACCESS_PROVIDERS.PROXMOXBS}`,
|
||||
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
|
||||
QINGCLOUD_LB: `${ACCESS_PROVIDERS.QINGCLOUD}-lb`,
|
||||
RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`,
|
||||
RAINYUN_SSLCENTER: `${ACCESS_PROVIDERS.RAINYUN}-sslcenter`,
|
||||
RATPANEL: `${ACCESS_PROVIDERS.RATPANEL}`,
|
||||
RATPANEL_CONSOLE: `${ACCESS_PROVIDERS.RATPANEL}-console`,
|
||||
S3: `${ACCESS_PROVIDERS.S3}`,
|
||||
SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
|
||||
SAMWAF: `${ACCESS_PROVIDERS.SAMWAF}`,
|
||||
SAMWAF_CONSOLE: `${ACCESS_PROVIDERS.SAMWAF}-console`,
|
||||
SSH: `${ACCESS_PROVIDERS.SSH}`,
|
||||
SYNOLOGYDSM: `${ACCESS_PROVIDERS.SYNOLOGYDSM}`,
|
||||
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
|
||||
TENCENTCLOUD_CLB: `${ACCESS_PROVIDERS.TENCENTCLOUD}-clb`,
|
||||
TENCENTCLOUD_COS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cos`,
|
||||
TENCENTCLOUD_CSS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-css`,
|
||||
TENCENTCLOUD_DDOSPRO: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ddospro`,
|
||||
TENCENTCLOUD_ECDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ecdn`,
|
||||
TENCENTCLOUD_EO: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eo`,
|
||||
TENCENTCLOUD_EOMAKERS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eomakers`,
|
||||
TENCENTCLOUD_GAAP: `${ACCESS_PROVIDERS.TENCENTCLOUD}-gaap`,
|
||||
TENCENTCLOUD_GA2: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ga2`,
|
||||
TENCENTCLOUD_SCF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-scf`,
|
||||
TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`,
|
||||
TENCENTCLOUD_SSL_DEPLOY: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssldeploy`,
|
||||
TENCENTCLOUD_SSL_UPDATE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-sslupdate`,
|
||||
TENCENTCLOUD_TSE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-tse`,
|
||||
TENCENTCLOUD_VOD: `${ACCESS_PROVIDERS.TENCENTCLOUD}-vod`,
|
||||
TENCENTCLOUD_WAF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-waf`,
|
||||
UCLOUD_UALB: `${ACCESS_PROVIDERS.UCLOUD}-ualb`,
|
||||
UCLOUD_UCDN: `${ACCESS_PROVIDERS.UCLOUD}-ucdn`,
|
||||
UCLOUD_UCLB: `${ACCESS_PROVIDERS.UCLOUD}-uclb`,
|
||||
UCLOUD_UEWAF: `${ACCESS_PROVIDERS.UCLOUD}-uewaf`,
|
||||
UCLOUD_UPATHX: `${ACCESS_PROVIDERS.UCLOUD}-upathx`,
|
||||
UCLOUD_US3: `${ACCESS_PROVIDERS.UCLOUD}-us3`,
|
||||
UNICLOUD_WEBHOST: `${ACCESS_PROVIDERS.UNICLOUD}-webhost`,
|
||||
VERCEL: `${ACCESS_PROVIDERS.VERCEL}`,
|
||||
VOLCENGINE_ALB: `${ACCESS_PROVIDERS.VOLCENGINE}-alb`,
|
||||
VOLCENGINE_APIG: `${ACCESS_PROVIDERS.VOLCENGINE}-apig`,
|
||||
VOLCENGINE_CDN: `${ACCESS_PROVIDERS.VOLCENGINE}-cdn`,
|
||||
VOLCENGINE_CERTCENTER: `${ACCESS_PROVIDERS.VOLCENGINE}-certcenter`,
|
||||
VOLCENGINE_CLB: `${ACCESS_PROVIDERS.VOLCENGINE}-clb`,
|
||||
VOLCENGINE_DCDN: `${ACCESS_PROVIDERS.VOLCENGINE}-dcdn`,
|
||||
VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`,
|
||||
VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`,
|
||||
VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`,
|
||||
VOLCENGINE_VOD: `${ACCESS_PROVIDERS.VOLCENGINE}-vod`,
|
||||
VOLCENGINE_WAF: `${ACCESS_PROVIDERS.VOLCENGINE}-waf`,
|
||||
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
|
||||
YANDEXCLOUD_CERTIFICATEMANAGER: `${ACCESS_PROVIDERS.YANDEXCLOUD}-certificatemanager`,
|
||||
} as const
|
||||
);
|
||||
|
||||
export type DeploymentProviderType = (typeof DEPLOYMENT_PROVIDERS)[keyof typeof DEPLOYMENT_PROVIDERS];
|
||||
|
||||
@@ -812,6 +820,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EOMAKERS, "provider.tencentcloud_eomakers", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CLB, "provider.tencentcloud_clb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_WAF, "provider.tencentcloud_waf", DEPLOYMENT_CATEGORIES.FIREWALL],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_DDOSPRO, "provider.tencentcloud_ddospro", DEPLOYMENT_CATEGORIES.FIREWALL],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud_css", DEPLOYMENT_CATEGORIES.AV],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_VOD, "provider.tencentcloud_vod", DEPLOYMENT_CATEGORIES.AV],
|
||||
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SCF, "provider.tencentcloud_scf", DEPLOYMENT_CATEGORIES.SERVERLESS],
|
||||
@@ -899,6 +908,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
|
||||
[DEPLOYMENT_PROVIDERS.RAINYUN_SSLCENTER, "provider.rainyun_sslcenter_upload", DEPLOYMENT_CATEGORIES.SSL],
|
||||
[DEPLOYMENT_PROVIDERS.UNICLOUD_WEBHOST, "provider.unicloud_webhost", DEPLOYMENT_CATEGORIES.WEBSITE],
|
||||
[DEPLOYMENT_PROVIDERS.MOHUA_MVH, "provider.mohua_mvh", DEPLOYMENT_CATEGORIES.WEBSITE],
|
||||
[DEPLOYMENT_PROVIDERS.ASIAISPCDN, "provider.asiaispcdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.AXISNOW, "provider.axisnow", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
|
||||
[DEPLOYMENT_PROVIDERS.FLEXCDN, "provider.flexcdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||
|
||||
@@ -220,6 +220,14 @@
|
||||
"placeholder": "Please enter ArvanCloud API key",
|
||||
"tooltip": "For more information, see <a href=\"https://docs.arvancloud.ir/en/developer-tools/api/api-key\" target=\"_blank\">https://docs.arvancloud.ir/en/developer-tools/api/api-key</a>"
|
||||
},
|
||||
"asiaispcdn_access_key_id": {
|
||||
"label": "Asia-ISP CDN AccessKeyID",
|
||||
"placeholder": "Please enter Asia-ISP CDN AccessKeyID"
|
||||
},
|
||||
"asiaispcdn_access_key_secret": {
|
||||
"label": "Asia-ISP CDN AccessKeySecret",
|
||||
"placeholder": "Please enter Asia-ISP CDN AccessKeySecret"
|
||||
},
|
||||
"aws_auth_method": {
|
||||
"label": "AWS API authentication method",
|
||||
"placeholder": "Please select AWS API authentication method",
|
||||
@@ -879,6 +887,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lecdn_auth_method": {
|
||||
"label": "LeCDN API authentication method",
|
||||
"placeholder": "Please select LeCDN API authentication method",
|
||||
"option": {
|
||||
"password": {
|
||||
"label": "Username & Password"
|
||||
},
|
||||
"apikey": {
|
||||
"label": "API key"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lecdn_username": {
|
||||
"label": "LeCDN username",
|
||||
"placeholder": "Please enter LeCDN username"
|
||||
@@ -887,6 +907,10 @@
|
||||
"label": "LeCDN password",
|
||||
"placeholder": "Please enter LeCDN password"
|
||||
},
|
||||
"lecdn_api_key": {
|
||||
"label": "LeCDN API key",
|
||||
"placeholder": "Please enter LeCDN API key"
|
||||
},
|
||||
"linode_access_token": {
|
||||
"label": "Linode access token",
|
||||
"placeholder": "Please enter Linode access token",
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"aliyun_cdn": "Alibaba Cloud - CDN (Content Delivery Network)",
|
||||
"aliyun_clb": "Alibaba Cloud - CLB (Classic Load Balancer)",
|
||||
"aliyun_dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)",
|
||||
"aliyun_ddospro": "Alibaba Cloud - Anti-DDoS Proxy",
|
||||
"aliyun_ddospro": "Alibaba Cloud - Anti-DDoS Pro",
|
||||
"aliyun_dns": "Alibaba Cloud - DNS",
|
||||
"aliyun_esa": "Alibaba Cloud - ESA (Edge Security Acceleration)",
|
||||
"aliyun_esa_saas": "Alibaba Cloud - ESA SaaS Manager (Edge Security Acceleration SaaS)",
|
||||
@@ -67,6 +67,7 @@
|
||||
"aliyun_waf": "Alibaba Cloud - WAF (Web Application Firewall)",
|
||||
"apisix": "Apache APISIX",
|
||||
"arvancloud": "ArvanCloud",
|
||||
"asiaispcdn": "Asia-ISP CDN",
|
||||
"aws": "AWS (Amazon Web Services)",
|
||||
"aws_acm": "AWS - ACM (Amazon Certificate Manager)",
|
||||
"aws_alb": "AWS - ALB (Application Load Balancer)",
|
||||
@@ -249,6 +250,7 @@
|
||||
"tencentcloud_clb": "Tencent Cloud - CLB (Cloud Load Balancer)",
|
||||
"tencentcloud_cos": "Tencent Cloud - COS (Cloud Object Storage)",
|
||||
"tencentcloud_css": "Tencent Cloud - CSS (Cloud Streaming Service)",
|
||||
"tencentcloud_ddospro": "Tencent Cloud - Anti-DDoS Pro",
|
||||
"tencentcloud_dns": "Tencent Cloud - DNS",
|
||||
"tencentcloud_ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)",
|
||||
"tencentcloud_eo": "Tencent Cloud - EdgeOne",
|
||||
|
||||
@@ -978,6 +978,10 @@
|
||||
"placeholder": "Please enter APISIX certificate ID",
|
||||
"tooltip": "You can find it on APISIX dashboard."
|
||||
},
|
||||
"asiaispcdn_domain": {
|
||||
"label": "Asia-ISP CDN domain",
|
||||
"placeholder": "Please enter Asia-ISP CDN domain name"
|
||||
},
|
||||
"aws_acm_region": {
|
||||
"label": "AWS Region",
|
||||
"placeholder": "Please enter AWS ACM region (e.g. us-east-1)",
|
||||
@@ -2190,6 +2194,20 @@
|
||||
"label": "Tencent Cloud CSS streaming domain",
|
||||
"placeholder": "Please enter Tencent Cloud CSS streaming domain name"
|
||||
},
|
||||
"tencentcloud_ddospro_endpoint": {
|
||||
"label": "Tencent Cloud API endpoint (Optional)",
|
||||
"placeholder": "Please enter Tencent Cloud Anti-DDoS Pro API endpoint (e.g. clb.intl.tencentcloudapi.com)",
|
||||
"tooltip": "<ul style=\"list-style: disc;\"><li><strong>clb.intl.tencentcloudapi.com</strong> for Tencent Cloud International</li><li><strong>clb.tencentcloudapi.com</strong> for Tencent Cloud in China (<em>Default</em>)</li></ul>"
|
||||
},
|
||||
"tencentcloud_ddospro_instance_id": {
|
||||
"label": "Tencent Cloud Anti-DDoS Pro instance ID",
|
||||
"placeholder": "Please enter Tencent Cloud Anti-DDoS Pro instance ID",
|
||||
"tooltip": "For more information, see <a href=\"https://console.tencentcloud.com/ddos\" target=\"_blank\">https://console.tencentcloud.com/ddos</a>"
|
||||
},
|
||||
"tencentcloud_ddospro_domain": {
|
||||
"label": "Tencent Cloud Anti-DDoS Pro domain",
|
||||
"placeholder": "Please enter Tencent Cloud Anti-DDoS Pro domain name"
|
||||
},
|
||||
"tencentcloud_ecdn_endpoint": {
|
||||
"label": "Tencent Cloud API endpoint (Optional)",
|
||||
"placeholder": "Please enter Tencent Cloud ECDN API endpoint (e.g. cdn.intl.tencentcloudapi.com)",
|
||||
@@ -2696,6 +2714,11 @@
|
||||
"placeholder": "Please enter VolcEngine CLB listener ID",
|
||||
"tooltip": "For more information, see <a href=\"https://console.volcengine.com/clb/LoadBalancer\" target=\"_blank\">https://console.volcengine.com/clb/LoadBalancer</a>"
|
||||
},
|
||||
"volcengine_clb_snidomain": {
|
||||
"label": "VolcEngine CLB SNI domain (Optional)",
|
||||
"placeholder": "Please enter the VolcEngine CLB extension domain",
|
||||
"help": "Notes: Leave it blank to set the default certificate; otherwise, set the extension one for SNI."
|
||||
},
|
||||
"volcengine_dcdn_domain": {
|
||||
"label": "VolcEngine DCDN domain",
|
||||
"placeholder": "Please enter VolcEngine DCDN domain name"
|
||||
|
||||
@@ -220,6 +220,14 @@
|
||||
"placeholder": "请输入 ArvanCloud API 密钥",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://docs.arvancloud.ir/en/developer-tools/api/api-key\" target=\"_blank\">https://docs.arvancloud.ir/en/developer-tools/api/api-key</a>"
|
||||
},
|
||||
"asiaispcdn_access_key_id": {
|
||||
"label": "橙域网络网迹 CDN AccessKeyID",
|
||||
"placeholder": "请输入橙域网络网迹 CDN AccessKeyID"
|
||||
},
|
||||
"asiaispcdn_access_key_secret": {
|
||||
"label": "橙域网络网迹 CDN AccessKeySecret",
|
||||
"placeholder": "请输入橙域网络网迹 CDN AccessKeySecret"
|
||||
},
|
||||
"aws_auth_method": {
|
||||
"label": "AWS API 认证方式",
|
||||
"placeholder": "请选择 AWS API 认证方式",
|
||||
@@ -879,6 +887,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lecdn_auth_method": {
|
||||
"label": "LeCDN API 认证方式",
|
||||
"placeholder": "请选择 LeCDN API 认证方式",
|
||||
"option": {
|
||||
"password": {
|
||||
"label": "账号密码"
|
||||
},
|
||||
"apikey": {
|
||||
"label": "API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lecdn_username": {
|
||||
"label": "LeCDN 用户名",
|
||||
"placeholder": "请输入 LeCDN 用户名"
|
||||
@@ -887,6 +907,10 @@
|
||||
"label": "LeCDN 用户密码",
|
||||
"placeholder": "请输入 LeCDN 用户密码"
|
||||
},
|
||||
"lecdn_api_key": {
|
||||
"label": "LeCDN API Key",
|
||||
"placeholder": "请输入 LeCDN API Key"
|
||||
},
|
||||
"linode_access_token": {
|
||||
"label": "Linode AccessToken",
|
||||
"placeholder": "请输入 Linode AccessToken",
|
||||
@@ -996,7 +1020,7 @@
|
||||
"placeholder": "请选择 NPM API 认证方式",
|
||||
"option": {
|
||||
"password": {
|
||||
"label": "密码"
|
||||
"label": "账号密码"
|
||||
},
|
||||
"token": {
|
||||
"label": "API Token"
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"aliyun_waf": "阿里云 - Web 应用防火墙 WAF",
|
||||
"apisix": "Apache APISIX",
|
||||
"arvancloud": "ArvanCloud",
|
||||
"asiaispcdn": "橙域网络网迹 CDN",
|
||||
"aws": "AWS(Amazon Web Services)",
|
||||
"aws_acm": "AWS - 证书管理器 ACM",
|
||||
"aws_alb": "AWS - 应用程序负载均衡器 ALB",
|
||||
@@ -249,6 +250,7 @@
|
||||
"tencentcloud_clb": "腾讯云 - 负载均衡 CLB",
|
||||
"tencentcloud_cos": "腾讯云 - 对象存储 COS",
|
||||
"tencentcloud_css": "腾讯云 - 云直播 CSS",
|
||||
"tencentcloud_ddospro": "腾讯云 - DDoS 高防",
|
||||
"tencentcloud_dns": "腾讯云 - 云解析 DNS",
|
||||
"tencentcloud_ecdn": "腾讯云 - 全站加速网络 ECDN",
|
||||
"tencentcloud_eo": "腾讯云 - 边缘安全加速平台 EdgeOne",
|
||||
|
||||
@@ -977,6 +977,10 @@
|
||||
"placeholder": "请输入 APISIX 证书 ID",
|
||||
"tooltip": "请登录 APISIX 控制台查看"
|
||||
},
|
||||
"asiaispcdn_domain": {
|
||||
"label": "橙域网络网迹 CDN 加速域名",
|
||||
"placeholder": "请输入橙域网络网迹 CDN 加速域名"
|
||||
},
|
||||
"aws_acm_region": {
|
||||
"label": "AWS 服务区域",
|
||||
"placeholder": "请输入 AWS ACM 服务区域(例如:us-east-1)",
|
||||
@@ -2189,6 +2193,20 @@
|
||||
"label": "腾讯云云直播流域名",
|
||||
"placeholder": "请输入腾讯云云直播流域名"
|
||||
},
|
||||
"tencentcloud_ddospro_endpoint": {
|
||||
"label": "腾讯云接口端点(可选)",
|
||||
"placeholder": "请输入腾讯云 DDoS 高防接口端点(例如:clb.tencentcloudapi.com)",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/214/30669\" target=\"_blank\">https://cloud.tencent.com/document/product/214/30669</a><br>国际站用户请填写 <em>clb.intl.tencentcloudapi.com</em>。"
|
||||
},
|
||||
"tencentcloud_ddospro_instance_id": {
|
||||
"label": "腾讯云 DDoS 高防实例 ID",
|
||||
"placeholder": "请输入腾讯云 DDoS 高防实例 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/ddos\" target=\"_blank\">https://console.cloud.tencent.com/ddos</a>"
|
||||
},
|
||||
"tencentcloud_ddospro_domain": {
|
||||
"label": "腾讯云 DDoS 高防网站域名",
|
||||
"placeholder": "请输入腾讯云 DDoS 高防网站域名"
|
||||
},
|
||||
"tencentcloud_ecdn_endpoint": {
|
||||
"label": "腾讯云接口端点(可选)",
|
||||
"placeholder": "请输入腾讯云 ECDN 接口端点(例如:cdn.tencentcloudapi.com)",
|
||||
@@ -2695,6 +2713,11 @@
|
||||
"placeholder": "请输入火山引擎 CLB 监听器 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://console.volcengine.com/clb/LoadBalancer\" target=\"_blank\">https://console.volcengine.com/clb/LoadBalancer</a>"
|
||||
},
|
||||
"volcengine_clb_snidomain": {
|
||||
"label": "火山引擎 CLB 扩展域名(可选)",
|
||||
"placeholder": "请输入火山引擎 CLB 扩展域名",
|
||||
"help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。"
|
||||
},
|
||||
"volcengine_dcdn_domain": {
|
||||
"label": "火山引擎 DCDN 加速域名",
|
||||
"placeholder": "请输入火山引擎 DCDN 加速域名"
|
||||
|
||||
Reference in New Issue
Block a user