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: conohavps
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package certifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
conohavpsv2 "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/conohavpsv2"
|
||||
conohavpsv3 "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/conohavpsv3"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeConoHaVPS, func(options *ProviderFactoryOptions) (core.ACMEChallenger, error) {
|
||||
credentials := domain.AccessConfigForConoHaVPS{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
switch credentials.ApiVersion {
|
||||
case "2", "2.0", "v2", "v2.0":
|
||||
return conohavpsv2.NewChallenger(&conohavpsv2.ChallengerConfig{
|
||||
ApiUserName: credentials.ApiUserName,
|
||||
ApiPassword: credentials.ApiPassword,
|
||||
TenantId: credentials.TenantId,
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
case "3", "3.0", "v3", "v3.0":
|
||||
return conohavpsv3.NewChallenger(&conohavpsv3.ChallengerConfig{
|
||||
ApiUserId: credentials.ApiUserId,
|
||||
ApiUserName: credentials.ApiUserName,
|
||||
ApiPassword: credentials.ApiPassword,
|
||||
TenantId: credentials.TenantId,
|
||||
TenantName: credentials.TenantName,
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("conohavps: unsupported api version: '%s'", credentials.ApiVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -160,6 +160,15 @@ type AccessConfigForCMCCCloud struct {
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type AccessConfigForConoHaVPS struct {
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
ApiUserId string `json:"apiUserId,omitempty"`
|
||||
ApiUserName string `json:"apiUserName"`
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
TenantId string `json:"tenantId"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
}
|
||||
|
||||
type AccessConfigForConstellix struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
|
||||
@@ -39,6 +39,7 @@ const (
|
||||
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
|
||||
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
|
||||
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
|
||||
AccessProviderTypeConoHaVPS = AccessProviderType("conohavps")
|
||||
AccessProviderTypeConstellix = AccessProviderType("constellix")
|
||||
AccessProviderTypeCPanel = AccessProviderType("cpanel")
|
||||
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud")
|
||||
@@ -202,6 +203,7 @@ const (
|
||||
ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS)
|
||||
ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCMCCCloudDNS]
|
||||
ACMEDns01ProviderTypeCMCCCloudDNS = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud + "-dns")
|
||||
ACMEDns01ProviderTypeConoHaVPS = ACMEDns01ProviderType(AccessProviderTypeConoHaVPS)
|
||||
ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix)
|
||||
ACMEDns01ProviderTypeCPanel = ACMEDns01ProviderType(AccessProviderTypeCPanel)
|
||||
ACMEDns01ProviderTypeCTCCCloud = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeCTCCCloudSmartDNS]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package conohavpsv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/providers/dns/conoha"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier"
|
||||
)
|
||||
|
||||
type ChallengerConfig struct {
|
||||
ApiUserName string `json:"apiUserName"`
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
TenantId string `json:"tenantId"`
|
||||
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the acme challenge provider is nil")
|
||||
}
|
||||
|
||||
providerConfig := conoha.NewDefaultConfig()
|
||||
providerConfig.Username = config.ApiUserName
|
||||
providerConfig.Password = config.ApiPassword
|
||||
providerConfig.TenantID = config.TenantId
|
||||
if config.DnsPropagationTimeout != 0 {
|
||||
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||
}
|
||||
if config.DnsTTL != 0 {
|
||||
providerConfig.TTL = config.DnsTTL
|
||||
}
|
||||
|
||||
provider, err := conoha.NewDNSProviderConfig(providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package conohavpsv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier"
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/conohavpsv3/internal"
|
||||
)
|
||||
|
||||
type ChallengerConfig struct {
|
||||
ApiUserId string `json:"apiUserId"`
|
||||
ApiUserName string `json:"apiUserName"`
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
TenantId string `json:"tenantId"`
|
||||
TenantName string `json:"tenantName"`
|
||||
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the acme challenge provider is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.NewDefaultConfig()
|
||||
providerConfig.UserID = config.ApiUserId
|
||||
providerConfig.UserName = config.ApiUserName
|
||||
providerConfig.Password = config.ApiPassword
|
||||
providerConfig.TenantID = config.TenantId
|
||||
providerConfig.TenantName = config.TenantName
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/challenge"
|
||||
"github.com/go-acme/lego/v5/challenge/dns01"
|
||||
"github.com/go-acme/lego/v5/platform/env"
|
||||
"github.com/samber/lo"
|
||||
|
||||
conohavpssdk "github.com/certimate-go/certimate/pkg/sdk3rd/conoha/vps/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "CONOHAV3_"
|
||||
|
||||
EnvAPIUserID = envNamespace + "API_USER_ID"
|
||||
EnvAPIUserName = envNamespace + "API_USER_NAME"
|
||||
EnvAPIPassword = envNamespace + "API_PASSWORD"
|
||||
EnvTenantID = envNamespace + "TENANT_ID"
|
||||
EnvTenantName = envNamespace + "TENANT_NAME"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
UserID string
|
||||
UserName string
|
||||
Password string
|
||||
TenantID string
|
||||
TenantName string
|
||||
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *conohavpssdk.Client
|
||||
|
||||
zoneIDs map[string]string // Key: ZoneFQDN; Value: ZoneUUID
|
||||
zoneIDsMu sync.Mutex
|
||||
recordIDs map[string]string // Key: ChallengeToken; Value: RecordUUID
|
||||
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(EnvAPIUserID, EnvAPIUserName, EnvAPIPassword, EnvTenantID, EnvTenantName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conohavpsv3: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.UserID = values[EnvAPIUserID]
|
||||
config.UserName = values[EnvAPIUserName]
|
||||
config.Password = values[EnvAPIPassword]
|
||||
config.TenantID = values[EnvTenantID]
|
||||
config.TenantName = values[EnvTenantName]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("conohavpsv3: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := conohavpssdk.NewClient(config.UserID, config.UserName, config.Password, config.TenantID, config.TenantName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conohavpsv3: %w", err)
|
||||
} else {
|
||||
client.SetTimeout(config.HTTPTimeout)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
zoneIDs: make(map[string]string),
|
||||
zoneIDsMu: sync.Mutex{},
|
||||
recordIDs: make(map[string]string),
|
||||
recordIDsMu: sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Present(ctx context.Context, domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(ctx, domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.DefaultClient().FindZoneByFqdn(ctx, info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conohavpsv3: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
zoneInfo, err := d.findZone(ctx, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conohavpsv3: error when list zones: %w", err)
|
||||
}
|
||||
|
||||
// REF: https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-create_record-v3/
|
||||
response, err := d.client.DnsCreateRecordWithContext(ctx, zoneInfo.UUID, &conohavpssdk.DnsCreateRecordRequest{
|
||||
Name: lo.ToPtr(info.EffectiveFQDN),
|
||||
Type: lo.ToPtr("TXT"),
|
||||
Data: lo.ToPtr(info.Value),
|
||||
TTL: lo.ToPtr(d.config.TTL),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("conohavpsv3: error when create record: %w", err)
|
||||
}
|
||||
|
||||
d.zoneIDsMu.Lock()
|
||||
d.zoneIDs[authZone] = zoneInfo.UUID
|
||||
d.zoneIDsMu.Unlock()
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[token] = response.UUID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(ctx context.Context, domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(ctx, domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.DefaultClient().FindZoneByFqdn(ctx, info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conohavpsv3: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
d.zoneIDsMu.Lock()
|
||||
zoneId, ok := d.zoneIDs[authZone]
|
||||
d.zoneIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("conohavpsv3: unknown zone ID for '%s'", authZone)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordId, ok := d.recordIDs[token]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("conohavpsv3: unknown record ID for '%s'", info.EffectiveFQDN)
|
||||
}
|
||||
|
||||
if _, err := d.client.DnsDeleteRecordWithContext(ctx, zoneId, recordId); err != nil {
|
||||
return fmt.Errorf("conohavpsv3: error when delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*conohavpssdk.DnsDomainRecord, error) {
|
||||
offset := 0
|
||||
limit := 10
|
||||
for {
|
||||
// REF: https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-get_domains_list-v3/
|
||||
request := &conohavpssdk.DnsGetDomainsListRequest{
|
||||
Offset: lo.ToPtr(offset),
|
||||
Limit: lo.ToPtr(limit),
|
||||
}
|
||||
response, err := d.client.DnsGetDomainsListWithContext(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, domainItem := range response.Domains {
|
||||
if dns01.UnFqdn(domainItem.Name) == dns01.UnFqdn(zoneName) {
|
||||
return domainItem, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(response.Domains) < limit || offset+limit >= response.TotalCount {
|
||||
break
|
||||
}
|
||||
|
||||
offset += limit
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find zone '%s'", zoneName)
|
||||
}
|
||||
@@ -32,9 +32,9 @@ type Config struct {
|
||||
AccessKeyId string
|
||||
SecretAccessKey string
|
||||
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
type Config struct {
|
||||
HTTPToken string
|
||||
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ type DNSProvider struct {
|
||||
config *Config
|
||||
client *dynv6sdk.Client
|
||||
|
||||
zoneIDs map[string]int64 // Key: ZoneName; Value: ZoneID
|
||||
zoneIDs map[string]int64 // Key: ZoneFQDN; Value: ZoneID
|
||||
zoneIDsMu sync.Mutex
|
||||
recordIDs map[string]int64 // Key: ChallengeToken; Value: RecordID
|
||||
recordIDsMu sync.Mutex
|
||||
@@ -102,13 +102,13 @@ func (d *DNSProvider) Present(ctx context.Context, domain, token, keyAuth string
|
||||
return fmt.Errorf("dynv6: %w", err)
|
||||
}
|
||||
|
||||
zone, err := d.findZone(ctx, dns01.UnFqdn(authZone))
|
||||
zoneInfo, err := d.findZone(ctx, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dynv6: error when list zones: %w", err)
|
||||
}
|
||||
|
||||
// REF: https://dynv6.github.io/api-spec/#tag/records/operation/addRecord
|
||||
response, err := d.client.AddRecordWithContext(ctx, zone.ID, &dynv6sdk.AddRecordRequest{
|
||||
response, err := d.client.AddRecordWithContext(ctx, zoneInfo.ID, &dynv6sdk.AddRecordRequest{
|
||||
Type: lo.ToPtr("TXT"),
|
||||
Name: lo.ToPtr(subDomain),
|
||||
Data: lo.ToPtr(info.Value),
|
||||
@@ -118,7 +118,7 @@ func (d *DNSProvider) Present(ctx context.Context, domain, token, keyAuth string
|
||||
}
|
||||
|
||||
d.zoneIDsMu.Lock()
|
||||
d.zoneIDs[zone.Name] = zone.ID
|
||||
d.zoneIDs[authZone] = zoneInfo.ID
|
||||
d.zoneIDsMu.Unlock()
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
@@ -137,10 +137,10 @@ func (d *DNSProvider) CleanUp(ctx context.Context, domain, token, keyAuth string
|
||||
}
|
||||
|
||||
d.zoneIDsMu.Lock()
|
||||
zoneId, ok := d.zoneIDs[dns01.UnFqdn(authZone)]
|
||||
zoneId, ok := d.zoneIDs[authZone]
|
||||
d.zoneIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("dynv6: unknown zone ID for '%s'", dns01.UnFqdn(authZone))
|
||||
return fmt.Errorf("dynv6: unknown zone ID for '%s'", authZone)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
@@ -169,7 +169,7 @@ func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*dynv6sdk.
|
||||
}
|
||||
|
||||
for _, zone := range *zones {
|
||||
if zone.Name == zoneName {
|
||||
if dns01.UnFqdn(zone.Name) == dns01.UnFqdn(zoneName) {
|
||||
return zone, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ type Config struct {
|
||||
AccessKey string
|
||||
AccessSecret string
|
||||
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type DnsCreateRecordRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Data *string `json:"data,omitempty"`
|
||||
TTL *int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type DnsCreateRecordResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
UUID string `json:"uuid"`
|
||||
DomainUUID string `json:"domain_uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (c *Client) DnsCreateRecord(domainId string, req *DnsCreateRecordRequest) (*DnsCreateRecordResponse, error) {
|
||||
return c.DnsCreateRecordWithContext(context.Background(), domainId, req)
|
||||
}
|
||||
|
||||
func (c *Client) DnsCreateRecordWithContext(ctx context.Context, domainId string, req *DnsCreateRecordRequest) (*DnsCreateRecordResponse, error) {
|
||||
if domainId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset domainId")
|
||||
}
|
||||
|
||||
if err := c.ensureAccessTokenExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, fmt.Sprintf("%s/v1/domains/%s/records", dnsBaseURL, url.PathEscape(domainId)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DnsCreateRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type DnsDeleteRecordResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) DnsDeleteRecord(domainId string, recordId string) (*DnsDeleteRecordResponse, error) {
|
||||
return c.DnsDeleteRecordWithContext(context.Background(), domainId, recordId)
|
||||
}
|
||||
|
||||
func (c *Client) DnsDeleteRecordWithContext(ctx context.Context, domainId string, recordId string) (*DnsDeleteRecordResponse, error) {
|
||||
if domainId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset domainId")
|
||||
}
|
||||
if recordId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset recordId")
|
||||
}
|
||||
|
||||
if err := c.ensureAccessTokenExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodDelete, fmt.Sprintf("%s/v1/domains/%s/records/%s", dnsBaseURL, url.PathEscape(domainId), url.PathEscape(recordId)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DnsDeleteRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
qs "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
type DnsGetDomainsListRequest struct {
|
||||
Limit *int `json:"limit,omitempty" url:"limit,omitempty"`
|
||||
Offset *int `json:"offset,omitempty" url:"offset,omitempty"`
|
||||
SortType *string `json:"sort_type,omitempty" url:"sort_type,omitempty"`
|
||||
SortKey *string `json:"sort_key,omitempty" url:"sort_key,omitempty"`
|
||||
}
|
||||
|
||||
type DnsGetDomainsListResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Domains []*DnsDomainRecord `json:"domains,omitempty"`
|
||||
TotalCount int `json:"total_count,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) DnsGetDomainsList(req *DnsGetDomainsListRequest) (*DnsGetDomainsListResponse, error) {
|
||||
return c.DnsGetDomainsListWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DnsGetDomainsListWithContext(ctx context.Context, req *DnsGetDomainsListRequest) (*DnsGetDomainsListResponse, error) {
|
||||
if err := c.ensureAccessTokenExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodGet, fmt.Sprintf("%s/v1/domains", dnsBaseURL))
|
||||
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 := &DnsGetDomainsListResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
userId string
|
||||
userName string
|
||||
userPassword string
|
||||
tenantId string
|
||||
tenantName string
|
||||
|
||||
accessToken string
|
||||
accessTokenExp time.Time
|
||||
accessTokenMtx sync.Mutex
|
||||
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(userId, userName, userPassword, tenantId, tenantName string) (*Client, error) {
|
||||
if userId == "" && userName == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset userId or userName")
|
||||
}
|
||||
if userPassword == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset userPassword")
|
||||
}
|
||||
if tenantId == "" || tenantName == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset tenantId or tenantName")
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
userId: userId,
|
||||
userName: userName,
|
||||
userPassword: userPassword,
|
||||
tenantId: tenantId,
|
||||
tenantName: tenantName,
|
||||
}
|
||||
client.client = resty.New().
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
if client.accessToken != "" {
|
||||
req.Header.Set("X-Auth-Token", client.accessToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
|
||||
c.client.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.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 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 tcode := res.GetCode(); tcode == 0 {
|
||||
return resp, fmt.Errorf("sdkerr: code='%d', error='%s'", tcode, res.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) ensureAccessTokenExists() error {
|
||||
c.accessTokenMtx.Lock()
|
||||
defer c.accessTokenMtx.Unlock()
|
||||
if c.accessToken != "" && c.accessTokenExp.After(time.Now()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, fmt.Sprintf("%s/v3/auth/tokens", identityBaseURL))
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
authUserParams := map[string]string{"password": c.userPassword}
|
||||
if c.userId != "" {
|
||||
authUserParams["id"] = c.userId
|
||||
}
|
||||
if c.userName != "" {
|
||||
authUserParams["name"] = c.userName
|
||||
}
|
||||
|
||||
authProjectParams := map[string]string{}
|
||||
if c.tenantId != "" {
|
||||
authProjectParams["id"] = c.tenantId
|
||||
}
|
||||
if c.tenantName != "" {
|
||||
authProjectParams["name"] = c.tenantName
|
||||
}
|
||||
|
||||
httpreq.SetBody(map[string]any{
|
||||
"auth": map[string]any{
|
||||
"identity": map[string]any{
|
||||
"methods": []string{"password"},
|
||||
"password": map[string]any{
|
||||
"user": authUserParams,
|
||||
},
|
||||
"scope": map[string]any{
|
||||
"project": authProjectParams,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type createAuthTokenResponse struct {
|
||||
sdkResponseBase
|
||||
Token *struct {
|
||||
IssuedAt string `json:"issued_at"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
} `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
result := &createAuthTokenResponse{}
|
||||
if httpresp, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return err
|
||||
} else if code := result.GetCode(); code != 0 {
|
||||
return fmt.Errorf("sdkerr: failed to get conoha access token: code='%d', error='%s'", code, result.GetError())
|
||||
} else {
|
||||
token := httpresp.Header().Get("X-Subject-Token")
|
||||
if token == "" {
|
||||
return fmt.Errorf("sdkerr: api error: received empty auth token")
|
||||
}
|
||||
|
||||
tokenExp, err := time.Parse(time.RFC3339Nano, result.Token.ExpiresAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sdkerr: api error: received invalid auth token expiration: %w", err)
|
||||
}
|
||||
|
||||
c.accessToken = token
|
||||
c.accessTokenExp = tokenExp
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package v3
|
||||
|
||||
import "fmt"
|
||||
|
||||
const region = "c3j1"
|
||||
|
||||
var (
|
||||
identityBaseURL = fmt.Sprintf("https://identity.%s.conoha.io", region)
|
||||
dnsBaseURL = fmt.Sprintf("https://dns-service.%s.conoha.io", region)
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
package v3
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
GetError() string
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetCode() int {
|
||||
if r.Code == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return *r.Code
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetError() string {
|
||||
if r.Error == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.Error
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
|
||||
type DnsDomainRecord struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type SSLInstallSSLRequest struct {
|
||||
Domain *string `url:"domain,omitempty"`
|
||||
Cert *string `url:"cert,omitempty"`
|
||||
Key *string `url:"key,omitempty"`
|
||||
CABundle *string `url:"cabundle,omitempty"`
|
||||
Domain *string `json:"domain,omitempty" url:"domain,omitempty"`
|
||||
Cert *string `json:"cert,omitempty" url:"cert,omitempty"`
|
||||
Key *string `json:"key,omitempty" url:"key,omitempty"`
|
||||
CABundle *string `json:"cabundle,omitempty" url:"cabundle,omitempty"`
|
||||
}
|
||||
|
||||
type SSLInstallSSLResponse struct {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" version="1.1" x="0" y="0" viewBox="0 0 652 652" style="enable-background:new 0 0 652 652;" width="200" height="200"><style type="text/css">.st0{fill:#005BAC;}.st1{fill:#12B8D7;}</style><path class="st0" d="M223.9,393.3c-1.3,0-2.7,0.4-4,1.3v-6.7h-3.1v19.3h3.1v-1.3c0.4,0.9,1.8,1.8,3.6,1.8c3.1,0,6.3-2.7,6.3-7.2 S227,393.3,223.9,393.3z M223.4,404.5c-0.4,0-2.2,0-3.1-2.7V400c0-2.7,1.8-4,3.1-4c1.8,0,3.6,1.3,3.1,4 C226.6,401.8,225.7,404.5,223.4,404.5z M239.6,393.7l-2.7,9.9l-3.1-9.9h-3.6l4.9,13.9c0,0.4-0.4,0.9-0.9,0.9 c-0.4,0.4-1.3,0.4-1.3,0.4H232v2.7h1.8c2.7,0,3.6-0.9,4.5-3.6l0.9-2.7l3.6-11.7H239.6z M319.4,405.9h2.2l8.1-13l4.5,15.2h9l-9-28.3 h-3.6l-10.8,16.6l-10.8-16.6h-3.6l-9,28.3h9l4.5-15.2l8.1,13C318.1,405.9,319.4,405.9,319.4,405.9z M296.5,392h-24.2 c-1.3,0-2.2,0.9-2.2,2.2s0.9,2.2,2.2,2.2h14.3c-1.8,4.5-7.6,7.6-14.3,7.6c-8.1,0-14.8-4.9-14.8-10.8c0-5.8,6.7-10.3,14.8-10.3 c4.5,0,8.5,1.3,11.2,3.6h9.9c-4-4.5-12.1-7.6-21.1-7.6c-13.5,0-24.2,6.7-24.2,14.8c0,8.1,10.8,14.8,24.2,14.8s24.2-6.7,24.2-14.8 C296.5,393.7,296.5,392,296.5,392z M366.5,379c-13.5,0-24.2,6.7-24.2,14.8c0,8.1,10.8,14.8,24.2,14.8s24.2-6.7,24.2-14.8 C391.2,385.7,379.9,379,366.5,379z M366.5,404.1c-8.1,0-14.8-4.9-14.8-10.3c0-5.8,6.7-10.3,14.8-10.3c8.1,0,14.8,4.9,14.8,10.3 C381.3,399.6,375,404.1,366.5,404.1z"/><path class="st1" d="M153.9,349.8l-0.4,0.9c-10.3,15.2-27.4,24.2-45.3,24.2c-30,0-54.7-24.7-54.7-54.7c0-30,24.7-54.7,54.7-54.7 c17.9,0,35,9,45.3,24.2l0.4,0.9c5.4-9.9,13.5-17.9,23.3-23.8c-16.6-22-42.2-34.5-69.1-34.5c-48.4,0-87.9,39.5-87.9,87.9 s39.5,87.9,87.9,87.9c27.4,0,52.5-12.6,69.1-34.1C167.8,368.2,159.8,359.7,153.9,349.8z"/><path class="st1" d="M593.8,274.5v4c-5.8-2.7-12.6-4-19.3-4c-17,0-31.8,9.4-39.5,23.3v-65.5h-32.7v71.3H449v-71.3h-32.7v65.5 c-8.1-13.9-22.9-22.9-39.5-22.9c-19.3,0-35.9,12.1-42.6,29.6c-6.7-17-23.3-29.6-42.6-29.6S255.7,287,249,304.1 c-6.7-17-23.3-29.1-42.6-29.1c-25.1,0-45.3,20.2-45.3,45.3s20.6,45.3,45.3,45.3c17,0,31.8-9.4,39.5-23.3v23.8h26.5v-45.7 c0-10.3,8.5-18.8,18.8-18.8s18.8,8.5,18.8,18.8v46.2h26.5v-23.8c7.6,13.9,22.9,23.3,39.5,23.3c17,0,31.8-9.4,39.5-22.9v65.5H449 v-72.2h53.4v71.3h32.7v-65.5c7.6,13.9,22.9,23.3,39.5,23.3c6.7,0,13-1.3,19.3-4v4h26.5v-91H593.8z M206.4,339 c-10.3,0-18.8-8.5-18.8-18.8s8.5-18.8,18.8-18.8c10.3,0,18.8,8.5,18.8,18.8C225.7,330.5,217.2,339,206.4,339z M376.8,339 c-10.3,0-18.8-8.5-18.8-18.8s8.5-18.8,18.8-18.8c10.3,0,18.8,8.5,18.8,18.8S387.1,339,376.8,339z M575,339 c-10.3,0-18.8-8.5-18.8-18.8s8.5-18.8,18.8-18.8c10.3,0,18.8,8.5,18.8,18.8S585.3,339,575,339z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -28,6 +28,7 @@ import AccessConfigFieldsProviderCdnfly from "./AccessConfigFieldsProviderCdnfly
|
||||
import AccessConfigFieldsProviderCloudflare from "./AccessConfigFieldsProviderCloudflare";
|
||||
import AccessConfigFieldsProviderClouDNS from "./AccessConfigFieldsProviderClouDNS";
|
||||
import AccessConfigFieldsProviderCMCCCloud from "./AccessConfigFieldsProviderCMCCCloud";
|
||||
import AccessConfigFieldsProviderConoHaVPS from "./AccessConfigFieldsProviderConoHaVPS";
|
||||
import AccessConfigFieldsProviderConstellix from "./AccessConfigFieldsProviderConstellix";
|
||||
import AccessConfigFieldsProviderCPanel from "./AccessConfigFieldsProviderCPanel";
|
||||
import AccessConfigFieldsProviderCTCCCloud from "./AccessConfigFieldsProviderCTCCCloud";
|
||||
@@ -146,6 +147,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.CLOUDFLARE]: AccessConfigFieldsProviderCloudflare,
|
||||
[ACCESS_PROVIDERS.CLOUDNS]: AccessConfigFieldsProviderClouDNS,
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD]: AccessConfigFieldsProviderCMCCCloud,
|
||||
[ACCESS_PROVIDERS.CONOHAVPS]: AccessConfigFieldsProviderConoHaVPS,
|
||||
[ACCESS_PROVIDERS.CONSTELLIX]: AccessConfigFieldsProviderConstellix,
|
||||
[ACCESS_PROVIDERS.CPANEL]: AccessConfigFieldsProviderCPanel,
|
||||
[ACCESS_PROVIDERS.CTCCCLOUD]: AccessConfigFieldsProviderCTCCCloud,
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const API_VERSION_V2 = "v2" as const;
|
||||
const API_VERSION_V3 = "v3" as const;
|
||||
|
||||
const AccessConfigFormFieldsProviderConoHaVPS = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance<z.infer<typeof formSchema>>();
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
const fieldApiVersion = Form.useWatch([parentNamePath, "apiVersion"], formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiVersion"]}
|
||||
initialValue={initialValues.apiVersion}
|
||||
label={t("access.form.conohavps_api_version.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={[API_VERSION_V2, API_VERSION_V3].map((s) => ({ label: s, value: s }))}
|
||||
placeholder={t("access.form.conohavps_api_version.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Show>
|
||||
<Show.Case when={fieldApiVersion === API_VERSION_V2}>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiUserName"]}
|
||||
initialValue={initialValues.apiUserName}
|
||||
label={t("access.form.conohavps_api_v2_username.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v2_username.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.conohavps_api_v2_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiPassword"]}
|
||||
initialValue={initialValues.apiPassword}
|
||||
label={t("access.form.conohavps_api_v2_password.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v2_password.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.conohavps_api_v2_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "tenantId"]}
|
||||
initialValue={initialValues.tenantId}
|
||||
label={t("access.form.conohavps_api_v2_tenant_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v2_tenant_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.conohavps_api_v2_tenant_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show.Case>
|
||||
|
||||
<Show.Case when={fieldApiVersion === API_VERSION_V3}>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiUserId"]}
|
||||
initialValue={initialValues.apiUserId}
|
||||
label={t("access.form.conohavps_api_v3_user_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v3_user_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.conohavps_api_v3_user_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiUserName"]}
|
||||
initialValue={initialValues.apiUserName}
|
||||
label={t("access.form.conohavps_api_v3_user_name.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v3_user_name.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.conohavps_api_v3_user_name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiPassword"]}
|
||||
initialValue={initialValues.apiPassword}
|
||||
label={t("access.form.conohavps_api_v3_password.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v3_password.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.conohavps_api_v3_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "tenantId"]}
|
||||
initialValue={initialValues.tenantId}
|
||||
label={t("access.form.conohavps_api_v3_project_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v3_project_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.conohavps_api_v3_project_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "tenantName"]}
|
||||
initialValue={initialValues.tenantName}
|
||||
label={t("access.form.conohavps_api_v3_project_name.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.conohavps_api_v3_project_name.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.conohavps_api_v3_project_name.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show.Case>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
apiVersion: API_VERSION_V3,
|
||||
apiUserId: "",
|
||||
apiUserName: "",
|
||||
apiPassword: "",
|
||||
tenantId: "",
|
||||
tenantName: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z
|
||||
.object({
|
||||
apiVersion: z.enum([API_VERSION_V2, API_VERSION_V3]),
|
||||
apiUserId: z.string().nullish(),
|
||||
apiUserName: z.string().nullish(),
|
||||
apiPassword: z.string().nonempty(),
|
||||
tenantId: z.string().nullish(),
|
||||
tenantName: z.string().nullish(),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
switch (values.apiVersion) {
|
||||
case API_VERSION_V2:
|
||||
{
|
||||
const scUserName = z.string().nonempty();
|
||||
const spUserName = scUserName.safeParse(values.apiUserName);
|
||||
if (!spUserName.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spUserName.error).errors.join(),
|
||||
path: ["apiUserName"],
|
||||
});
|
||||
}
|
||||
|
||||
const scTenantId = z.string().nonempty();
|
||||
const spTenantId = scTenantId.safeParse(values.tenantId);
|
||||
if (!spTenantId.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spTenantId.error).errors.join(),
|
||||
path: ["tenantId"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case API_VERSION_V3:
|
||||
{
|
||||
const scUserId = z.string().nonempty();
|
||||
const spUserId = scUserId.safeParse(values.apiUserId);
|
||||
const scUserName = z.string().nonempty();
|
||||
const spUserName = scUserName.safeParse(values.apiUserName);
|
||||
if (!spUserId.success && !spUserName.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spUserName.error).errors.join(),
|
||||
path: ["apiUserName"],
|
||||
});
|
||||
} else if (spUserId.success && spUserName.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("access.form.conohavps_api_v3_user_id.errmsg.conflict"),
|
||||
path: ["apiUserId"],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("access.form.conohavps_api_v3_user_name.errmsg.conflict"),
|
||||
path: ["apiUserName"],
|
||||
});
|
||||
}
|
||||
|
||||
const scTenantId = z.string().nonempty();
|
||||
const spTenantId = scTenantId.safeParse(values.tenantId);
|
||||
const scTenantName = z.string().nonempty();
|
||||
const spTenantName = scTenantName.safeParse(values.tenantName);
|
||||
if (!spTenantId.success && !spTenantName.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: z.treeifyError(spTenantId.error).errors.join(),
|
||||
path: ["tenantId"],
|
||||
});
|
||||
} else if (spTenantId.success && spTenantName.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("access.form.conohavps_api_v3_project_id.errmsg.conflict"),
|
||||
path: ["tenantId"],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("access.form.conohavps_api_v3_project_name.errmsg.conflict"),
|
||||
path: ["tenantName"],
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderConoHaVPS, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -41,6 +41,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
CLOUDFLARE: "cloudflare",
|
||||
CLOUDNS: "cloudns",
|
||||
CMCCCLOUD: "cmcccloud",
|
||||
CONOHAVPS: "conohavps",
|
||||
CONSTELLIX: "constellix",
|
||||
CPANEL: "cpanel",
|
||||
CTCCCLOUD: "ctcccloud",
|
||||
@@ -213,6 +214,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.BOOKMYNAME, "provider.bookmyname", "/imgs/providers/bookmyname.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CLOUDNS, "provider.cloudns", "/imgs/providers/cloudns.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CONOHAVPS, "provider.conohavps", "/imgs/providers/conohavps.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CONSTELLIX, "provider.constellix", "/imgs/providers/constellix.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DESEC, "provider.desec", "/imgs/providers/desec.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DIGITALOCEAN, "provider.digitalocean", "/imgs/providers/digitalocean.svg", [ACCESS_USAGES.DNS]],
|
||||
@@ -375,6 +377,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
||||
CLOUDNS: `${ACCESS_PROVIDERS.CLOUDNS}`,
|
||||
CMCCCLOUD: `${ACCESS_PROVIDERS.CMCCCLOUD}`, // 兼容旧值,等同于 `CMCCCLOUD_DNS`
|
||||
CMCCCLOUD_DNS: `${ACCESS_PROVIDERS.CMCCCLOUD}-dns`,
|
||||
CONOHAVPS: `${ACCESS_PROVIDERS.CONOHAVPS}`,
|
||||
CONSTELLIX: `${ACCESS_PROVIDERS.CONSTELLIX}`,
|
||||
CPANEL: `${ACCESS_PROVIDERS.CPANEL}`,
|
||||
CTCCCLOUD: `${ACCESS_PROVIDERS.CTCCCLOUD}`, // 兼容旧值,等同于 `CTCCCLOUD_SMARTDNS`
|
||||
@@ -459,6 +462,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
||||
[ACME_DNS01_PROVIDERS.BUNNY, "provider.bunny"],
|
||||
[ACME_DNS01_PROVIDERS.CLOUDFLARE, "provider.cloudflare"],
|
||||
[ACME_DNS01_PROVIDERS.CLOUDNS, "provider.cloudns"],
|
||||
[ACME_DNS01_PROVIDERS.CONOHAVPS, "provider.conohavps"],
|
||||
[ACME_DNS01_PROVIDERS.CONSTELLIX, "provider.constellix"],
|
||||
[ACME_DNS01_PROVIDERS.DESEC, "provider.desec"],
|
||||
[ACME_DNS01_PROVIDERS.DIGITALOCEAN, "provider.digitalocean"],
|
||||
|
||||
@@ -377,6 +377,62 @@
|
||||
"placeholder": "Please enter CMCC ECloud AccessKeySecret",
|
||||
"tooltip": "For more information, see <a href=\"https://ecloud.10086.cn/op-help-center/doc/article/49739\" target=\"_blank\">https://ecloud.10086.cn/op-help-center/doc/article/49739</a>"
|
||||
},
|
||||
"conohavps_api_version": {
|
||||
"label": "ConoHa VPS version",
|
||||
"placeholder": "Please select ConoHa VPS version"
|
||||
},
|
||||
"conohavps_api_v2_username": {
|
||||
"label": "ConoHa VPS API username",
|
||||
"placeholder": "Please enter ConoHa VPS 2.0 API username",
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v2_password": {
|
||||
"label": "ConoHa VPS API password",
|
||||
"placeholder": "Please enter ConoHa VPS 2.0 API password",
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v2_tenant_id": {
|
||||
"label": "ConoHa VPS tenant ID",
|
||||
"placeholder": "Please enter ConoHa VPS 2.0 tenant ID",
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps2/api-identity-vps2/identity-post_tokens-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-identity-vps2/identity-post_tokens-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v3_user_id": {
|
||||
"label": "ConoHa VPS API user ID",
|
||||
"placeholder": "Please enter ConoHa VPS 3.0 API user ID",
|
||||
"errmsg": {
|
||||
"conflict": "Conflict with the \"API user name\", please retain only one field"
|
||||
},
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_user_name": {
|
||||
"label": "ConoHa VPS API user name",
|
||||
"placeholder": "Please enter ConoHa VPS 3.0 API user name",
|
||||
"errmsg": {
|
||||
"conflict": "Conflict with the \"API user ID\", please retain only one field"
|
||||
},
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_password": {
|
||||
"label": "ConoHa VPS API password",
|
||||
"placeholder": "Please enter ConoHa VPS 3.0 API password",
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_project_id": {
|
||||
"label": "ConoHa VPS project ID",
|
||||
"placeholder": "Please enter ConoHa VPS 3.0 tenant project ID",
|
||||
"errmsg": {
|
||||
"conflict": "Conflict with the \"project name\", please retain only one field"
|
||||
},
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_project_name": {
|
||||
"label": "ConoHa VPS project name",
|
||||
"placeholder": "Please enter ConoHa VPS 3.0 tenant project name",
|
||||
"errmsg": {
|
||||
"conflict": "Conflict with the \"project ID\", please retain only one field"
|
||||
},
|
||||
"tooltip": "For more information, see <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/</a>"
|
||||
},
|
||||
"constellix_api_key": {
|
||||
"label": "Constellix API key",
|
||||
"placeholder": "Please enter Constellix API key",
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"cloudns": "ClouDNS",
|
||||
"cmcccloud": "China Mobile ECloud",
|
||||
"cmcccloud_dns": "China Mobile ECloud - DNS",
|
||||
"conohavps": "ConoHa VPS",
|
||||
"constellix": "Constellix",
|
||||
"cpanel": "cPanel",
|
||||
"ctcccloud": "China Telecom StateCloud",
|
||||
|
||||
@@ -375,6 +375,62 @@
|
||||
"placeholder": "请输入移动云 AccessKeySecret",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://ecloud.10086.cn/op-help-center/doc/article/49739\" target=\"_blank\">https://ecloud.10086.cn/op-help-center/doc/article/49739</a>"
|
||||
},
|
||||
"conohavps_api_version": {
|
||||
"label": "ConoHa VPS 版本",
|
||||
"placeholder": "请选择 ConoHa VPS 版本"
|
||||
},
|
||||
"conohavps_api_v2_username": {
|
||||
"label": "ConoHa VPS API 用户名",
|
||||
"placeholder": "请输入 ConoHa VPS 2.0 API 用户名",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v2_password": {
|
||||
"label": "ConoHa VPS API 密码",
|
||||
"placeholder": "请输入 ConoHa VPS 2.0 API 密码",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-guideline-vps2/api-guideline-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v2_tenant_id": {
|
||||
"label": "ConoHa VPS 租户 ID",
|
||||
"placeholder": "请输入 ConoHa VPS 2.0 租户 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps2/api-identity-vps2/identity-post_tokens-v2/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps2/api-identity-vps2/identity-post_tokens-v2/</a>"
|
||||
},
|
||||
"conohavps_api_v3_user_id": {
|
||||
"label": "ConoHa VPS API 用户 ID",
|
||||
"placeholder": "请输入 ConoHa VPS 3.0 API 用户 ID",
|
||||
"errmsg": {
|
||||
"conflict": "与字段「API 用户名」冲突,请二选一"
|
||||
},
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_user_name": {
|
||||
"label": "ConoHa VPS API 用户名",
|
||||
"placeholder": "请输入 ConoHa VPS 3.0 API 用户名",
|
||||
"errmsg": {
|
||||
"conflict": "与字段「API 用户 ID」冲突,请二选一"
|
||||
},
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_password": {
|
||||
"label": "ConoHa VPS API 密码",
|
||||
"placeholder": "请输入 ConoHa VPS 3.0 API 密码",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-create_api_user-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_project_id": {
|
||||
"label": "ConoHa VPS 项目 ID",
|
||||
"placeholder": "请输入 ConoHa VPS 3.0 租户项目 ID",
|
||||
"errmsg": {
|
||||
"conflict": "与字段「项目名称」冲突,请二选一"
|
||||
},
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/</a>"
|
||||
},
|
||||
"conohavps_api_v3_project_name": {
|
||||
"label": "ConoHa VPS 项目名称",
|
||||
"placeholder": "请输入 ConoHa VPS 3.0 租户项目名称",
|
||||
"errmsg": {
|
||||
"conflict": "与字段「项目 ID」冲突,请二选一"
|
||||
},
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/\" target=\"_blank\">https://doc.conoha.jp/reference/api-vps3/api-cp-vps3/cp-get_api_info-v3/</a>"
|
||||
},
|
||||
"constellix_api_key": {
|
||||
"label": "Constellix API Key",
|
||||
"placeholder": "请输入 Constellix API Key",
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"cloudns": "ClouDNS",
|
||||
"cmcccloud": "移动云",
|
||||
"cmcccloud_dns": "移动云 - 云解析 DNS",
|
||||
"conohavps": "ConoHa VPS",
|
||||
"constellix": "Constellix",
|
||||
"cpanel": "cPanel",
|
||||
"ctcccloud": "天翼云",
|
||||
|
||||
Reference in New Issue
Block a user