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,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 {
|
||||
|
||||
Reference in New Issue
Block a user