feat: new acme dns-01 provider: statecloud smartdns
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package cmcccloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
@@ -18,7 +17,7 @@ type ChallengeProviderConfig struct {
|
||||
|
||||
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.NewDefaultConfig()
|
||||
|
||||
@@ -18,8 +18,9 @@ import (
|
||||
const (
|
||||
envNamespace = "CMCCCLOUD_"
|
||||
|
||||
EnvAccessKey = envNamespace + "ACCESS_KEY"
|
||||
EnvSecretKey = envNamespace + "SECRET_KEY"
|
||||
EnvAccessKey = envNamespace + "ACCESS_KEY"
|
||||
EnvSecretKey = envNamespace + "SECRET_KEY"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
@@ -30,13 +31,14 @@ const (
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
ReadTimeOut int
|
||||
ConnectTimeout int
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int32
|
||||
ReadTimeOut int
|
||||
ConnectTimeout int
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package ctcccloud
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ctcccloud/internal"
|
||||
)
|
||||
|
||||
type ChallengeProviderConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int32 `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.NewDefaultConfig()
|
||||
providerConfig.AccessKeyId = config.AccessKeyId
|
||||
providerConfig.SecretAccessKey = config.SecretAccessKey
|
||||
if config.DnsTTL != 0 {
|
||||
providerConfig.TTL = int(config.DnsTTL)
|
||||
}
|
||||
if config.DnsPropagationTimeout != 0 {
|
||||
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||
}
|
||||
|
||||
provider, err := internal.NewDNSProviderConfig(providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
|
||||
ctyundns "github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/dns"
|
||||
typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "CTYUNSMARTDNS_"
|
||||
|
||||
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
|
||||
EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
AccessKeyId string
|
||||
SecretAccessKey string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
client *ctyundns.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ctyun: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKeyId = values[EnvAccessKeyID]
|
||||
config.SecretAccessKey = values[EnvSecretAccessKey]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ctyun: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := ctyundns.NewClient(config.AccessKeyId, config.SecretAccessKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
client.SetTimeout(config.HTTPTimeout)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ctyun: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ctyun: %w", err)
|
||||
}
|
||||
|
||||
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
|
||||
return fmt.Errorf("ctyun: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ctyun: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ctyun: %w", err)
|
||||
}
|
||||
|
||||
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
|
||||
return fmt.Errorf("ctyun: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findDNSRecordId(zoneName, subDomain string) (int32, error) {
|
||||
// 查询解析记录列表
|
||||
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11264&data=181&isNormal=1&vid=259
|
||||
request := &ctyundns.QueryRecordListRequest{}
|
||||
request.Domain = typeutil.ToPtr(zoneName)
|
||||
request.Host = typeutil.ToPtr(subDomain)
|
||||
request.Type = typeutil.ToPtr("TXT")
|
||||
|
||||
response, err := d.client.QueryRecordList(request)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if response.ReturnObj == nil || response.ReturnObj.Records == nil || len(response.ReturnObj.Records) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return response.ReturnObj.Records[0].RecordId, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
|
||||
recordId, err := d.findDNSRecordId(zoneName, subDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordId == 0 {
|
||||
// 新增解析记录
|
||||
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11259&data=181&isNormal=1&vid=259
|
||||
request := &ctyundns.AddRecordRequest{
|
||||
Domain: typeutil.ToPtr(zoneName),
|
||||
Host: typeutil.ToPtr(subDomain),
|
||||
Type: typeutil.ToPtr("TXT"),
|
||||
LineCode: typeutil.ToPtr("Default"),
|
||||
Value: typeutil.ToPtr(value),
|
||||
State: typeutil.ToPtr(int32(1)),
|
||||
TTL: typeutil.ToPtr(int32(d.config.TTL)),
|
||||
}
|
||||
_, err := d.client.AddRecord(request)
|
||||
return err
|
||||
} else {
|
||||
// 修改解析记录
|
||||
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11261&data=181&isNormal=1&vid=259
|
||||
request := &ctyundns.UpdateRecordRequest{
|
||||
RecordId: typeutil.ToPtr(recordId),
|
||||
Domain: typeutil.ToPtr(zoneName),
|
||||
Host: typeutil.ToPtr(subDomain),
|
||||
Type: typeutil.ToPtr("TXT"),
|
||||
LineCode: typeutil.ToPtr("Default"),
|
||||
Value: typeutil.ToPtr(value),
|
||||
State: typeutil.ToPtr(int32(1)),
|
||||
TTL: typeutil.ToPtr(int32(d.config.TTL)),
|
||||
}
|
||||
_, err := d.client.UpdateRecord(request)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
|
||||
recordId, err := d.findDNSRecordId(zoneName, subDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordId == 0 {
|
||||
return nil
|
||||
} else {
|
||||
// 删除解析记录
|
||||
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=122&api=11262&data=181&isNormal=1&vid=259
|
||||
request := &ctyundns.DeleteRecordRequest{
|
||||
RecordId: typeutil.ToPtr(recordId),
|
||||
}
|
||||
_, err = d.client.DeleteRecord(request)
|
||||
return err
|
||||
}
|
||||
}
|
||||
46
internal/pkg/sdk3rd/ctyun/dns/api_add_record.go
Normal file
46
internal/pkg/sdk3rd/ctyun/dns/api_add_record.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AddRecordRequest struct {
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
Host *string `json:"host,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
LineCode *string `json:"lineCode,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
TTL *int32 `json:"ttl,omitempty"`
|
||||
State *int32 `json:"state,omitempty"`
|
||||
Remark *string `json:"remark"`
|
||||
}
|
||||
|
||||
type AddRecordResponse struct {
|
||||
baseResult
|
||||
|
||||
ReturnObj *struct {
|
||||
RecordId int32 `json:"recordId"`
|
||||
} `json:"returnObj,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) AddRecord(req *AddRecordRequest) (*AddRecordResponse, error) {
|
||||
return c.AddRecordWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) AddRecordWithContext(ctx context.Context, req *AddRecordRequest) (*AddRecordResponse, error) {
|
||||
request, err := c.newRequest(http.MethodPost, "/v2/addRecord")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
request.SetContext(ctx)
|
||||
request.SetBody(req)
|
||||
}
|
||||
|
||||
result := &AddRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(request, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
35
internal/pkg/sdk3rd/ctyun/dns/api_delete_record.go
Normal file
35
internal/pkg/sdk3rd/ctyun/dns/api_delete_record.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DeleteRecordRequest struct {
|
||||
RecordId *int32 `json:"recordId,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteRecordResponse struct {
|
||||
baseResult
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRecord(req *DeleteRecordRequest) (*DeleteRecordResponse, error) {
|
||||
return c.DeleteRecordWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRecordWithContext(ctx context.Context, req *DeleteRecordRequest) (*DeleteRecordResponse, error) {
|
||||
request, err := c.newRequest(http.MethodPost, "/v2/deleteRecord")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
request.SetContext(ctx)
|
||||
request.SetBody(req)
|
||||
}
|
||||
|
||||
result := &DeleteRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(request, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
53
internal/pkg/sdk3rd/ctyun/dns/api_query_record_list.go
Normal file
53
internal/pkg/sdk3rd/ctyun/dns/api_query_record_list.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type QueryRecordListRequest struct {
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
Host *string `json:"host,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
LineCode *string `json:"lineCode,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
State *int32 `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
type QueryRecordListResponse struct {
|
||||
baseResult
|
||||
|
||||
ReturnObj *struct {
|
||||
Records []*struct {
|
||||
RecordId int32 `json:"recordId"`
|
||||
Host string `json:"host"`
|
||||
Type string `json:"type"`
|
||||
LineCode string `json:"lineCode"`
|
||||
Value string `json:"value"`
|
||||
TTL int32 `json:"ttl"`
|
||||
State int32 `json:"state"`
|
||||
Remark string `json:"remark"`
|
||||
} `json:"records,omitempty"`
|
||||
} `json:"returnObj,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) QueryRecordList(req *QueryRecordListRequest) (*QueryRecordListResponse, error) {
|
||||
return c.QueryRecordListWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) QueryRecordListWithContext(ctx context.Context, req *QueryRecordListRequest) (*QueryRecordListResponse, error) {
|
||||
request, err := c.newRequest(http.MethodGet, "/v2/queryRecordList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
request.SetContext(ctx)
|
||||
request.SetBody(req)
|
||||
}
|
||||
|
||||
result := &QueryRecordListResponse{}
|
||||
if _, err := c.doRequestWithResult(request, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
47
internal/pkg/sdk3rd/ctyun/dns/api_update_record.go
Normal file
47
internal/pkg/sdk3rd/ctyun/dns/api_update_record.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateRecordRequest struct {
|
||||
RecordId *int32 `json:"recordId,omitempty"`
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
Host *string `json:"host,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
LineCode *string `json:"lineCode,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
TTL *int32 `json:"ttl,omitempty"`
|
||||
State *int32 `json:"state,omitempty"`
|
||||
Remark *string `json:"remark"`
|
||||
}
|
||||
|
||||
type UpdateRecordResponse struct {
|
||||
baseResult
|
||||
|
||||
ReturnObj *struct {
|
||||
RecordId int32 `json:"recordId"`
|
||||
} `json:"returnObj,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) UpdateRecord(req *UpdateRecordRequest) (*UpdateRecordResponse, error) {
|
||||
return c.UpdateRecordWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateRecordWithContext(ctx context.Context, req *UpdateRecordRequest) (*UpdateRecordResponse, error) {
|
||||
request, err := c.newRequest(http.MethodPost, "/v2/updateRecord")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
request.SetContext(ctx)
|
||||
request.SetBody(req)
|
||||
}
|
||||
|
||||
result := &UpdateRecordResponse{}
|
||||
if _, err := c.doRequestWithResult(request, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
40
internal/pkg/sdk3rd/ctyun/dns/client.go
Normal file
40
internal/pkg/sdk3rd/ctyun/dns/client.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/usual2970/certimate/internal/pkg/sdk3rd/ctyun/openapi"
|
||||
)
|
||||
|
||||
const endpoint = "https://smartdns-global.ctapi.ctyun.cn"
|
||||
|
||||
type Client struct {
|
||||
client *openapi.Client
|
||||
}
|
||||
|
||||
func NewClient(accessKeyId, secretAccessKey string) (*Client, error) {
|
||||
client, err := openapi.NewClient(endpoint, accessKeyId, secretAccessKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{client: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
|
||||
return c.client.NewRequest(method, path)
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(request *resty.Request) (*resty.Response, error) {
|
||||
return c.client.DoRequest(request)
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(request *resty.Request, result any) (*resty.Response, error) {
|
||||
return c.client.DoRequestWithResult(request, result)
|
||||
}
|
||||
11
internal/pkg/sdk3rd/ctyun/dns/types.go
Normal file
11
internal/pkg/sdk3rd/ctyun/dns/types.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dns
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type baseResult struct {
|
||||
StatusCode json.RawMessage `json:"statusCode,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
ErrorMessage *string `json:"errorMessage,omitempty"`
|
||||
RequestId *string `json:"requestId,omitempty"`
|
||||
}
|
||||
167
internal/pkg/sdk3rd/ctyun/openapi/client.go
Normal file
167
internal/pkg/sdk3rd/ctyun/openapi/client.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(endpoint, accessKeyId, secretAccessKey string) (*Client, error) {
|
||||
if endpoint == "" {
|
||||
return nil, fmt.Errorf("sdk error: unset endpoint")
|
||||
}
|
||||
if _, err := url.Parse(endpoint); err != nil {
|
||||
return nil, fmt.Errorf("sdk error: invalid endpoint: %w", err)
|
||||
}
|
||||
if accessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdk error: unset accessKey")
|
||||
}
|
||||
if secretAccessKey == "" {
|
||||
return nil, fmt.Errorf("sdk error: unset secretKey")
|
||||
}
|
||||
|
||||
client := resty.New().
|
||||
SetBaseURL(endpoint).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", "certimate").
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// 生成时间戳及流水号
|
||||
now := time.Now()
|
||||
eopDate := now.Format("20060102T150405Z")
|
||||
eopReqId := uuid.New().String()
|
||||
|
||||
// 获取查询参数
|
||||
queryStr := ""
|
||||
if req.URL != nil {
|
||||
queryStr = req.URL.Query().Encode()
|
||||
}
|
||||
|
||||
// 获取请求正文
|
||||
payloadStr := ""
|
||||
if req.Body != nil {
|
||||
reader, err := req.GetBody()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
payload, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloadStr = string(payload)
|
||||
}
|
||||
|
||||
// 构造代签字符串
|
||||
payloadHash := sha256.Sum256([]byte(payloadStr))
|
||||
payloadHashHex := hex.EncodeToString(payloadHash[:])
|
||||
dataToSign := fmt.Sprintf("ctyun-eop-request-id:%s\neop-date:%s\n\n%s\n%s", eopReqId, eopDate, queryStr, payloadHashHex)
|
||||
|
||||
// 生成 ktime
|
||||
hasher := hmac.New(sha256.New, []byte(secretAccessKey))
|
||||
hasher.Write([]byte(eopDate))
|
||||
ktime := hasher.Sum(nil)
|
||||
|
||||
// 生成 kak
|
||||
hasher = hmac.New(sha256.New, ktime)
|
||||
hasher.Write([]byte(accessKeyId))
|
||||
kak := hasher.Sum(nil)
|
||||
|
||||
// 生成 kdata
|
||||
hasher = hmac.New(sha256.New, kak)
|
||||
hasher.Write([]byte(now.Format("20060102")))
|
||||
kdate := hasher.Sum(nil)
|
||||
|
||||
// 构造签名
|
||||
hasher = hmac.New(sha256.New, kdate)
|
||||
hasher.Write([]byte(dataToSign))
|
||||
sign := hasher.Sum(nil)
|
||||
signStr := base64.StdEncoding.EncodeToString(sign)
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("ctyun-eop-request-id", eopReqId)
|
||||
req.Header.Set("eop-date", eopDate)
|
||||
req.Header.Set("eop-authorization", fmt.Sprintf("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", accessKeyId, signStr))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) NewRequest(method string, path string) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdk error: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdk error: unset path")
|
||||
}
|
||||
|
||||
req := c.client.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) DoRequest(request *resty.Request) (*resty.Response, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("sdk error: nil request")
|
||||
}
|
||||
|
||||
// WARN:
|
||||
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` here.
|
||||
|
||||
resp, err := request.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdk error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdk error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) DoRequestWithResult(request *resty.Request, result any) (*resty.Response, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("sdk error: nil request")
|
||||
}
|
||||
|
||||
response, err := c.DoRequest(request)
|
||||
if err != nil {
|
||||
if response != nil {
|
||||
json.Unmarshal(response.Body(), &result)
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
if len(response.Body()) != 0 {
|
||||
if err := json.Unmarshal(response.Body(), &result); err != nil {
|
||||
return response, fmt.Errorf("sdk error: failed to unmarshal response: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
Reference in New Issue
Block a user