diff --git a/go.mod b/go.mod index 0140ddc8..861bfceb 100644 --- a/go.mod +++ b/go.mod @@ -140,6 +140,7 @@ require ( github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/vultr/govultr/v3 v3.21.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index 74227562..959dfd1a 100644 --- a/go.sum +++ b/go.sum @@ -887,6 +887,8 @@ github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0 github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/volcengine/volcengine-go-sdk v1.1.30 h1:D85LL8euXgxwsZzwQ0azT/hannEsiKxKY4h/7/HU764= github.com/volcengine/volcengine-go-sdk v1.1.30/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo= +github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM= +github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= diff --git a/internal/certapply/applicators/sp_vultr.go b/internal/certapply/applicators/sp_vultr.go new file mode 100644 index 00000000..6101a023 --- /dev/null +++ b/internal/certapply/applicators/sp_vultr.go @@ -0,0 +1,29 @@ +package applicators + +import ( + "fmt" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/vultr" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeVultr, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForVultr{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := vultr.NewChallengeProvider(&vultr.ChallengeProviderConfig{ + ApiKey: credentials.ApiKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index d43462ed..5f745e3c 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -402,6 +402,10 @@ type AccessConfigForVolcEngine struct { SecretAccessKey string `json:"secretAccessKey"` } +type AccessConfigForVultr struct { + ApiKey string `json:"apiKey"` +} + type AccessConfigForWangsu struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index e35919a7..379b6276 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -86,6 +86,7 @@ const ( AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeVercel = AccessProviderType("vercel") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") + AccessProviderTypeVultr = AccessProviderType("vultr") AccessProviderTypeWangsu = AccessProviderType("wangsu") AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWeComBot = AccessProviderType("wecombot") @@ -171,6 +172,7 @@ const ( ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel) ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS] ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns") + ACMEDns01ProviderTypeVultr = ACMEDns01ProviderType(AccessProviderTypeVultr) ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn) ) diff --git a/internal/workflow/service.go b/internal/workflow/service.go index adf2a39e..d869a1c0 100644 --- a/internal/workflow/service.go +++ b/internal/workflow/service.go @@ -111,7 +111,9 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR workflowRun = resp } - s.dispatcher.Start(ctx, workflowRun.Id) + if err := s.dispatcher.Start(ctx, workflowRun.Id); err != nil { + return nil, err + } return &dtos.WorkflowStartRunResp{RunId: workflowRun.Id}, nil } @@ -131,7 +133,9 @@ func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCance return nil, errors.New("workflow run is not pending or processing") } - s.dispatcher.Cancel(ctx, workflowRun.Id) + if err := s.dispatcher.Cancel(ctx, workflowRun.Id); err != nil { + return nil, err + } return &dtos.WorkflowCancelRunResp{}, nil } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go new file mode 100644 index 00000000..c3badc34 --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go @@ -0,0 +1,38 @@ +package vultr + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/providers/dns/vultr" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiKey string `json:"apiKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + providerConfig := vultr.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := vultr.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/vultr.svg b/ui/public/imgs/providers/vultr.svg new file mode 100644 index 00000000..cf49d15d --- /dev/null +++ b/ui/public/imgs/providers/vultr.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 976434ae..ed643447 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -80,6 +80,7 @@ import AccessConfigFieldsProviderUniCloud from "./forms/AccessConfigFieldsProvid import AccessConfigFieldsProviderUpyun from "./forms/AccessConfigFieldsProviderUpyun"; import AccessConfigFieldsProviderVercel from "./forms/AccessConfigFieldsProviderVercel"; import AccessConfigFieldsProviderVolcEngine from "./forms/AccessConfigFieldsProviderVolcEngine"; +import AccessConfigFieldsProviderVultr from "./forms/AccessConfigFieldsProviderVultr"; import AccessConfigFieldsProviderWangsu from "./forms/AccessConfigFieldsProviderWangsu"; import AccessConfigFieldsProviderWebhook from "./forms/AccessConfigFieldsProviderWebhook"; import AccessConfigFieldsProviderWeComBot from "./forms/AccessConfigFieldsProviderWeComBot"; @@ -335,6 +336,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, .. case ACCESS_PROVIDERS.VOLCENGINE: { return ; } + case ACCESS_PROVIDERS.VULTR: { + return ; + } case ACCESS_PROVIDERS.WANGSU: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx new file mode 100644 index 00000000..1ce3e4ea --- /dev/null +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const AccessConfigFormFieldsProviderVultr = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + apiKey: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + apiKey: z.string().nonempty(t("access.form.vultr_api_key.placeholder")), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderVultr, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index aeb875dd..bbb87fd7 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -88,6 +88,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ UPYUN: "upyun", VERCEL: "vercel", VOLCENGINE: "volcengine", + VULTR: "vultr", WANGSU: "wangsu", WEBHOOK: "webhook", WECOMBOT: "wecombot", @@ -178,6 +179,7 @@ export const accessProvidersMap: Maphttps://www.volcengine.com/docs/6291/216571", + "access.form.vultr_api_key.label": "Vultr API key", + "access.form.vultr_api_key.placeholder": "Please enter Vultr API key", + "access.form.vultr_api_key.tooltip": "For more information, see https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key", "access.form.wangsu_access_key_id.label": "Wangsu Cloud AccessKeyId", "access.form.wangsu_access_key_id.placeholder": "Please enter Wangsu Cloud AccessKeyId", "access.form.wangsu_access_key_id.tooltip": "For more information, see https://en.wangsu.com/document/account-manage/15775", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 593cda3d..b620226e 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -171,6 +171,7 @@ "provider.volcengine.imagex": "Volcengine - ImageX", "provider.volcengine.live": "Volcengine - Live", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", + "provider.vultr": "Vultr", "provider.wangsu": "Wangsu Cloud", "provider.wangsu.cdn": "Wangsu Cloud - CDN (Content Delivery Network)", "provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro (CDN 360)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 9aef0305..5df865f4 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -491,6 +491,9 @@ "access.form.volcengine_secret_access_key.label": "火山引擎 SecretAccessKey", "access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 SecretAccessKey", "access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", + "access.form.vultr_api_key.label": "Vultr API Key", + "access.form.vultr_api_key.placeholder": "请输入 Vultr API Key", + "access.form.vultr_api_key.tooltip": "这是什么?请参阅 https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key", "access.form.wangsu_access_key_id.label": "网宿云 AccessKeyId", "access.form.wangsu_access_key_id.placeholder": "请输入网宿云 AccessKeyId", "access.form.wangsu_access_key_id.tooltip": "这是什么?请参阅 https://www.wangsu.com/document/account-manage/15775", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 4c39a384..6d9f78d6 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -171,6 +171,7 @@ "provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX", "provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS", + "provider.vultr": "Vultr", "provider.wangsu": "网宿云", "provider.wangsu.cdn": "网宿云 - 内容分发网络 CDN", "provider.wangsu.cdnpro": "网宿云 - CDN Pro (CDN 360)",