mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(provider): new acme dns-01 provider: qingcloud
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package applicators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/qingcloud"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeQingCloudDNS, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
|
||||
credentials := domain.AccessConfigForQingCloud{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := qingcloud.NewChallengeProvider(&qingcloud.ChallengeProviderConfig{
|
||||
AccessKeyId: credentials.AccessKeyId,
|
||||
SecretAccessKey: credentials.SecretAccessKey,
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
|
||||
ACMEDns01Registries.MustRegisterAlias(domain.ACMEDns01ProviderTypeQingCloud, domain.ACMEDns01ProviderTypeQingCloudDNS)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
udnr "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ucloud"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ func init() {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := udnr.NewChallengeProvider(&udnr.ChallengeProviderConfig{
|
||||
provider, err := ucloud.NewChallengeProvider(&ucloud.ChallengeProviderConfig{
|
||||
PrivateKey: credentials.PrivateKey,
|
||||
PublicKey: credentials.PublicKey,
|
||||
ProjectId: credentials.ProjectId,
|
||||
@@ -26,4 +26,6 @@ func init() {
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
|
||||
ACMEDns01Registries.MustRegisterAlias(domain.ACMEDns01ProviderTypeUCloud, domain.ACMEDns01ProviderTypeUCloudUDNR)
|
||||
}
|
||||
|
||||
@@ -396,6 +396,11 @@ type AccessConfigForProxmoxVE struct {
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
}
|
||||
|
||||
type AccessConfigForQingCloud struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
}
|
||||
|
||||
type AccessConfigForQiniu struct {
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
|
||||
@@ -85,7 +85,7 @@ const (
|
||||
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
|
||||
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
|
||||
AccessProviderTypeQiniu = AccessProviderType("qiniu")
|
||||
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
|
||||
AccessProviderTypeQingCloud = AccessProviderType("qingcloud")
|
||||
AccessProviderTypeRainYun = AccessProviderType("rainyun")
|
||||
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
|
||||
AccessProviderTypeRFC2136 = AccessProviderType("rfc2136")
|
||||
@@ -197,6 +197,8 @@ const (
|
||||
ACMEDns01ProviderTypeOVHcloud = ACMEDns01ProviderType(AccessProviderTypeOVHcloud)
|
||||
ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
|
||||
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
|
||||
ACMEDns01ProviderTypeQingCloud = ACMEDns01ProviderType(AccessProviderTypeQingCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeQingCloudDNS]
|
||||
ACMEDns01ProviderTypeQingCloudDNS = ACMEDns01ProviderType(AccessProviderTypeQingCloud + "-dns")
|
||||
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
|
||||
ACMEDns01ProviderTypeRFC2136 = ACMEDns01ProviderType(AccessProviderTypeRFC2136)
|
||||
ACMEDns01ProviderTypeSpaceship = ACMEDns01ProviderType(AccessProviderTypeSpaceship)
|
||||
@@ -204,6 +206,7 @@ const (
|
||||
ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
|
||||
ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns")
|
||||
ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo")
|
||||
ACMEDns01ProviderTypeUCloud = ACMEDns01ProviderType(AccessProviderTypeUCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeUCloudUDNR]
|
||||
ACMEDns01ProviderTypeUCloudUDNR = ACMEDns01ProviderType(AccessProviderTypeUCloud + "-udnr")
|
||||
ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
|
||||
ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
|
||||
|
||||
@@ -40,8 +40,8 @@ type Config struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ func NewDefaultConfig() *Config {
|
||||
ReadTimeout: env.GetOrDefaultInt(EnvReadTimeout, 30),
|
||||
ConnectTimeout: env.GetOrDefaultInt(EnvConnectTimeout, 30),
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"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"
|
||||
"github.com/samber/lo"
|
||||
|
||||
qingcloudsdk "github.com/certimate-go/certimate/pkg/sdk3rd/qingcloud/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "DNSLA_"
|
||||
|
||||
EnvAccessKey = envNamespace + "ACCESS_KEY"
|
||||
EnvAccessSecret = envNamespace + "ACCESS_SECRET"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
AccessSecret string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
client *qingcloudsdk.Client
|
||||
config *Config
|
||||
|
||||
recordIDs map[string]*int64
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAccessKey, EnvAccessSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("qingcloud: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKey = values[EnvAccessKey]
|
||||
config.AccessSecret = values[EnvAccessSecret]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("qingcloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := qingcloudsdk.NewClient(config.AccessKey, config.AccessSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
client.SetTimeout(config.HTTPTimeout)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
recordIDs: make(map[string]*int64),
|
||||
recordIDsMu: sync.Mutex{},
|
||||
}, 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("qingcloud: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
// REF: https://docsv4.qingcloud.com/user_guide/development_docs/api/api_list/dns/record/#_createrecord
|
||||
qingcloudCreateRecordReq := &qingcloudsdk.CreateRecordRequest{
|
||||
ZoneName: lo.ToPtr(authZone),
|
||||
DomainName: lo.ToPtr(info.EffectiveFQDN),
|
||||
ViewId: lo.ToPtr(int32(0)),
|
||||
Type: lo.ToPtr("TXT"),
|
||||
Ttl: lo.ToPtr(int32(d.config.TTL)),
|
||||
Records: []*qingcloudsdk.CreateRecordRequestRecord{
|
||||
{
|
||||
Values: []*qingcloudsdk.CreateRecordRequestRecordValue{
|
||||
{
|
||||
Value: lo.ToPtr(info.Value),
|
||||
Status: lo.ToPtr(int32(1)),
|
||||
},
|
||||
},
|
||||
Weight: lo.ToPtr(int32(0)),
|
||||
},
|
||||
},
|
||||
Mode: lo.ToPtr(int32(1)),
|
||||
AutoMerge: lo.ToPtr(int32(1)),
|
||||
}
|
||||
qingcloudCreateRecordResp, err := d.client.CreateRecord(qingcloudCreateRecordReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("qingcloud: error when create record: %w", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[token] = qingcloudCreateRecordResp.DomainRecordId
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[token]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("qingcloud: unknown record ID for '%s'", info.EffectiveFQDN)
|
||||
}
|
||||
|
||||
// REF: https://docsv4.qingcloud.com/user_guide/development_docs/api/api_list/dns/record/#_deleterecord
|
||||
if _, err := d.client.DeleteRecord([]*int64{recordID}); err != nil {
|
||||
return fmt.Errorf("qingcloud: error when delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package qingcloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/qingcloud/internal"
|
||||
)
|
||||
|
||||
type ChallengeProviderConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"apiPassword"`
|
||||
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration of the acme challenge provider is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.NewDefaultConfig()
|
||||
providerConfig.AccessKey = config.AccessKeyId
|
||||
providerConfig.AccessSecret = config.SecretAccessKey
|
||||
if config.DnsPropagationTimeout != 0 {
|
||||
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||
}
|
||||
if config.DnsTTL != 0 {
|
||||
providerConfig.TTL = config.DnsTTL
|
||||
}
|
||||
|
||||
provider, err := internal.NewDNSProviderConfig(providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
+8
-8
@@ -48,7 +48,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func NewDefaultConfig() *Config {
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvPrivateKey, EnvPublicKey, EnvProjectId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ucloud-udnr: %w", err)
|
||||
return nil, fmt.Errorf("ucloud: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
@@ -70,7 +70,7 @@ func NewDNSProvider() (*DNSProvider, error) {
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ucloud-udnr: the configuration of the DNS provider is nil")
|
||||
return nil, errors.New("ucloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
cfg := ucloud.NewConfig()
|
||||
@@ -91,7 +91,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err)
|
||||
return fmt.Errorf("ucloud: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_domain_dns_add
|
||||
@@ -105,7 +105,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
udnrDomainDNSAddReq.SetProjectId(d.config.ProjectId)
|
||||
}
|
||||
if _, err := d.client.AddDomainDNS(udnrDomainDNSAddReq); err != nil {
|
||||
return fmt.Errorf("ucloud-udnr: error when create record: %w", err)
|
||||
return fmt.Errorf("ucloud: error when create record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -116,7 +116,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err)
|
||||
return fmt.Errorf("ucloud: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_domain_dns_query
|
||||
@@ -127,7 +127,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
}
|
||||
udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ucloud-udnr: error when list records: %w", err)
|
||||
return fmt.Errorf("ucloud: error when list records: %w", err)
|
||||
}
|
||||
|
||||
// REF: https://docs.ucloud.cn/api/udnr-api/udnr_delete_dns_record
|
||||
@@ -143,7 +143,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
}
|
||||
_, err := d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ucloud-udnr: error when delete record: %w", err)
|
||||
return fmt.Errorf("ucloud: error when delete record: %w", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
package ucloududnr
|
||||
package ucloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ucloud-udnr/internal"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/ucloud/internal"
|
||||
)
|
||||
|
||||
type ChallengeProviderConfig struct {
|
||||
@@ -49,7 +49,7 @@ type DNSProvider struct {
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CreateRecordRequest struct {
|
||||
ZoneName *string `json:"zone_name,omitempty"`
|
||||
DomainName *string `json:"domain_name,omitempty"`
|
||||
ViewId *int32 `json:"view_id,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Records []*CreateRecordRequestRecord `json:"record,omitempty"`
|
||||
Ttl *int32 `json:"ttl,omitempty"`
|
||||
Mode *int32 `json:"mode,omitempty"`
|
||||
AutoMerge *int32 `json:"auto_merge,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRecordRequestRecord struct {
|
||||
Values []*CreateRecordRequestRecordValue `json:"values,omitempty"`
|
||||
Weight *int32 `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRecordRequestRecordValue struct {
|
||||
Value *string `json:"value,omitempty"`
|
||||
Status *int32 `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRecordResponse struct {
|
||||
apiResponseBase
|
||||
DomainName *string `json:"domain_name,omitempty"`
|
||||
DomainRecordId *int64 `json:"domain_record_id,omitempty"`
|
||||
ViewId *int64 `json:"view_id,omitempty"`
|
||||
Records []*CreateRecordResponseRecord `json:"records,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRecordResponseRecord struct {
|
||||
GroupId *int64 `json:"group_id,omitempty"`
|
||||
GroupStatus *int32 `json:"group_status,omitempty"`
|
||||
Values []*CreateRecordResponseRecordValue `json:"value,omitempty"`
|
||||
Weight *int32 `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRecordResponseRecordValue struct {
|
||||
ValueId *int64 `json:"id,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
Status *int32 `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateRecord(req *CreateRecordRequest) (*CreateRecordResponse, error) {
|
||||
return c.CreateRecordWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateRecordWithContext(ctx context.Context, req *CreateRecordRequest) (*CreateRecordResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/v1/record/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &CreateRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DeleteRecordResponse struct {
|
||||
apiResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRecord(recordIds []*int64) (*DeleteRecordResponse, error) {
|
||||
return c.DeleteRecordWithContext(context.Background(), recordIds)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRecordWithContext(ctx context.Context, recordIds []*int64) (*DeleteRecordResponse, error) {
|
||||
if len(recordIds) == 0 {
|
||||
return nil, fmt.Errorf("sdkerr: unset recordIds")
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/v1/change_record_status/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(map[string]any{
|
||||
"ids": recordIds,
|
||||
"action": "delete",
|
||||
"target": "record",
|
||||
})
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DeleteRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(accessKeyId, secretAccessKey string) (*Client, error) {
|
||||
if accessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
|
||||
}
|
||||
if secretAccessKey == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
|
||||
}
|
||||
|
||||
client := resty.New().
|
||||
SetBaseURL("http://api.routewize.com").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Host", "api.routewize.com").
|
||||
SetHeader("User-Agent", "certimate").
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// 生成时间
|
||||
date := time.Now().UTC().Format(time.RFC1123)
|
||||
|
||||
// 获取请求谓词
|
||||
verb := req.Method
|
||||
|
||||
// 获取访问资源
|
||||
canonicalizedResource := "/"
|
||||
if req.URL != nil {
|
||||
canonicalizedResource = req.URL.Path
|
||||
if req.URL.RawQuery != "" {
|
||||
values, _ := url.ParseQuery(req.URL.RawQuery)
|
||||
canonicalizedResource += "?" + values.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
// 计算签名
|
||||
stringToSign := verb + "\n" +
|
||||
date + "\n" +
|
||||
canonicalizedResource
|
||||
h := hmac.New(sha256.New, []byte(secretAccessKey))
|
||||
h.Write([]byte(stringToSign))
|
||||
sign := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Date", date)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("QC-HMAC-SHA256 %s:%s", accessKeyId, sign))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &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("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
req := c.client.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
// WARN:
|
||||
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
|
||||
|
||||
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 apiResponse) (*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", err)
|
||||
} else {
|
||||
if tcode := res.GetCode(); tcode != 0 {
|
||||
return resp, fmt.Errorf("sdkerr: code='%d', message='%s'", tcode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dns
|
||||
|
||||
type apiResponse interface {
|
||||
GetCode() int
|
||||
GetMessage() string
|
||||
}
|
||||
|
||||
type apiResponseBase struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (r *apiResponseBase) GetCode() int {
|
||||
if r.Code == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return *r.Code
|
||||
}
|
||||
|
||||
func (r *apiResponseBase) GetMessage() string {
|
||||
if r.Message == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.Message
|
||||
}
|
||||
|
||||
var _ apiResponse = (*apiResponseBase)(nil)
|
||||
|
||||
type DnsRecord struct {
|
||||
GroupId *int64 `json:"group_id,omitempty"`
|
||||
GroupStatus *int32 `json:"group_status,omitempty"`
|
||||
Value []*DnsRecordValue `json:"value,omitempty"`
|
||||
Weight *int32 `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
type DnsRecordValue struct {
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
Line *string `json:"line,omitempty"`
|
||||
Ttl *int32 `json:"ttl,omitempty"`
|
||||
}
|
||||
@@ -73,8 +73,7 @@ func NewClient(agentId, appSecret string) (*Client, error) {
|
||||
requestMethod + "\n" +
|
||||
urlPath + "\n" +
|
||||
requestBody
|
||||
key := []byte(appSecret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h := hmac.New(sha256.New, []byte(appSecret))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M222.293333 210.218667l0.128 0.426666v334.549334l289.109334 166.997333h0.896l289.109333-166.997333 0.085333-0.426667-0.085333-334.122667 0.085333-0.426666L512.426667 42.666667h-0.896L222.421333 209.792l-0.128 0.426667zM42.666667 981.333333h938.666666v-187.818666H42.666667V981.333333z" fill="#0FA06D"></path></svg>
|
||||
@@ -72,6 +72,7 @@ import AccessConfigFieldsProviderOVHcloud from "./AccessConfigFieldsProviderOVHc
|
||||
import AccessConfigFieldsProviderPorkbun from "./AccessConfigFieldsProviderPorkbun";
|
||||
import AccessConfigFieldsProviderPowerDNS from "./AccessConfigFieldsProviderPowerDNS";
|
||||
import AccessConfigFieldsProviderProxmoxVE from "./AccessConfigFieldsProviderProxmoxVE";
|
||||
import AccessConfigFieldsProviderQingCloud from "./AccessConfigFieldsProviderQingCloud";
|
||||
import AccessConfigFieldsProviderQiniu from "./AccessConfigFieldsProviderQiniu";
|
||||
import AccessConfigFieldsProviderRainYun from "./AccessConfigFieldsProviderRainYun";
|
||||
import AccessConfigFieldsProviderRatPanel from "./AccessConfigFieldsProviderRatPanel";
|
||||
@@ -173,6 +174,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.PORKBUN]: AccessConfigFieldsProviderPorkbun,
|
||||
[ACCESS_PROVIDERS.POWERDNS]: AccessConfigFieldsProviderPowerDNS,
|
||||
[ACCESS_PROVIDERS.PROXMOXVE]: AccessConfigFieldsProviderProxmoxVE,
|
||||
[ACCESS_PROVIDERS.QINGCLOUD]: AccessConfigFieldsProviderQingCloud,
|
||||
[ACCESS_PROVIDERS.QINIU]: AccessConfigFieldsProviderQiniu,
|
||||
[ACCESS_PROVIDERS.RAINYUN]: AccessConfigFieldsProviderRainYun,
|
||||
[ACCESS_PROVIDERS.RATPANEL]: AccessConfigFieldsProviderRatPanel,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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 AccessConfigFormFieldsProviderQingCloud = () => {
|
||||
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.qingcloud_access_key_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qingcloud_access_key_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.qingcloud_access_key_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "secretAccessKey"]}
|
||||
initialValue={initialValues.secretAccessKey}
|
||||
label={t("access.form.qingcloud_secret_access_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qingcloud_secret_access_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.qingcloud_secret_access_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
accessKeyId: z.string().nonempty(t("access.form.qingcloud_access_key_id.placeholder")),
|
||||
secretAccessKey: z.string().nonempty(t("access.form.qingcloud_secret_access_key.placeholder")),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderQingCloud, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -88,6 +88,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
PORKBUN: "porkbun",
|
||||
POWERDNS: "powerdns",
|
||||
PROXMOXVE: "proxmoxve",
|
||||
QINGCLOUD: "qingcloud",
|
||||
QINIU: "qiniu",
|
||||
RAINYUN: "rainyun",
|
||||
RATPANEL: "ratpanel",
|
||||
@@ -213,6 +214,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.VULTR, "provider.vultr", "/imgs/providers/vultr.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.QINGCLOUD, "provider.qingcloud", "/imgs/providers/qingcloud.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS["35CN"], "provider.35cn", "/imgs/providers/35cn.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.XINNET, "provider.xinnet", "/imgs/providers/xinnet.png", [ACCESS_USAGES.DNS]],
|
||||
@@ -365,9 +367,12 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
||||
OVHCLOUD: `${ACCESS_PROVIDERS.OVHCLOUD}`,
|
||||
PORKBUN: `${ACCESS_PROVIDERS.PORKBUN}`,
|
||||
POWERDNS: `${ACCESS_PROVIDERS.POWERDNS}`,
|
||||
QINGCLOUD: `${ACCESS_PROVIDERS.QINGCLOUD}`, // 兼容旧值,等同于 `QINGCLOUD_DNS`
|
||||
QINGCLOUD_DNS: `${ACCESS_PROVIDERS.QINGCLOUD}-dns`,
|
||||
RAINYUN: `${ACCESS_PROVIDERS.RAINYUN}`,
|
||||
RFC2136: `${ACCESS_PROVIDERS.RFC2136}`,
|
||||
SPACESHIP: `${ACCESS_PROVIDERS.SPACESHIP}`,
|
||||
UCLOUD: `${ACCESS_PROVIDERS.UCLOUD}`, // 兼容旧值,等同于 `UCLOUD_UDNR`
|
||||
UCLOUD_UDNR: `${ACCESS_PROVIDERS.UCLOUD}-udnr`,
|
||||
TECHNITIUMDNS: `${ACCESS_PROVIDERS.TECHNITIUMDNS}`,
|
||||
TENCENTCLOUD: `${ACCESS_PROVIDERS.TENCENTCLOUD}`, // 兼容旧值,等同于 `TENCENTCLOUD_DNS`
|
||||
@@ -441,6 +446,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
||||
[ACME_DNS01_PROVIDERS.CTCCCLOUD_SMARTDNS, "provider.ctcccloud.smartdns"],
|
||||
[ACME_DNS01_PROVIDERS.RAINYUN, "provider.rainyun"],
|
||||
[ACME_DNS01_PROVIDERS.UCLOUD_UDNR, "provider.ucloud.udnr"],
|
||||
[ACME_DNS01_PROVIDERS.QINGCLOUD_DNS, "provider.qingcloud.dns"],
|
||||
[ACME_DNS01_PROVIDERS.WESTCN, "provider.westcn"],
|
||||
[ACME_DNS01_PROVIDERS["35CN"], "provider.35cn"],
|
||||
[ACME_DNS01_PROVIDERS.XINNET, "provider.xinnet"],
|
||||
|
||||
@@ -471,6 +471,12 @@
|
||||
"access.form.proxmoxve_api_token_secret.label": "Proxmox VE API token secret (Optional)",
|
||||
"access.form.proxmoxve_api_token_secret.placeholder": "Please enter Proxmox VE API token secret",
|
||||
"access.form.proxmoxve_api_token_secret.tooltip": "For more information, see <a href=\"https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens\" target=\"_blank\">https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens</a>",
|
||||
"access.form.qingcloud_access_key_id.label": "QingCloud AccessKeyID",
|
||||
"access.form.qingcloud_access_key_id.placeholder": "Please enter QingCloud AccessKeyID",
|
||||
"access.form.qingcloud_access_key_id.tooltip": "For more information, see <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>",
|
||||
"access.form.qingcloud_secret_access_key.label": "QingCloud SecretAccessKey",
|
||||
"access.form.qingcloud_secret_access_key.placeholder": "Please enter QingCloud SecretAccessKey",
|
||||
"access.form.qingcloud_secret_access_key.tooltip": "For more information, see <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>",
|
||||
"access.form.qiniu_access_key.label": "Qiniu AccessKey",
|
||||
"access.form.qiniu_access_key.placeholder": "Please enter Qiniu AccessKey",
|
||||
"access.form.qiniu_access_key.tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
"provider.porkbun": "Porkbun",
|
||||
"provider.powerdns": "PowerDNS",
|
||||
"provider.proxmoxve": "Proxmox VE",
|
||||
"provider.qingcloud": "QingCloud",
|
||||
"provider.qingcloud.dns": "QingCloud - DNS",
|
||||
"provider.qiniu": "Qiniu",
|
||||
"provider.qiniu.cdn": "Qiniu - CDN (Content Delivery Network)",
|
||||
"provider.qiniu.kodo": "Qiniu - Kodo",
|
||||
|
||||
@@ -470,6 +470,12 @@
|
||||
"access.form.proxmoxve_api_token_secret.label": "Proxmox VE API Token Secret(可选)",
|
||||
"access.form.proxmoxve_api_token_secret.placeholder": "请输入 Proxmox VE API Token Secret",
|
||||
"access.form.proxmoxve_api_token_secret.tooltip": "这是什么?请参阅 <a href=\"https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens\" target=\"_blank\">https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens</a>",
|
||||
"access.form.qingcloud_access_key_id.label": "青云 AccessKeyID",
|
||||
"access.form.qingcloud_access_key_id.placeholder": "请输入青云 AccessKeyID",
|
||||
"access.form.qingcloud_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>",
|
||||
"access.form.qingcloud_secret_access_key.label": "青云 SecretAccessKey",
|
||||
"access.form.qingcloud_secret_access_key.placeholder": "请输入青云 SecretAccessKey",
|
||||
"access.form.qingcloud_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://console.qingcloud.com/access_keys/\" target=\"_blank\">https://console.qingcloud.com/access_keys/</a>",
|
||||
"access.form.qiniu_access_key.label": "七牛云 AccessKey",
|
||||
"access.form.qiniu_access_key.placeholder": "请输入七牛云 AccessKey",
|
||||
"access.form.qiniu_access_key.tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
"provider.porkbun": "Porkbun",
|
||||
"provider.powerdns": "PowerDNS",
|
||||
"provider.proxmoxve": "Proxmox VE",
|
||||
"provider.qingcloud": "青云",
|
||||
"provider.qingcloud.dns": "青云 - 云解析 DNS",
|
||||
"provider.qiniu": "七牛云",
|
||||
"provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
|
||||
"provider.qiniu.kodo": "七牛云 - 对象存储 Kodo",
|
||||
|
||||
Reference in New Issue
Block a user