diff --git a/go.mod b/go.mod index 2eaa3cd0..76016d6d 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/baidubce/bce-sdk-go v0.9.268 github.com/byteplus-sdk/byteplus-go-sdk-v2 v1.0.66 github.com/byteplus-sdk/byteplus-sdk-golang v1.0.68 - github.com/cloudflare/cloudflare-go/v7 v7.4.0 github.com/go-acme/lego/v5 v5.2.2 github.com/go-cmd/cmd v1.4.3 github.com/go-resty/resty/v2 v2.17.2 @@ -59,7 +58,6 @@ require ( github.com/minio/minio-go/v7 v7.2.0 github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47 github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 - github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.10 github.com/pocketbase/dbx v1.12.0 github.com/pocketbase/pocketbase v0.39.3 @@ -269,10 +267,10 @@ require ( github.com/nrdcg/namesilo v0.5.0 // indirect github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect github.com/philhofer/fwd v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/tidwall/sjson v1.2.5 // indirect github.com/tinylib/msgp v1.6.4 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 0a0c78a0..c1920bbf 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,6 @@ github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go/v7 v7.4.0 h1:JdTxzeXcAhtJ9rUkNISK4ABA55pZP8HLxx6XsPSA7dU= -github.com/cloudflare/cloudflare-go/v7 v7.4.0/go.mod h1:9zcoIAtu6cmcoPszCNISvqYMXs8wObtVGXE1qGFMrNU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -957,7 +955,6 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112 h1:vxOJOer github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112/go.mod h1:zCS+AwYqW2r0RBeCWHh9dmydCmP6LSew6y9Uyxsedy0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.111 h1:CKRs1TWBVinSLGZ2J0OVtfzoqoWMizrJ6IdYo6QEfow= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.111/go.mod h1:AfaoGamQm8lafjz+jkZn+T9VE1+tZ3ul9V9v6PORMVE= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -966,8 +963,6 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= diff --git a/pkg/core/deployer/providers/cloudflare-ssl/cloudflare_ssl.go b/pkg/core/deployer/providers/cloudflare-ssl/cloudflare_ssl.go index 00bfcdfe..ea96549c 100644 --- a/pkg/core/deployer/providers/cloudflare-ssl/cloudflare_ssl.go +++ b/pkg/core/deployer/providers/cloudflare-ssl/cloudflare_ssl.go @@ -6,12 +6,10 @@ import ( "fmt" "log/slog" - cf "github.com/cloudflare/cloudflare-go/v7" - cfcertificates "github.com/cloudflare/cloudflare-go/v7/custom_certificates" - cfhostnames "github.com/cloudflare/cloudflare-go/v7/custom_hostnames" - cfoption "github.com/cloudflare/cloudflare-go/v7/option" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" + cloudflaresdk "github.com/certimate-go/certimate/pkg/sdk3rd/cloudflare" ) type ( @@ -36,7 +34,7 @@ type DeployerConfig struct { type Deployer struct { config *DeployerConfig logger *slog.Logger - sdkClient *cfcertificates.CustomCertificateService + sdkClient *cloudflaresdk.Client } var _ Provider = (*Deployer)(nil) @@ -74,29 +72,30 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep if d.config.CertificateId == "" { // 新建自定义证书 // REF: https://developers.cf.com/api/resources/custom_certificates/methods/create - customCertificateNewReq := cfcertificates.CustomCertificateNewParams{ - ZoneID: cf.F(d.config.ZoneId), - Certificate: cf.F(certPEM), - PrivateKey: cf.F(privkeyPEM), - BundleMethod: cf.F(cfhostnames.BundleMethodUbiquitous), - Deploy: cf.F(cfcertificates.CustomCertificateNewParamsDeploy(cmp.Or(d.config.Environment, "production"))), + customCertificateCreateReq := &cloudflaresdk.CustomCertificateCreateRequest{ + ZoneId: d.config.ZoneId, + Certificate: lo.ToPtr(certPEM), + PrivateKey: lo.ToPtr(privkeyPEM), + BundleMethod: lo.ToPtr("ubiquitous"), + Deploy: lo.ToPtr(cmp.Or(d.config.Environment, "production")), } - customCertificateNewResp, err := d.sdkClient.New(ctx, customCertificateNewReq) - d.logger.Debug("sdk request 'CustomCertificates.New'", slog.Any("request", customCertificateNewReq), slog.Any("response", customCertificateNewResp)) + customCertificateCreateResp, err := d.sdkClient.CustomCertificateCreateWithContext(ctx, customCertificateCreateReq) + d.logger.Debug("sdk request 'CustomCertificates.Create'", slog.Any("request", customCertificateCreateReq), slog.Any("response", customCertificateCreateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'CustomCertificates.New': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'CustomCertificates.Create': %w", err) } } else { // 编辑自定义证书 // REF: https://developers.cloudflare.com/api/resources/custom_certificates/methods/edit - customCertificateEditReq := cfcertificates.CustomCertificateEditParams{ - ZoneID: cf.F(d.config.ZoneId), - Certificate: cf.F(certPEM), - PrivateKey: cf.F(privkeyPEM), - BundleMethod: cf.F(cfhostnames.BundleMethodUbiquitous), - Deploy: cf.F(cfcertificates.CustomCertificateEditParamsDeploy(cmp.Or(d.config.Environment, "production"))), + customCertificateEditReq := &cloudflaresdk.CustomCertificateEditRequest{ + ZoneId: d.config.ZoneId, + CertificateId: d.config.CertificateId, + Certificate: lo.ToPtr(certPEM), + PrivateKey: lo.ToPtr(privkeyPEM), + BundleMethod: lo.ToPtr("ubiquitous"), + Deploy: lo.ToPtr(cmp.Or(d.config.Environment, "production")), } - customCertificateEditResp, err := d.sdkClient.Edit(ctx, d.config.CertificateId, customCertificateEditReq) + customCertificateEditResp, err := d.sdkClient.CustomCertificateEditWithContext(ctx, customCertificateEditReq) d.logger.Debug("sdk request 'CustomCertificates.Edit'", slog.Any("request", customCertificateEditReq), slog.Any("response", customCertificateEditResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'CustomCertificates.Edit': %w", err) @@ -106,14 +105,13 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep return &DeployResult{}, nil } -func createSDKClient(apiToken string) (*cfcertificates.CustomCertificateService, error) { - if apiToken == "" { - return nil, fmt.Errorf("cloudflare: invalid api token") +func createSDKClient(apiToken string) (*cloudflaresdk.Client, error) { + client, err := cloudflaresdk.NewClient( + cloudflaresdk.WithApiToken(apiToken), + ) + if err != nil { + return nil, err } - opts := append(cf.DefaultClientOptions(), cfoption.WithAPIToken(apiToken)) - - srv := cfcertificates.NewCustomCertificateService(opts...) - - return srv, nil + return client, err } diff --git a/pkg/sdk3rd-trimmed/github.com/volcengine/volc-sdk-golang/service/vod/vod_cdn_service.go b/pkg/sdk3rd-trimmed/github.com/volcengine/volc-sdk-golang/service/vod/vod_cdn_service.go index ce6b1308..43687d1c 100644 --- a/pkg/sdk3rd-trimmed/github.com/volcengine/volc-sdk-golang/service/vod/vod_cdn_service.go +++ b/pkg/sdk3rd-trimmed/github.com/volcengine/volc-sdk-golang/service/vod/vod_cdn_service.go @@ -2,11 +2,11 @@ package vod import ( "encoding/json" + "errors" "net/http" "net/url" "strconv" - "github.com/pkg/errors" "github.com/volcengine/volc-sdk-golang/service/vod/models/request" "github.com/volcengine/volc-sdk-golang/service/vod/models/response" "google.golang.org/protobuf/encoding/protojson" diff --git a/pkg/sdk3rd/cloudflare/api_customcertificate_create.go b/pkg/sdk3rd/cloudflare/api_customcertificate_create.go new file mode 100644 index 00000000..a9ff02db --- /dev/null +++ b/pkg/sdk3rd/cloudflare/api_customcertificate_create.go @@ -0,0 +1,48 @@ +package cloudflare + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +type CustomCertificateCreateRequest struct { + ZoneId string `json:"-"` + CustomCsrId *string `json:"custom_csr_id,omitempty"` + Certificate *string `json:"certificate,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` + BundleMethod *string `json:"bundle_method,omitempty"` + Type *string `json:"type,omitempty"` + Deploy *string `json:"deploy,omitempty"` + Policy *string `json:"policy,omitempty"` + GeoRestrictions []*GeoRestriction `json:"geo_restrictions,omitempty"` +} + +type CustomCertificateCreateResponse struct { + sdkResponseBase + + Result *CustomCertificate `json:"result,omitempty"` +} + +func (c *Client) CustomCertificateCreate(req *CustomCertificateCreateRequest) (*CustomCertificateCreateResponse, error) { + return c.CustomCertificateCreateWithContext(context.Background(), req) +} + +func (c *Client) CustomCertificateCreateWithContext(ctx context.Context, req *CustomCertificateCreateRequest) (*CustomCertificateCreateResponse, error) { + path := fmt.Sprintf("/zones/%s/custom_certificates", url.PathEscape(req.ZoneId)) + httpreq, err := c.newRequest(http.MethodPost, path) + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &CustomCertificateCreateResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/cloudflare/api_customcertificate_edit.go b/pkg/sdk3rd/cloudflare/api_customcertificate_edit.go new file mode 100644 index 00000000..ee835ee1 --- /dev/null +++ b/pkg/sdk3rd/cloudflare/api_customcertificate_edit.go @@ -0,0 +1,48 @@ +package cloudflare + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +type CustomCertificateEditRequest struct { + ZoneId string `json:"-"` + CertificateId string `json:"-"` + CustomCsrId *string `json:"custom_csr_id,omitempty"` + Certificate *string `json:"certificate,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` + BundleMethod *string `json:"bundle_method,omitempty"` + Deploy *string `json:"deploy,omitempty"` + Policy *string `json:"policy,omitempty"` + GeoRestrictions []*GeoRestriction `json:"geo_restrictions,omitempty"` +} + +type CustomCertificateEditResponse struct { + sdkResponseBase + + Result *CustomCertificate `json:"result,omitempty"` +} + +func (c *Client) CustomCertificateEdit(req *CustomCertificateEditRequest) (*CustomCertificateEditResponse, error) { + return c.CustomCertificateEditWithContext(context.Background(), req) +} + +func (c *Client) CustomCertificateEditWithContext(ctx context.Context, req *CustomCertificateEditRequest) (*CustomCertificateEditResponse, error) { + path := fmt.Sprintf("/zones/%s/custom_certificates/%s", url.PathEscape(req.ZoneId), url.PathEscape(req.CertificateId)) + httpreq, err := c.newRequest(http.MethodPatch, path) + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &CustomCertificateEditResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/cloudflare/client.go b/pkg/sdk3rd/cloudflare/client.go new file mode 100644 index 00000000..9c715e1f --- /dev/null +++ b/pkg/sdk3rd/cloudflare/client.go @@ -0,0 +1,104 @@ +package cloudflare + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/certimate-go/certimate/internal/app" +) + +type Client struct { + rc *resty.Client +} + +func NewClient(optFns ...OptionsFunc) (*Client, error) { + opts := &Options{} + for _, fn := range optFns { + fn(opts) + } + + if opts.ApiToken == "" { + return nil, fmt.Errorf("sdkerr: unset apiToken") + } + + restyClient := resty.New(). + SetBaseURL("https://api.cloudflare.com/client/v4"). + SetHeader("Accept", "application/json"). + SetHeader("Authorization", "Bearer "+opts.ApiToken). + SetHeader("Content-Type", "application/json"). + SetHeader("User-Agent", app.AppUserAgent) + + return &Client{rc: restyClient}, nil +} + +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.rc.SetTimeout(timeout) + return c +} + +func (c *Client) SetTLSConfig(config *tls.Config) *Client { + c.rc.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.rc.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 rErrors := res.GetErrors(); rErrors != nil { + return resp, fmt.Errorf("sdkerr: errors='%s'", rErrors.Error()) + } + } + } + + return resp, nil +} diff --git a/pkg/sdk3rd/cloudflare/options.go b/pkg/sdk3rd/cloudflare/options.go new file mode 100644 index 00000000..5d72f1eb --- /dev/null +++ b/pkg/sdk3rd/cloudflare/options.go @@ -0,0 +1,13 @@ +package cloudflare + +type Options struct { + ApiToken string +} + +type OptionsFunc func(*Options) + +func WithApiToken(apiToken string) OptionsFunc { + return func(o *Options) { + o.ApiToken = apiToken + } +} diff --git a/pkg/sdk3rd/cloudflare/types.go b/pkg/sdk3rd/cloudflare/types.go new file mode 100644 index 00000000..a2f04a95 --- /dev/null +++ b/pkg/sdk3rd/cloudflare/types.go @@ -0,0 +1,84 @@ +package cloudflare + +import ( + "fmt" + "strings" +) + +type sdkResponse interface { + GetErrors() error + GetSuccess() bool +} + +type sdkResponseBase struct { + Errors APIErrors `json:"errors,omitempty"` + Messages []APIMessage `json:"messages,omitempty"` + Success bool `json:"success,omitempty"` +} + +type APIMessage struct { + Code int `json:"code"` + Message string `json:"message"` + DocumentationURL string `json:"documentation_url"` + ErrorChain []APIErrorChain `json:"error_chain"` + Source *APISource `json:"source"` +} + +type APIErrors []APIMessage + +type APIErrorChain struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type APISource struct { + Pointer string `json:"pointer"` +} + +func (e APIErrors) Error() string { + builder := &strings.Builder{} + + for _, item := range e { + fmt.Fprintf(builder, "%d: %s", item.Code, item.Message) + + for _, link := range item.ErrorChain { + fmt.Fprintf(builder, "; %d: %s", link.Code, link.Message) + } + } + + return builder.String() +} + +func (r *sdkResponseBase) GetErrors() error { + if len(r.Errors) > 0 { + return r.Errors + } + return nil +} + +func (r *sdkResponseBase) GetSuccess() bool { + return r.Success +} + +var _ sdkResponse = (*sdkResponseBase)(nil) + +type GeoRestriction struct { + Label string `json:"label"` +} + +type CustomCertificate struct { + ID string `json:"id"` + ZoneID string `json:"zone_id"` + BundleMethod string `json:"bundle_method"` + CustomCsrID string `json:"custom_csr_id"` + GeoRestrictions []GeoRestriction `json:"geo_restrictions"` + Hosts []string `json:"hosts"` + Issuer string `json:"issuer"` + PolicyRestrictions string `json:"policy_restrictions"` + Priority float64 `json:"priority"` + Signature string `json:"signature"` + Status string `json:"status"` + ExpiresOn string `json:"expires_on"` + UploadedOn string `json:"uploaded_on"` + ModifiedOn string `json:"modified_on"` +}