diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index fbf24742..3dbfd79e 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -17,6 +17,7 @@ import (
pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud"
pConstellix "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix"
+ pCTCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud"
pDeSEC "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/desec"
pDigitalOcean "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/digitalocean"
pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla"
@@ -220,7 +221,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
- case domain.ACMEDns01ProviderTypeCMCCCloud:
+ case domain.ACMEDns01ProviderTypeCMCCCloud, domain.ACMEDns01ProviderTypeCMCCCloudDNS:
{
access := domain.AccessConfigForCMCCCloud{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
@@ -252,6 +253,22 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
+ case domain.ACMEDns01ProviderTypeCTCCCloud, domain.ACMEDns01ProviderTypeCTCCCloudSmartDNS:
+ {
+ access := domain.AccessConfigForCTCCCloud{}
+ if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ applicant, err := pCTCCCloud.NewChallengeProvider(&pCTCCCloud.ChallengeProviderConfig{
+ AccessKeyId: access.AccessKeyId,
+ SecretAccessKey: access.SecretAccessKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ACMEDns01ProviderTypeDeSEC:
{
access := domain.AccessConfigForDeSEC{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 4cce40a2..29d07513 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -120,6 +120,11 @@ type AccessConfigForConstellix struct {
SecretKey string `json:"secretKey"`
}
+type AccessConfigForCTCCCloud struct {
+ AccessKeyId string `json:"accessKeyId"`
+ SecretAccessKey string `json:"secretAccessKey"`
+}
+
type AccessConfigForDeSEC struct {
Token string `json:"token"`
}
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 2ca69241..b10cda8f 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -30,7 +30,7 @@ const (
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
AccessProviderTypeConstellix = AccessProviderType("constellix")
- AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 天翼云(预留)
+ AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud")
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 联通云(预留)
AccessProviderTypeDeSEC = AccessProviderType("desec")
AccessProviderTypeDigitalOcean = AccessProviderType("digitalocean")
@@ -119,51 +119,54 @@ ACME DNS-01 提供商常量值。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
- ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq)
- ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS]
- ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns")
- ACMEDns01ProviderTypeAliyunESA = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-esa")
- ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53]
- ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53")
- ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure]
- ACMEDns01ProviderTypeAzureDNS = ACMEDns01ProviderType(AccessProviderTypeAzure + "-dns")
- ACMEDns01ProviderTypeBaiduCloud = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeBaiduCloudDNS]
- ACMEDns01ProviderTypeBaiduCloudDNS = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud + "-dns")
- ACMEDns01ProviderTypeBunny = ACMEDns01ProviderType(AccessProviderTypeBunny)
- ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare)
- ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
- ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud)
- ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix)
- ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
- ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
- ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
- ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)
- ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6)
- ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore)
- ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname)
- ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy)
- ACMEDns01ProviderTypeHetzner = ACMEDns01ProviderType(AccessProviderTypeHetzner)
- ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
- ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
- ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
- ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns")
- ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap)
- ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom)
- ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo)
- ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup)
- ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify)
- ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
- ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
- ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
- ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
- ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
- ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns")
- ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo")
- ACMEDns01ProviderTypeUCloudUDNR = ACMEDns01ProviderType(AccessProviderTypeUCloud + "-udnr")
- ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
- ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
- ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
- ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
+ ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq)
+ ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS]
+ ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns")
+ ACMEDns01ProviderTypeAliyunESA = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-esa")
+ ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53]
+ ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53")
+ ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure]
+ ACMEDns01ProviderTypeAzureDNS = ACMEDns01ProviderType(AccessProviderTypeAzure + "-dns")
+ ACMEDns01ProviderTypeBaiduCloud = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeBaiduCloudDNS]
+ ACMEDns01ProviderTypeBaiduCloudDNS = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud + "-dns")
+ ACMEDns01ProviderTypeBunny = ACMEDns01ProviderType(AccessProviderTypeBunny)
+ ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare)
+ ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
+ ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCMCCCloudDNS]
+ ACMEDns01ProviderTypeCMCCCloudDNS = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud + "-dns")
+ ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix)
+ ACMEDns01ProviderTypeCTCCCloud = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCTCCCloudSmartDNS]
+ ACMEDns01ProviderTypeCTCCCloudSmartDNS = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud + "-smartdns")
+ ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
+ ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
+ ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
+ ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)
+ ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6)
+ ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore)
+ ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname)
+ ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy)
+ ACMEDns01ProviderTypeHetzner = ACMEDns01ProviderType(AccessProviderTypeHetzner)
+ ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
+ ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
+ ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
+ ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns")
+ ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap)
+ ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom)
+ ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo)
+ ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup)
+ ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify)
+ ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
+ ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
+ ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
+ ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
+ ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
+ ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns")
+ ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo")
+ ACMEDns01ProviderTypeUCloudUDNR = ACMEDns01ProviderType(AccessProviderTypeUCloud + "-udnr")
+ ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
+ ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
+ ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
+ ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
)
type DeploymentProviderType string
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/cmcccloud.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/cmcccloud.go
index ba0721fd..83425f2d 100644
--- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/cmcccloud.go
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/cmcccloud.go
@@ -1,7 +1,6 @@
package cmcccloud
import (
- "errors"
"time"
"github.com/go-acme/lego/v4/challenge"
@@ -18,7 +17,7 @@ type ChallengeProviderConfig struct {
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
- return nil, errors.New("config is nil")
+ panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/internal/lego.go
index 6bccb1dc..b4d6b971 100644
--- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/internal/lego.go
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/internal/lego.go
@@ -18,8 +18,9 @@ import (
const (
envNamespace = "CMCCCLOUD_"
- EnvAccessKey = envNamespace + "ACCESS_KEY"
- EnvSecretKey = envNamespace + "SECRET_KEY"
+ EnvAccessKey = envNamespace + "ACCESS_KEY"
+ EnvSecretKey = envNamespace + "SECRET_KEY"
+
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
@@ -30,13 +31,14 @@ const (
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
- AccessKey string
- SecretKey string
- ReadTimeOut int
- ConnectTimeout int
+ AccessKey string
+ SecretKey string
+
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
+ ReadTimeOut int
+ ConnectTimeout int
}
type DNSProvider struct {
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/ctcccloud.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/ctcccloud.go
new file mode 100644
index 00000000..8b3d494a
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/ctcccloud.go
@@ -0,0 +1,39 @@
+package ctcccloud
+
+import (
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+
+ "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal"
+)
+
+type ChallengeProviderConfig struct {
+ AccessKeyId string `json:"accessKeyId"`
+ SecretAccessKey string `json:"secretAccessKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ providerConfig := internal.NewDefaultConfig()
+ providerConfig.AccessKeyId = config.AccessKeyId
+ providerConfig.SecretAccessKey = config.SecretAccessKey
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+
+ provider, err := internal.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal/lego.go
new file mode 100644
index 00000000..1dd7f2e0
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal/lego.go
@@ -0,0 +1,203 @@
+package internal
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/challenge/dns01"
+ "github.com/go-acme/lego/v4/platform/config/env"
+
+ ctyundns "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/dns"
+ typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
+)
+
+const (
+ envNamespace = "CTYUNSMARTDNS_"
+
+ EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
+ EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
+
+ EnvTTL = envNamespace + "TTL"
+ EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+ EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
+ EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
+)
+
+var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
+
+type Config struct {
+ AccessKeyId string
+ SecretAccessKey string
+
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ HTTPTimeout time.Duration
+}
+
+type DNSProvider struct {
+ client *ctyundns.Client
+ config *Config
+}
+
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: env.GetOrDefaultInt(EnvTTL, 600),
+ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
+ HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+ }
+}
+
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
+ if err != nil {
+ return nil, fmt.Errorf("ctyun: %w", err)
+ }
+
+ config := NewDefaultConfig()
+ config.AccessKeyId = values[EnvAccessKeyID]
+ config.SecretAccessKey = values[EnvSecretAccessKey]
+
+ return NewDNSProviderConfig(config)
+}
+
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("ctyun: the configuration of the DNS provider is nil")
+ }
+
+ client, err := ctyundns.NewClient(config.AccessKeyId, config.SecretAccessKey)
+ if err != nil {
+ return nil, err
+ } else {
+ client.SetTimeout(config.HTTPTimeout)
+ }
+
+ return &DNSProvider{
+ client: client,
+ config: config,
+ }, nil
+}
+
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ info := dns01.GetChallengeInfo(domain, keyAuth)
+
+ authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
+ if err != nil {
+ return fmt.Errorf("ctyun: could not find zone for domain %q: %w", domain, err)
+ }
+
+ subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
+ if err != nil {
+ return fmt.Errorf("ctyun: %w", err)
+ }
+
+ if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
+ return fmt.Errorf("ctyun: %w", err)
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ info := dns01.GetChallengeInfo(domain, keyAuth)
+
+ authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
+ if err != nil {
+ return fmt.Errorf("ctyun: could not find zone for domain %q: %w", domain, err)
+ }
+
+ subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
+ if err != nil {
+ return fmt.Errorf("ctyun: %w", err)
+ }
+
+ if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
+ return fmt.Errorf("ctyun: %w", err)
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+func (d *DNSProvider) findDNSRecordId(zoneName, subDomain string) (int32, error) {
+ // 查询解析记录列表
+ // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11264&data=181&isNormal=1&vid=259
+ request := &ctyundns.QueryRecordListRequest{}
+ request.Domain = typeutil.ToPtr(zoneName)
+ request.Host = typeutil.ToPtr(subDomain)
+ request.Type = typeutil.ToPtr("TXT")
+
+ response, err := d.client.QueryRecordList(request)
+ if err != nil {
+ return 0, err
+ }
+
+ if response.ReturnObj == nil || response.ReturnObj.Records == nil || len(response.ReturnObj.Records) == 0 {
+ return 0, nil
+ }
+
+ return response.ReturnObj.Records[0].RecordId, nil
+}
+
+func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
+ recordId, err := d.findDNSRecordId(zoneName, subDomain)
+ if err != nil {
+ return err
+ }
+
+ if recordId == 0 {
+ // 新增解析记录
+ // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11259&data=181&isNormal=1&vid=259
+ request := &ctyundns.AddRecordRequest{
+ Domain: typeutil.ToPtr(zoneName),
+ Host: typeutil.ToPtr(subDomain),
+ Type: typeutil.ToPtr("TXT"),
+ LineCode: typeutil.ToPtr("Default"),
+ Value: typeutil.ToPtr(value),
+ State: typeutil.ToPtr(int32(1)),
+ TTL: typeutil.ToPtr(int32(d.config.TTL)),
+ }
+ _, err := d.client.AddRecord(request)
+ return err
+ } else {
+ // 修改解析记录
+ // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11261&data=181&isNormal=1&vid=259
+ request := &ctyundns.UpdateRecordRequest{
+ RecordId: typeutil.ToPtr(recordId),
+ Domain: typeutil.ToPtr(zoneName),
+ Host: typeutil.ToPtr(subDomain),
+ Type: typeutil.ToPtr("TXT"),
+ LineCode: typeutil.ToPtr("Default"),
+ Value: typeutil.ToPtr(value),
+ State: typeutil.ToPtr(int32(1)),
+ TTL: typeutil.ToPtr(int32(d.config.TTL)),
+ }
+ _, err := d.client.UpdateRecord(request)
+ return err
+ }
+}
+
+func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
+ recordId, err := d.findDNSRecordId(zoneName, subDomain)
+ if err != nil {
+ return err
+ }
+
+ if recordId == 0 {
+ return nil
+ } else {
+ // 删除解析记录
+ // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11262&data=181&isNormal=1&vid=259
+ request := &ctyundns.DeleteRecordRequest{
+ RecordId: typeutil.ToPtr(recordId),
+ }
+ _, err = d.client.DeleteRecord(request)
+ return err
+ }
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/api_add_record.go b/internal/pkg/sdk3rd/ctyun/dns/api_add_record.go
new file mode 100644
index 00000000..8bcdcb21
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/api_add_record.go
@@ -0,0 +1,46 @@
+package dns
+
+import (
+ "context"
+ "net/http"
+)
+
+type AddRecordRequest struct {
+ Domain *string `json:"domain,omitempty"`
+ Host *string `json:"host,omitempty"`
+ Type *string `json:"type,omitempty"`
+ LineCode *string `json:"lineCode,omitempty"`
+ Value *string `json:"value,omitempty"`
+ TTL *int32 `json:"ttl,omitempty"`
+ State *int32 `json:"state,omitempty"`
+ Remark *string `json:"remark"`
+}
+
+type AddRecordResponse struct {
+ baseResult
+
+ ReturnObj *struct {
+ RecordId int32 `json:"recordId"`
+ } `json:"returnObj,omitempty"`
+}
+
+func (c *Client) AddRecord(req *AddRecordRequest) (*AddRecordResponse, error) {
+ return c.AddRecordWithContext(context.Background(), req)
+}
+
+func (c *Client) AddRecordWithContext(ctx context.Context, req *AddRecordRequest) (*AddRecordResponse, error) {
+ request, err := c.newRequest(http.MethodPost, "/v2/addRecord")
+ if err != nil {
+ return nil, err
+ } else {
+ request.SetContext(ctx)
+ request.SetBody(req)
+ }
+
+ result := &AddRecordResponse{}
+ if _, err := c.doRequestWithResult(request, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/api_delete_record.go b/internal/pkg/sdk3rd/ctyun/dns/api_delete_record.go
new file mode 100644
index 00000000..679e6fcf
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/api_delete_record.go
@@ -0,0 +1,35 @@
+package dns
+
+import (
+ "context"
+ "net/http"
+)
+
+type DeleteRecordRequest struct {
+ RecordId *int32 `json:"recordId,omitempty"`
+}
+
+type DeleteRecordResponse struct {
+ baseResult
+}
+
+func (c *Client) DeleteRecord(req *DeleteRecordRequest) (*DeleteRecordResponse, error) {
+ return c.DeleteRecordWithContext(context.Background(), req)
+}
+
+func (c *Client) DeleteRecordWithContext(ctx context.Context, req *DeleteRecordRequest) (*DeleteRecordResponse, error) {
+ request, err := c.newRequest(http.MethodPost, "/v2/deleteRecord")
+ if err != nil {
+ return nil, err
+ } else {
+ request.SetContext(ctx)
+ request.SetBody(req)
+ }
+
+ result := &DeleteRecordResponse{}
+ if _, err := c.doRequestWithResult(request, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/api_query_record_list.go b/internal/pkg/sdk3rd/ctyun/dns/api_query_record_list.go
new file mode 100644
index 00000000..4d7f1617
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/api_query_record_list.go
@@ -0,0 +1,53 @@
+package dns
+
+import (
+ "context"
+ "net/http"
+)
+
+type QueryRecordListRequest struct {
+ Domain *string `json:"domain,omitempty"`
+ Host *string `json:"host,omitempty"`
+ Type *string `json:"type,omitempty"`
+ LineCode *string `json:"lineCode,omitempty"`
+ Value *string `json:"value,omitempty"`
+ State *int32 `json:"state,omitempty"`
+}
+
+type QueryRecordListResponse struct {
+ baseResult
+
+ ReturnObj *struct {
+ Records []*struct {
+ RecordId int32 `json:"recordId"`
+ Host string `json:"host"`
+ Type string `json:"type"`
+ LineCode string `json:"lineCode"`
+ Value string `json:"value"`
+ TTL int32 `json:"ttl"`
+ State int32 `json:"state"`
+ Remark string `json:"remark"`
+ } `json:"records,omitempty"`
+ } `json:"returnObj,omitempty"`
+}
+
+func (c *Client) QueryRecordList(req *QueryRecordListRequest) (*QueryRecordListResponse, error) {
+ return c.QueryRecordListWithContext(context.Background(), req)
+}
+
+func (c *Client) QueryRecordListWithContext(ctx context.Context, req *QueryRecordListRequest) (*QueryRecordListResponse, error) {
+ request, err := c.newRequest(http.MethodGet, "/v2/queryRecordList")
+ if err != nil {
+ return nil, err
+ } else {
+ request.SetContext(ctx)
+ request.SetBody(req)
+ }
+
+ result := &QueryRecordListResponse{}
+ if _, err := c.doRequestWithResult(request, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/api_update_record.go b/internal/pkg/sdk3rd/ctyun/dns/api_update_record.go
new file mode 100644
index 00000000..ca827ce3
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/api_update_record.go
@@ -0,0 +1,47 @@
+package dns
+
+import (
+ "context"
+ "net/http"
+)
+
+type UpdateRecordRequest struct {
+ RecordId *int32 `json:"recordId,omitempty"`
+ Domain *string `json:"domain,omitempty"`
+ Host *string `json:"host,omitempty"`
+ Type *string `json:"type,omitempty"`
+ LineCode *string `json:"lineCode,omitempty"`
+ Value *string `json:"value,omitempty"`
+ TTL *int32 `json:"ttl,omitempty"`
+ State *int32 `json:"state,omitempty"`
+ Remark *string `json:"remark"`
+}
+
+type UpdateRecordResponse struct {
+ baseResult
+
+ ReturnObj *struct {
+ RecordId int32 `json:"recordId"`
+ } `json:"returnObj,omitempty"`
+}
+
+func (c *Client) UpdateRecord(req *UpdateRecordRequest) (*UpdateRecordResponse, error) {
+ return c.UpdateRecordWithContext(context.Background(), req)
+}
+
+func (c *Client) UpdateRecordWithContext(ctx context.Context, req *UpdateRecordRequest) (*UpdateRecordResponse, error) {
+ request, err := c.newRequest(http.MethodPost, "/v2/updateRecord")
+ if err != nil {
+ return nil, err
+ } else {
+ request.SetContext(ctx)
+ request.SetBody(req)
+ }
+
+ result := &UpdateRecordResponse{}
+ if _, err := c.doRequestWithResult(request, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/client.go b/internal/pkg/sdk3rd/ctyun/dns/client.go
new file mode 100644
index 00000000..c2f2b594
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/client.go
@@ -0,0 +1,40 @@
+package dns
+
+import (
+ "time"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/openapi"
+)
+
+const endpoint = "https://smartdns-global.ctapi.ctyun.cn"
+
+type Client struct {
+ client *openapi.Client
+}
+
+func NewClient(accessKeyId, secretAccessKey string) (*Client, error) {
+ client, err := openapi.NewClient(endpoint, accessKeyId, secretAccessKey)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Client{client: client}, nil
+}
+
+func (c *Client) SetTimeout(timeout time.Duration) *Client {
+ c.client.SetTimeout(timeout)
+ return c
+}
+
+func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
+ return c.client.NewRequest(method, path)
+}
+
+func (c *Client) doRequest(request *resty.Request) (*resty.Response, error) {
+ return c.client.DoRequest(request)
+}
+
+func (c *Client) doRequestWithResult(request *resty.Request, result any) (*resty.Response, error) {
+ return c.client.DoRequestWithResult(request, result)
+}
diff --git a/internal/pkg/sdk3rd/ctyun/dns/types.go b/internal/pkg/sdk3rd/ctyun/dns/types.go
new file mode 100644
index 00000000..8fe2eabd
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/dns/types.go
@@ -0,0 +1,11 @@
+package dns
+
+import "encoding/json"
+
+type baseResult struct {
+ StatusCode json.RawMessage `json:"statusCode,omitempty"`
+ Message *string `json:"message,omitempty"`
+ Error *string `json:"error,omitempty"`
+ ErrorMessage *string `json:"errorMessage,omitempty"`
+ RequestId *string `json:"requestId,omitempty"`
+}
diff --git a/internal/pkg/sdk3rd/ctyun/openapi/client.go b/internal/pkg/sdk3rd/ctyun/openapi/client.go
new file mode 100644
index 00000000..6960c83c
--- /dev/null
+++ b/internal/pkg/sdk3rd/ctyun/openapi/client.go
@@ -0,0 +1,167 @@
+package openapi
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/google/uuid"
+)
+
+type Client struct {
+ client *resty.Client
+}
+
+func NewClient(endpoint, accessKeyId, secretAccessKey string) (*Client, error) {
+ if endpoint == "" {
+ return nil, fmt.Errorf("sdk error: unset endpoint")
+ }
+ if _, err := url.Parse(endpoint); err != nil {
+ return nil, fmt.Errorf("sdk error: invalid endpoint: %w", err)
+ }
+ if accessKeyId == "" {
+ return nil, fmt.Errorf("sdk error: unset accessKey")
+ }
+ if secretAccessKey == "" {
+ return nil, fmt.Errorf("sdk error: unset secretKey")
+ }
+
+ client := resty.New().
+ SetBaseURL(endpoint).
+ SetHeader("Accept", "application/json").
+ SetHeader("Content-Type", "application/json").
+ SetHeader("User-Agent", "certimate").
+ SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
+ // 生成时间戳及流水号
+ now := time.Now()
+ eopDate := now.Format("20060102T150405Z")
+ eopReqId := uuid.New().String()
+
+ // 获取查询参数
+ queryStr := ""
+ if req.URL != nil {
+ queryStr = req.URL.Query().Encode()
+ }
+
+ // 获取请求正文
+ payloadStr := ""
+ if req.Body != nil {
+ reader, err := req.GetBody()
+ if err != nil {
+ return err
+ }
+
+ defer reader.Close()
+ payload, err := io.ReadAll(reader)
+ if err != nil {
+ return err
+ }
+
+ payloadStr = string(payload)
+ }
+
+ // 构造代签字符串
+ payloadHash := sha256.Sum256([]byte(payloadStr))
+ payloadHashHex := hex.EncodeToString(payloadHash[:])
+ dataToSign := fmt.Sprintf("ctyun-eop-request-id:%s\neop-date:%s\n\n%s\n%s", eopReqId, eopDate, queryStr, payloadHashHex)
+
+ // 生成 ktime
+ hasher := hmac.New(sha256.New, []byte(secretAccessKey))
+ hasher.Write([]byte(eopDate))
+ ktime := hasher.Sum(nil)
+
+ // 生成 kak
+ hasher = hmac.New(sha256.New, ktime)
+ hasher.Write([]byte(accessKeyId))
+ kak := hasher.Sum(nil)
+
+ // 生成 kdata
+ hasher = hmac.New(sha256.New, kak)
+ hasher.Write([]byte(now.Format("20060102")))
+ kdate := hasher.Sum(nil)
+
+ // 构造签名
+ hasher = hmac.New(sha256.New, kdate)
+ hasher.Write([]byte(dataToSign))
+ sign := hasher.Sum(nil)
+ signStr := base64.StdEncoding.EncodeToString(sign)
+
+ // 设置请求头
+ req.Header.Set("ctyun-eop-request-id", eopReqId)
+ req.Header.Set("eop-date", eopDate)
+ req.Header.Set("eop-authorization", fmt.Sprintf("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", accessKeyId, signStr))
+
+ return nil
+ })
+
+ return &Client{
+ client: client,
+ }, nil
+}
+
+func (c *Client) SetTimeout(timeout time.Duration) *Client {
+ c.client.SetTimeout(timeout)
+ return c
+}
+
+func (c *Client) NewRequest(method string, path string) (*resty.Request, error) {
+ if method == "" {
+ return nil, fmt.Errorf("sdk error: unset method")
+ }
+ if path == "" {
+ return nil, fmt.Errorf("sdk error: unset path")
+ }
+
+ req := c.client.R()
+ req.Method = method
+ req.URL = path
+ return req, nil
+}
+
+func (c *Client) DoRequest(request *resty.Request) (*resty.Response, error) {
+ if request == nil {
+ return nil, fmt.Errorf("sdk error: nil request")
+ }
+
+ // WARN:
+ // PLEASE DO NOT USE `req.SetResult` or `req.SetError` here.
+
+ resp, err := request.Send()
+ if err != nil {
+ return resp, fmt.Errorf("sdk error: failed to send request: %w", err)
+ } else if resp.IsError() {
+ return resp, fmt.Errorf("sdk error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
+ }
+
+ return resp, nil
+}
+
+func (c *Client) DoRequestWithResult(request *resty.Request, result any) (*resty.Response, error) {
+ if request == nil {
+ return nil, fmt.Errorf("sdk error: nil request")
+ }
+
+ response, err := c.DoRequest(request)
+ if err != nil {
+ if response != nil {
+ json.Unmarshal(response.Body(), &result)
+ }
+ return response, err
+ }
+
+ if len(response.Body()) != 0 {
+ if err := json.Unmarshal(response.Body(), &result); err != nil {
+ return response, fmt.Errorf("sdk error: failed to unmarshal response: %w", err)
+ }
+ }
+
+ return response, nil
+}
diff --git a/ui/public/imgs/providers/ctcccloud.svg b/ui/public/imgs/providers/ctcccloud.svg
new file mode 100644
index 00000000..b5ea5d76
--- /dev/null
+++ b/ui/public/imgs/providers/ctcccloud.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 7b7640d3..91d7139f 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -30,6 +30,7 @@ import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig";
import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig";
import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig";
import AccessFormConstellixConfig from "./AccessFormConstellixConfig";
+import AccessFormCTCCCloudConfig from "./AccessFormCTCCCloudConfig";
import AccessFormDeSECConfig from "./AccessFormDeSECConfig";
import AccessFormDigitalOceanConfig from "./AccessFormDigitalOceanConfig";
import AccessFormDingTalkBotConfig from "./AccessFormDingTalkBotConfig";
@@ -225,6 +226,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.CONSTELLIX:
return ;
+ case ACCESS_PROVIDERS.CTCCCLOUD:
+ return ;
case ACCESS_PROVIDERS.DESEC:
return ;
case ACCESS_PROVIDERS.DIGITALOCEAN:
diff --git a/ui/src/components/access/AccessFormCTCCCloudConfig.tsx b/ui/src/components/access/AccessFormCTCCCloudConfig.tsx
new file mode 100644
index 00000000..f0e9df39
--- /dev/null
+++ b/ui/src/components/access/AccessFormCTCCCloudConfig.tsx
@@ -0,0 +1,73 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+import { type AccessConfigForCTCCCloud } from "@/domain/access";
+
+type AccessFormCTCCCloudConfigFieldValues = Nullish;
+
+export type AccessFormCTCCCloudConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormCTCCCloudConfigFieldValues;
+ onValuesChange?: (values: AccessFormCTCCCloudConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormCTCCCloudConfigFieldValues => {
+ return {
+ accessKeyId: "",
+ secretAccessKey: "",
+ };
+};
+
+const AccessFormCTCCCloudConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormCTCCCloudConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ accessKeyId: z
+ .string()
+ .min(1, t("access.form.ctcccloud_access_key_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ secretAccessKey: z
+ .string()
+ .min(1, t("access.form.ctcccloud_secret_access_key.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormCTCCCloudConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index cdf01c89..a2dbde73 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -25,6 +25,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForClouDNS
| AccessConfigForCMCCCloud
| AccessConfigForConstellix
+ | AccessConfigForCTCCCloud
| AccessConfigForDeSEC
| AccessConfigForDigitalOcean
| AccessConfigForDingTalkBot
@@ -185,6 +186,11 @@ export type AccessConfigForConstellix = {
secretKey: string;
};
+export type AccessConfigForCTCCCloud = {
+ accessKeyId: string;
+ secretAccessKey: string;
+};
+
export type AccessConfigForDeSEC = {
token: string;
};
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 9956a8c9..212ded80 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -24,6 +24,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
CLOUDNS: "cloudns",
CMCCCLOUD: "cmcccloud",
CONSTELLIX: "constellix",
+ CTCCCLOUD: "ctcccloud",
DESEC: "desec",
DIGITALOCEAN: "digitalocean",
DINGTALKBOT: "dingtalkbot",
@@ -164,6 +165,7 @@ export const accessProvidersMap: Maphttps://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key",
+ "access.form.ctcccloud_access_key_id.label": "CTCC StateCloud AccessKeyId",
+ "access.form.ctcccloud_access_key_id.placeholder": "Please enter CTCC StateCloud AccessKeyId",
+ "access.form.ctcccloud_access_key_id.tooltip": "For more information, see https://www.ctyun.cn/document/10015882/10015953",
+ "access.form.ctcccloud_secret_access_key.label": "CTCC StateCloud SecretAccessKey",
+ "access.form.ctcccloud_secret_access_key.placeholder": "Please enter CTCC StateCloud SecretAccessKey",
+ "access.form.ctcccloud_secret_access_key.tooltip": "For more information, see https://www.ctyun.cn/document/10015882/10015953",
"access.form.desec_token.label": "deSEC token",
"access.form.desec_token.placeholder": "Please enter deSEC token",
"access.form.desec_token.tooltip": "For more information, see https://desec.readthedocs.io/en/latest/auth/tokens.html",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index f72c0ca1..a9761ae2 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -13,7 +13,7 @@
"provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)",
"provider.aliyun.dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)",
"provider.aliyun.ddos": "Alibaba Cloud - Anti-DDoS Proxy",
- "provider.aliyun.dns": "Alibaba Cloud - DNS (Domain Name Service)",
+ "provider.aliyun.dns": "Alibaba Cloud - DNS",
"provider.aliyun.esa": "Alibaba Cloud - ESA (Edge Security Acceleration)",
"provider.aliyun.fc": "Alibaba Cloud - FC (Function Compute)",
"provider.aliyun.ga": "Alibaba Cloud - GA (Global Accelerator)",
@@ -38,7 +38,7 @@
"provider.baiducloud.blb": "Baidu Cloud - BLB (Load Balancer)",
"provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)",
"provider.baiducloud.cert_upload": "Baidu Cloud - Upload to SSL Certificate Service",
- "provider.baiducloud.dns": "Baidu Cloud - DNS (Domain Name Service)",
+ "provider.baiducloud.dns": "Baidu Cloud - DNS",
"provider.baishan": "Baishan",
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
"provider.baotapanel": "aaPanel (aka BaoTaPanel)",
@@ -57,8 +57,10 @@
"provider.cloudflare": "Cloudflare",
"provider.cloudns": "ClouDNS",
"provider.cmcccloud": "China Mobile Cloud (ECloud)",
+ "provider.cmcccloud.dns": "China Mobile Cloud (ECloud) - DNS",
"provider.constellix": "Constellix",
- "provider.ctcccloud": "China Telecom Cloud (State Cloud)",
+ "provider.ctcccloud": "China Telecom Cloud (StateCloud)",
+ "provider.ctcccloud.smartdns": "China Telecom Cloud (StateCloud) - Smart DNS",
"provider.cucccloud": "China Unicom Cloud",
"provider.desec": "deSEC",
"provider.digitalocean": "DigitalOcean",
@@ -83,7 +85,7 @@
"provider.hetzner": "Hetzner",
"provider.huaweicloud": "Huawei Cloud",
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
- "provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
+ "provider.huaweicloud.dns": "Huawei Cloud - DNS",
"provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)",
"provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
"provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)",
@@ -130,7 +132,7 @@
"provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)",
"provider.tencentcloud.cos": "Tencent Cloud - COS (Cloud Object Storage)",
"provider.tencentcloud.css": "Tencent Cloud - CSS (Cloud Streaming Service)",
- "provider.tencentcloud.dns": "Tencent Cloud - DNS (Domain Name Service)",
+ "provider.tencentcloud.dns": "Tencent Cloud - DNS",
"provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)",
"provider.tencentcloud.eo": "Tencent Cloud - EdgeOne",
"provider.tencentcloud.gaap": "Tencent Cloud - GAAP (Global Application Acceleration Platform)",
@@ -155,7 +157,7 @@
"provider.volcengine.certcenter_upload": "Volcengine - Upload to Certificate Center",
"provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)",
"provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)",
- "provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)",
+ "provider.volcengine.dns": "Volcengine - DNS",
"provider.volcengine.imagex": "Volcengine - ImageX",
"provider.volcengine.live": "Volcengine - Live",
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index f5a9f3d8..a3852926 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -157,6 +157,12 @@
"access.form.constellix_secret_key.label": "Constellix Secret Key",
"access.form.constellix_secret_key.placeholder": "请输入 Constellix Secret Key",
"access.form.constellix_secret_key.tooltip": "这是什么?请参阅 https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key",
+ "access.form.ctcccloud_access_key_id.label": "天翼云 AccessKeyId",
+ "access.form.ctcccloud_access_key_id.placeholder": "请输入天翼云 AccessKeyId",
+ "access.form.ctcccloud_access_key_id.tooltip": "这是什么?请参阅 https://www.ctyun.cn/document/10015882/10015953",
+ "access.form.ctcccloud_secret_access_key.label": "天翼云 SecretAccessKey",
+ "access.form.ctcccloud_secret_access_key.placeholder": "请输入天翼云 SecretAccessKey",
+ "access.form.ctcccloud_secret_access_key.tooltip": "这是什么?请参阅 https://www.ctyun.cn/document/10015882/10015953",
"access.form.desec_token.label": "deSEC Token",
"access.form.desec_token.placeholder": "请输入 deSEC Token",
"access.form.desec_token.tooltip": "这是什么?请参阅 https://desec.readthedocs.io/en/latest/auth/tokens.html",
diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json
index bf542b34..f021a586 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -57,9 +57,11 @@
"provider.cloudflare": "Cloudflare",
"provider.cloudns": "ClouDNS",
"provider.cmcccloud": "移动云",
+ "provider.cmcccloud.dns": "移动云 - 云解析 DNS",
"provider.constellix": "Constellix",
- "provider.ctcccloud": "联通云",
- "provider.cucccloud": "天翼云",
+ "provider.ctcccloud": "天翼云",
+ "provider.ctcccloud.smartdns": "天翼云 - 智能 DNS",
+ "provider.cucccloud": "联通云",
"provider.desec": "deSEC",
"provider.digitalocean": "DigitalOcean",
"provider.dingtalkbot": "钉钉群机器人",