diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 98561daf..ba4fadef 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -16,6 +16,7 @@ import ( pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare" pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud" + pConstellix "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix" pDeSEC "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/desec" pDigitalOcean "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/digitalocean" pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla" @@ -234,6 +235,22 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi return applicant, err } + case domain.ACMEDns01ProviderTypeConstellix: + { + access := domain.AccessConfigForConstellix{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pConstellix.NewChallengeProvider(&pConstellix.ChallengeProviderConfig{ + ApiKey: access.ApiKey, + SecretKey: access.SecretKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ACMEDns01ProviderTypeDeSEC: { access := domain.AccessConfigForDeSEC{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 0ed9f0ee..274f2fd0 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -109,6 +109,11 @@ type AccessConfigForCMCCCloud struct { AccessKeySecret string `json:"accessKeySecret"` } +type AccessConfigForConstellix struct { + ApiKey string `json:"apiKey"` + SecretKey string `json:"secretKey"` +} + type AccessConfigForDeSEC struct { Token string `json:"token"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 55f8b2af..dd9663f2 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -28,6 +28,7 @@ const ( AccessProviderTypeCloudflare = AccessProviderType("cloudflare") AccessProviderTypeClouDNS = AccessProviderType("cloudns") AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud") + AccessProviderTypeConstellix = AccessProviderType("constellix") AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 天翼云(预留) AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 联通云(预留) AccessProviderTypeDeSEC = AccessProviderType("desec") @@ -131,6 +132,7 @@ const ( ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare) ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS) ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud) + ACMEDns01ProviderTypeConstellix = ACMEDns01ProviderType(AccessProviderTypeConstellix) ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC) ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean) ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA) diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix/constellix.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix/constellix.go new file mode 100644 index 00000000..12e7d615 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/constellix/constellix.go @@ -0,0 +1,38 @@ +package cloudns + +import ( + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/constellix" +) + +type ChallengeProviderConfig struct { + ApiKey string `json:"apiKey"` + SecretKey string `json:"secretKey"` + 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 := constellix.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + providerConfig.SecretKey = config.SecretKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := constellix.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/constellix.png b/ui/public/imgs/providers/constellix.png new file mode 100644 index 00000000..71a8722a Binary files /dev/null and b/ui/public/imgs/providers/constellix.png differ diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 44e389ed..4bb1d439 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -28,6 +28,7 @@ import AccessFormCdnflyConfig from "./AccessFormCdnflyConfig"; import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig"; +import AccessFormConstellixConfig from "./AccessFormConstellixConfig"; import AccessFormDeSECConfig from "./AccessFormDeSECConfig"; import AccessFormDigitalOceanConfig from "./AccessFormDigitalOceanConfig"; import AccessFormDingTalkBotConfig from "./AccessFormDingTalkBotConfig"; @@ -219,6 +220,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.CMCCCLOUD: return ; + case ACCESS_PROVIDERS.CONSTELLIX: + return ; case ACCESS_PROVIDERS.DESEC: return ; case ACCESS_PROVIDERS.DIGITALOCEAN: diff --git a/ui/src/components/access/AccessFormConstellixConfig.tsx b/ui/src/components/access/AccessFormConstellixConfig.tsx new file mode 100644 index 00000000..5966828a --- /dev/null +++ b/ui/src/components/access/AccessFormConstellixConfig.tsx @@ -0,0 +1,67 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; +import { type AccessConfigForConstellix } from "@/domain/access"; + +type AccessFormConstellixConfigFieldValues = Nullish; + +export type AccessFormConstellixConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormConstellixConfigFieldValues; + onValuesChange?: (values: AccessFormConstellixConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormConstellixConfigFieldValues => { + return { + apiKey: "", + secretKey: "", + }; +}; + +const AccessFormConstellixConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormConstellixConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiKey: z.string().trim().nonempty(t("access.form.constellix_api_key.placeholder")), + secretKey: z.string().trim().nonempty(t("access.form.constellix_secret_key.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormConstellixConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index fe9f12e3..6da7c90b 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -23,6 +23,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForCloudflare | AccessConfigForClouDNS | AccessConfigForCMCCCloud + | AccessConfigForConstellix | AccessConfigForDeSEC | AccessConfigForDigitalOcean | AccessConfigForDingTalkBot @@ -172,6 +173,11 @@ export type AccessConfigForCMCCCloud = { accessKeySecret: string; }; +export type AccessConfigForConstellix = { + apiKey: string; + secretKey: string; +}; + export type AccessConfigForDeSEC = { token: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index bb550691..5bd1439b 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ CLOUDFLARE: "cloudflare", CLOUDNS: "cloudns", CMCCCLOUD: "cmcccloud", + CONSTELLIX: "constellix", DESEC: "desec", DIGITALOCEAN: "digitalocean", DINGTALKBOT: "dingtalkbot", @@ -144,6 +145,7 @@ export const accessProvidersMap: Maphttps://ecloud.10086.cn/op-help-center/doc/article/49739", + "access.form.constellix_api_key.label": "Constellix API key", + "access.form.constellix_api_key.placeholder": "Please enter Constellix API key", + "access.form.constellix_api_key.tooltip": "For more information, see https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key", + "access.form.constellix_secret_key.label": "Constellix API secret key", + "access.form.constellix_secret_key.placeholder": "Please enter Constellix API secret key", + "access.form.constellix_secret_key.tooltip": "For more information, see https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key", "access.form.desec_token.label": "deSEC token", "access.form.desec_token.placeholder": "Please enter deSEC token", "access.form.desec_token.tooltip": "For more information, see https://desec.readthedocs.io/en/latest/auth/tokens.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 9e59d9d0..72f5e487 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -55,6 +55,7 @@ "provider.cloudflare": "Cloudflare", "provider.cloudns": "ClouDNS", "provider.cmcccloud": "China Mobile Cloud (ECloud)", + "provider.constellix": "Constellix", "provider.ctcccloud": "China Telecom Cloud (State Cloud)", "provider.cucccloud": "China Unicom Cloud", "provider.desec": "deSEC", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 66563f32..8af85792 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -146,6 +146,12 @@ "access.form.cmcccloud_access_key_secret.label": "移动云 AccessKeySecret", "access.form.cmcccloud_access_key_secret.placeholder": "请输入移动云 AccessKeySecret", "access.form.cmcccloud_access_key_secret.tooltip": "这是什么?请参阅 https://ecloud.10086.cn/op-help-center/doc/article/49739", + "access.form.constellix_api_key.label": "Constellix API Key", + "access.form.constellix_api_key.placeholder": "请输入 Constellix API Key", + "access.form.constellix_api_key.tooltip": "这是什么?请参阅 https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key", + "access.form.constellix_secret_key.label": "Constellix Secret Key", + "access.form.constellix_secret_key.placeholder": "请输入 Constellix Secret Key", + "access.form.constellix_secret_key.tooltip": "这是什么?请参阅 https://support.constellix.com/hc/en-us/articles/34574197390491-How-to-Generate-an-API-Key", "access.form.desec_token.label": "deSEC Token", "access.form.desec_token.placeholder": "请输入 deSEC Token", "access.form.desec_token.tooltip": "这是什么?请参阅 https://desec.readthedocs.io/en/latest/auth/tokens.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 27aa8d0e..79bba686 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -55,6 +55,7 @@ "provider.cloudflare": "Cloudflare", "provider.cloudns": "ClouDNS", "provider.cmcccloud": "移动云", + "provider.constellix": "Constellix", "provider.ctcccloud": "联通云", "provider.cucccloud": "天翼云", "provider.desec": "deSEC",