feat(provider): new acme dns-01 provider: vultr

This commit is contained in:
Fu Diwei 2025-09-05 22:31:48 +08:00
parent 007385a071
commit 395eb44b06
15 changed files with 151 additions and 2 deletions

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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)
}
}

View File

@ -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"`

View File

@ -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)
)

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 54 54" style="enable-background:new 0 0 54 54;" xml:space="preserve"><g><rect class="st0" width="54" height="54" fill="#102147"/></g><g><path class="st1" d="M20.6,9.8c-0.4-0.6-1-1-1.8-1H8.1C6.9,8.8,6,9.7,6,10.9l0,0c0,0.4,0.1,0.8,0.3,1.1l2.2,3.5l14.3-2.2L20.6,9.8z" fill="#007BFC"/><path class="st2" d="M22.8,13.3c-0.4-0.6-1.1-1-1.8-1H10.3c-1.2,0-2.1,0.9-2.1,2.1l0,0c0,0.4,0.1,0.8,0.3,1.1l3.1,4.9l14.3-2.2 L22.8,13.3z" fill="#51B9FF"/><path class="st3" d="M11.6,20.4c-0.2-0.3-0.3-0.7-0.3-1.1c0-1.2,0.9-2.1,2.1-2.1l0,0h10.8c0.7,0,1.4,0.4,1.8,1l9.6,15.3 c0.2,0.3,0.3,0.7,0.3,1.1c0,0.4-0.1,0.8-0.3,1.1l-5.4,8.5c-0.6,1-1.9,1.3-2.9,0.6c-0.3-0.2-0.5-0.4-0.6-0.6L11.6,20.4z" fill="#FFFFFF"/><path class="st3" d="M38.7,25c0.6,1,1.9,1.3,2.9,0.6c0.3-0.2,0.5-0.4,0.6-0.6l1.8-2.9l3.5-5.6c0.2-0.3,0.3-0.7,0.3-1.1 c0-0.4-0.1-0.8-0.3-1.1l-2.8-4.4c-0.4-0.6-1.1-1-1.8-1H32.3c-1.2,0-2.1,0.9-2.1,2.1l0,0c0,0.4,0.1,0.8,0.3,1.1L38.7,25z" fill="#FFFFFF"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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 <AccessConfigFieldsProviderVolcEngine />;
}
case ACCESS_PROVIDERS.VULTR: {
return <AccessConfigFieldsProviderVultr />;
}
case ACCESS_PROVIDERS.WANGSU: {
return <AccessConfigFieldsProviderWangsu />;
}

View File

@ -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 (
<>
<Form.Item
name={[parentNamePath, "apiKey"]}
initialValue={initialValues.apiKey}
label={t("access.form.vultr_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.vultr_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.vultr_api_key.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
apiKey: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
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;

View File

@ -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: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.SPACESHIP, "provider.spaceship", "/imgs/providers/spaceship.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.VULTR, "provider.vultr", "/imgs/providers/vultr.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
@ -316,6 +318,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
VERCEL: `${ACCESS_PROVIDERS.VERCEL}`,
VOLCENGINE: `${ACCESS_PROVIDERS.VOLCENGINE}`, // 兼容旧值,等同于 `VOLCENGINE_DNS`
VOLCENGINE_DNS: `${ACCESS_PROVIDERS.VOLCENGINE}-dns`,
VULTR: `${ACCESS_PROVIDERS.VULTR}`,
WESTCN: `${ACCESS_PROVIDERS.WESTCN}`,
} as const);
@ -362,6 +365,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.PORKBUN, "provider.porkbun"],
[ACME_DNS01_PROVIDERS.SPACESHIP, "provider.spaceship"],
[ACME_DNS01_PROVIDERS.VERCEL, "provider.vercel"],
[ACME_DNS01_PROVIDERS.VULTR, "provider.vultr"],
[ACME_DNS01_PROVIDERS.CMCCCLOUD_DNS, "provider.cmcccloud.dns"],
[ACME_DNS01_PROVIDERS.CTCCCLOUD_SMARTDNS, "provider.ctcccloud.smartdns"],
[ACME_DNS01_PROVIDERS.RAINYUN, "provider.rainyun"],

View File

@ -492,6 +492,9 @@
"access.form.volcengine_secret_access_key.label": "VolcEngine SecretAccessKey",
"access.form.volcengine_secret_access_key.placeholder": "Please enter VolcEngine SecretAccessKey",
"access.form.volcengine_secret_access_key.tooltip": "For more information, see <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"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 <a href=\"https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key\" target=\"_blank\">https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key</a>",
"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 <a href=\"https://en.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://en.wangsu.com/document/account-manage/15775</a>",

View File

@ -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)",

View File

@ -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": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.vultr_api_key.label": "Vultr API Key",
"access.form.vultr_api_key.placeholder": "请输入 Vultr API Key",
"access.form.vultr_api_key.tooltip": "这是什么?请参阅 <a href=\"https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key\" target=\"_blank\">https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key</a>",
"access.form.wangsu_access_key_id.label": "网宿云 AccessKeyId",
"access.form.wangsu_access_key_id.placeholder": "请输入网宿云 AccessKeyId",
"access.form.wangsu_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://www.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://www.wangsu.com/document/account-manage/15775</a>",

View File

@ -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)",