feat(provider): new deployment provider: cloudflare ssl

This commit is contained in:
Fu Diwei
2026-06-04 18:55:48 +08:00
committed by RHQYZ
parent f374fb7133
commit f4879f9d93
14 changed files with 332 additions and 7 deletions
+2
View File
@@ -44,6 +44,7 @@ require (
github.com/baidubce/bce-sdk-go v0.9.267
github.com/byteplus-sdk/byteplus-go-sdk-v2 v1.0.65
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.65
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
@@ -272,6 +273,7 @@ require (
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
+5
View File
@@ -299,6 +299,8 @@ 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=
@@ -960,6 +962,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.96 h1:3fSwkZOA
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.96/go.mod h1:8PWWzrPRuy6iYbINeI/hd9kZk4p9Pm5vCOXMw8a8kZs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.95 h1:7kkSVeUUOi1Sc7K/neN7y3qhdebQrQOqs0HScHBDGOg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.95/go.mod h1:JHnW2i0WR2wq62Z41prri06y5pLMZon7TavXGVpnYfQ=
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=
@@ -968,6 +971,8 @@ 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=
@@ -0,0 +1,27 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/cloudflare-ssl"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeCloudflareSSL, func(options *ProviderFactoryOptions) (core.Deployer, error) {
credentials := domain.AccessConfigForCloudflare{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
ApiToken: credentials.ApiToken,
Environment: xmaps.GetString(options.ProviderExtendedConfig, "environment"),
ZoneId: xmaps.GetString(options.ProviderExtendedConfig, "zoneId"),
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
})
return provider, err
})
}
+1
View File
@@ -355,6 +355,7 @@ const (
DeploymentProviderTypeBytePlusTOS = DeploymentProviderType(AccessProviderTypeBytePlus + "-tos")
DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly)
DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly)
DeploymentProviderTypeCloudflareSSL = DeploymentProviderType(AccessProviderTypeCloudflare + "-ssl")
DeploymentProviderTypeCPanel = DeploymentProviderType(AccessProviderTypeCPanel)
DeploymentProviderTypeCTCCCloudAO = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-ao")
DeploymentProviderTypeCTCCCloudCDN = DeploymentProviderType(AccessProviderTypeCTCCCloud + "-cdn")
@@ -0,0 +1,119 @@
package cloudflaressl
import (
"cmp"
"context"
"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/certimate-go/certimate/pkg/core"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// Cloudflare API Token。
ApiToken string `json:"apiToken"`
// Cloudflare 环境。
// 选填。
// 零值时默认值 "production"。
Environment string `json:"environment,omitempty"`
// Cloudflare DNS 区域 ID。
ZoneId string `json:"zoneId"`
// Cloudflare 证书 ID。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateId string `json:"certificateId,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *cfcertificates.CustomCertificateService
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.ApiToken)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
if d.config.ZoneId == "" {
return nil, fmt.Errorf("config `zoneId` is required")
}
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"))),
}
customCertificateNewResp, err := d.sdkClient.New(ctx, customCertificateNewReq)
d.logger.Debug("sdk request 'CustomCertificates.New'", slog.Any("request", customCertificateNewReq), slog.Any("response", customCertificateNewResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'CustomCertificates.New': %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"))),
}
customCertificateEditResp, err := d.sdkClient.Edit(ctx, d.config.CertificateId, 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)
}
}
return &DeployResult{}, nil
}
func createSDKClient(apiToken string) (*cfcertificates.CustomCertificateService, error) {
if apiToken == "" {
return nil, fmt.Errorf("cloudflare: invalid api token")
}
opts := append(cf.DefaultClientOptions(), cfoption.WithAPIToken(apiToken))
srv := cfcertificates.NewCustomCertificateService(opts...)
return srv, nil
}
@@ -0,0 +1,53 @@
package cloudflaressl_test
import (
"testing"
"github.com/certimate-go/certimate/pkg/core/deployer/internal/tester"
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/cloudflare-ssl"
)
var (
fp = tester.Args("CLOUDFLARESSL_")
fTestCertPath string
fTestKeyPath string
fApiToken string
fZoneId string
fCertificateId string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fApiToken, "APITOKEN")
fp.DefineString(&fZoneId, "ZONEID")
fp.DefineString(&fCertificateId, "CERTIFICATEID")
}
/*
Shell command to run this test:
go test -v ./cloudflare_ssl_test.go -args \
--CLOUDFLARESSL_TESTCERTPATH="/path/to/your-test-cert.pem" \
--CLOUDFLARESSL_TESTKEYPATH="/path/to/your-test-key.pem" \
--CLOUDFLARESSL_APITOKEN="your-api-token" \
--CLOUDFLARESSL_ZONEID="your-zone-id" \
--CLOUDFLARESSL_CERTIFICATEID="your-certificate-id"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
ApiToken: fApiToken,
ZoneId: fZoneId,
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -50,6 +50,7 @@ import BizDeployNodeConfigFieldsProviderBytePlusCLB from "./BizDeployNodeConfigF
import BizDeployNodeConfigFieldsProviderBytePlusMediaLive from "./BizDeployNodeConfigFieldsProviderBytePlusMediaLive";
import BizDeployNodeConfigFieldsProviderBytePlusTOS from "./BizDeployNodeConfigFieldsProviderBytePlusTOS";
import BizDeployNodeConfigFieldsProviderCdnfly from "./BizDeployNodeConfigFieldsProviderCdnfly";
import BizDeployNodeConfigFieldsProviderCloudflareSSL from "./BizDeployNodeConfigFieldsProviderCloudflareSSL";
import BizDeployNodeConfigFieldsProviderCPanel from "./BizDeployNodeConfigFieldsProviderCPanel";
import BizDeployNodeConfigFieldsProviderCTCCCloudAO from "./BizDeployNodeConfigFieldsProviderCTCCCloudAO";
import BizDeployNodeConfigFieldsProviderCTCCCloudCDN from "./BizDeployNodeConfigFieldsProviderCTCCCloudCDN";
@@ -189,6 +190,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.BYTEPLUS_MEDIALIVE]: BizDeployNodeConfigFieldsProviderBytePlusMediaLive,
[DEPLOYMENT_PROVIDERS.BYTEPLUS_TOS]: BizDeployNodeConfigFieldsProviderBytePlusTOS,
[DEPLOYMENT_PROVIDERS.CDNFLY]: BizDeployNodeConfigFieldsProviderCdnfly,
[DEPLOYMENT_PROVIDERS.CLOUDFLARE_SSL]: BizDeployNodeConfigFieldsProviderCloudflareSSL,
[DEPLOYMENT_PROVIDERS.CPANEL]: BizDeployNodeConfigFieldsProviderCPanel,
[DEPLOYMENT_PROVIDERS.CTCCCLOUD_AO]: BizDeployNodeConfigFieldsProviderCTCCCloudAO,
[DEPLOYMENT_PROVIDERS.CTCCCLOUD_CDN]: BizDeployNodeConfigFieldsProviderCTCCCloudCDN,
@@ -0,0 +1,80 @@
import { getI18n, useTranslation } from "react-i18next";
import { AutoComplete, Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderCloudflareSSL = () => {
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, "environment"]}
initialValue={initialValues.environment}
label={t("workflow_node.deploy.form.cloudflare_ssl_environment.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.cloudflare_ssl_environment.tooltip") }}></span>}
>
<AutoComplete
allowClear
options={["production", "staging"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.deploy.form.cloudflare_ssl_environment.placeholder")}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "zoneId"]}
initialValue={initialValues.zoneId}
label={t("workflow_node.deploy.form.cloudflare_ssl_zone_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.cloudflare_ssl_zone_id.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.cloudflare_ssl_zone_id.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "certificateId"]}
initialValue={initialValues.certificateId}
label={t("workflow_node.deploy.form.cloudflare_ssl_certificate_id.label")}
extra={t("workflow_node.deploy.form.cloudflare_ssl_certificate_id.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.cloudflare_ssl_certificate_id.tooltip") }}></span>}
>
<Input allowClear placeholder={t("workflow_node.deploy.form.cloudflare_ssl_certificate_id.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
environment: "production",
zoneId: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
environment: z.string().nullish(),
zoneId: z.string().nonempty(),
certificateId: z.string().nullish(),
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderCloudflareSSL, {
getInitialValues,
getSchema,
});
export default _default;
@@ -31,7 +31,7 @@ const BizDeployNodeConfigFieldsProviderWangsuCDNPro = () => {
rules={[formRule]}
>
<Select
options={["production", "stating"].map((s) => ({
options={["production", "staging"].map((s) => ({
label: t(`workflow_node.deploy.form.wangsu_cdnpro_environment.option.${s}.label`),
value: s,
}))}
+3 -1
View File
@@ -176,6 +176,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BUNNY, "provider.bunny", "/imgs/providers/bunny.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.NETLIFY, "provider.netlify", "/imgs/providers/netlify.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
@@ -217,7 +218,6 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.ARVANCLOUD, "provider.arvancloud", "/imgs/providers/arvancloud.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.BEGET, "provider.beget", "/imgs/providers/beget.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.BOOKMYNAME, "provider.bookmyname", "/imgs/providers/bookmyname.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CLOUDNS, "provider.cloudns", "/imgs/providers/cloudns.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CONOHAVPS, "provider.conohavps", "/imgs/providers/conohavps.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CONSTELLIX, "provider.constellix", "/imgs/providers/constellix.png", [ACCESS_USAGES.DNS]],
@@ -640,6 +640,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
BYTEPLUS_TOS: `${ACCESS_PROVIDERS.BYTEPLUS}-tos`,
CACHEFLY: `${ACCESS_PROVIDERS.CACHEFLY}`,
CDNFLY: `${ACCESS_PROVIDERS.CDNFLY}`,
CLOUDFLARE_SSL: `${ACCESS_PROVIDERS.CLOUDFLARE}-ssl`,
CPANEL: `${ACCESS_PROVIDERS.CPANEL}`,
CTCCCLOUD_AO: `${ACCESS_PROVIDERS.CTCCCLOUD}-ao`,
CTCCCLOUD_CDN: `${ACCESS_PROVIDERS.CTCCCLOUD}-cdn`,
@@ -874,6 +875,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.BYTEPLUS_APIG, "provider.byteplus_apig", DEPLOYMENT_CATEGORIES.APIGATEWAY],
[DEPLOYMENT_PROVIDERS.BYTEPLUS_CERTCENTER, "provider.byteplus_certcenter_upload", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.CLOUDFLARE_SSL, "provider.cloudflare_ssl", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.GCORE_CDN, "provider.gcore_cdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.MOHUA_MVH, "provider.mohua_mvh", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.FLYIO, "provider.flyio", DEPLOYMENT_CATEGORIES.WEBSITE],
@@ -105,6 +105,7 @@
"cachefly": "CacheFly",
"cdnfly": "Cdnfly",
"cloudflare": "Cloudflare",
"cloudflare_ssl": "Cloudflare - Edge Certificates",
"cloudns": "ClouDNS",
"cmcccloud": "China Mobile ECloud",
"cmcccloud_dns": "China Mobile ECloud - DNS",
@@ -819,12 +819,12 @@
"aliyun_ga_accelerator_id": {
"label": "Alibaba Cloud GA accelerator ID",
"placeholder": "Please enter Alibaba Cloud GA accelerator ID",
"tooltip": "For more information, <a href=\"https://ga.console.aliyun.com\" target=\"_blank\">https://ga.console.aliyun.com</a>"
"tooltip": "For more information, see <a href=\"https://ga.console.aliyun.com\" target=\"_blank\">https://ga.console.aliyun.com</a>"
},
"aliyun_ga_listener_id": {
"label": "Alibaba Cloud GA listener ID",
"placeholder": "Please enter Alibaba Cloud GA listener ID",
"tooltip": "For more information, <a href=\"https://ga.console.aliyun.com\" target=\"_blank\">https://ga.console.aliyun.com</a>"
"tooltip": "For more information, see <a href=\"https://ga.console.aliyun.com\" target=\"_blank\">https://ga.console.aliyun.com</a>"
},
"aliyun_ga_snidomain": {
"label": "Alibaba Cloud GA SNI domain (Optional)",
@@ -1412,6 +1412,22 @@
"placeholder": "Please enter Cdnfly certificate ID",
"tooltip": "You can find it on Cdnfly dashboard."
},
"cloudflare_ssl_environment": {
"label": "Cloudflare environment",
"placeholder": "Please enter Cloudflare environment",
"tooltip": "For more information, see <a href=\"https://developers.cloudflare.com/ssl/edge-certificates/staging-environment/\" target=\"_blank\">https://developers.cloudflare.com/ssl/edge-certificates/staging-environment/</a>"
},
"cloudflare_ssl_zone_id": {
"label": "Cloudflare DNS zone ID",
"placeholder": "Please enter Cloudflare DNS zone ID",
"tooltip": "For more information, see <a href=\"https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/#copy-your-zone-id\" target=\"_blank\">https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/</a>"
},
"cloudflare_ssl_certificate_id": {
"label": "Cloudflare custom certificate ID (Optional)",
"placeholder": "Please enter Cloudflare custom certificate ID",
"help": "Notes: Leave it blank to import a new certificate; otherwise, to replace the existing one.",
"tooltip": "For more information, see <a href=\"https://developers.cloudflare.com/ssl/edge-certificates/custom-certificates/uploading/\" target=\"_blank\">https://developers.cloudflare.com/ssl/edge-certificates/custom-certificates/uploading/</a>"
},
"cpanel_deploy_target": {
"option": {
"website": {
@@ -2698,7 +2714,7 @@
"zenlayer_cdn_certificate_id": {
"label": "Zenlayer CDN certificate ID",
"placeholder": "Please enter Zenlayer CDN certificate ID",
"tooltip": "For more information, <a href=\"https://console.zenlayer.com/cdn/\" target=\"_blank\">https://console.zenlayer.com/cdn/</a>"
"tooltip": "For more information, see <a href=\"https://console.zenlayer.com/cdn/\" target=\"_blank\">https://console.zenlayer.com/cdn/</a>"
},
"zenlayer_ga_deploy_target": {
"option": {
@@ -2713,12 +2729,12 @@
"zenlayer_ga_accelerator_id": {
"label": "Zenlayer ZGA accelerator ID",
"placeholder": "Please enter Zenlayer ZGA accelerator ID",
"tooltip": "For more information, <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>"
"tooltip": "For more information, see <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>"
},
"zenlayer_ga_certificate_id": {
"label": "Zenlayer ZGA certificate ID",
"placeholder": "Please enter Zenlayer ZGA certificate ID",
"tooltip": "For more information, <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>"
"tooltip": "For more information, see <a href=\"https://console.zenlayer.com/gia/\" target=\"_blank\">https://console.zenlayer.com/gia/</a>"
}
}
},
@@ -105,6 +105,7 @@
"cachefly": "CacheFly",
"cdnfly": "Cdnfly",
"cloudflare": "Cloudflare",
"cloudflare_ssl": "Cloudflare - 边缘证书",
"cloudns": "ClouDNS",
"cmcccloud": "移动云",
"cmcccloud_dns": "移动云 - 云解析 DNS",
@@ -1411,6 +1411,22 @@
"placeholder": "请输入 Cdnfly 证书 ID",
"tooltip": "请登录 Cdnfly 控制台查看"
},
"cloudflare_ssl_environment": {
"label": "Cloudflare 环境",
"placeholder": "请输入 Cloudflare 环境",
"tooltip": "这是什么?请参阅 <a href=\"https://developers.cloudflare.com/ssl/edge-certificates/staging-environment/\" target=\"_blank\">https://developers.cloudflare.com/ssl/edge-certificates/staging-environment/</a>"
},
"cloudflare_ssl_zone_id": {
"label": "Cloudflare DNS 区域 ID",
"placeholder": "请输入 Cloudflare DNS 区域 ID",
"tooltip": "这是什么?请参阅 <a href=\"https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/#copy-your-zone-id\" target=\"_blank\">https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/</a>"
},
"cloudflare_ssl_certificate_id": {
"label": "Cloudflare 自定义证书 ID(可选)",
"placeholder": "请输入 Cloudflare 自定义证书 ID",
"help": "提示:不填写时,将上传新证书;否则,将替换原证书。",
"tooltip": "这是什么?请参阅 <a href=\"https://developers.cloudflare.com/ssl/edge-certificates/custom-certificates/uploading/\" target=\"_blank\">https://developers.cloudflare.com/ssl/edge-certificates/custom-certificates/uploading/</a>"
},
"cpanel_deploy_target": {
"option": {
"website": {