From fd875feef390dbd00d67bcfb93f3e5b1689923f7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 16 May 2025 18:57:39 +0800 Subject: [PATCH 01/13] feat: support 1panel v2 --- internal/deployer/providers.go | 2 + internal/domain/access.go | 1 + .../1panel-console/1panel_console.go | 18 +++++--- .../1panel-console/1panel_console_test.go | 5 +++ .../providers/1panel-site/1panel_site.go | 29 +++++++----- .../providers/1panel-site/1panel_site_test.go | 5 +++ .../providers/1panel-ssl/1panel_ssl.go | 20 ++++++--- .../providers/1panel-ssl/1panel_ssl_test.go | 9 +++- internal/pkg/sdk3rd/1panel/client.go | 20 ++++++--- migrations/1747389600_upgrade.go | 44 +++++++++++++++++++ .../access/AccessForm1PanelConfig.tsx | 8 +++- ui/src/domain/access.ts | 1 + ui/src/i18n/locales/en/nls.access.json | 6 ++- ui/src/i18n/locales/en/nls.provider.json | 4 +- .../i18n/locales/en/nls.workflow.nodes.json | 4 +- ui/src/i18n/locales/zh/nls.access.json | 6 ++- ui/src/i18n/locales/zh/nls.provider.json | 4 +- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 18 files changed, 143 insertions(+), 45 deletions(-) create mode 100644 migrations/1747389600_upgrade.go diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index d69b05b7..79fa88c3 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -111,6 +111,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderType1PanelConsole: deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{ ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), @@ -120,6 +121,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderType1PanelSite: deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{ ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), diff --git a/internal/domain/access.go b/internal/domain/access.go index e27d290a..d08ea658 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -17,6 +17,7 @@ type Access struct { type AccessConfigFor1Panel struct { ApiUrl string `json:"apiUrl"` + ApiVersion string `json:"apiVersion"` ApiKey string `json:"apiKey"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go index 069e01f9..98073585 100644 --- a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go @@ -9,12 +9,14 @@ import ( "net/url" "github.com/usual2970/certimate/internal/pkg/core/deployer" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 @@ -26,7 +28,7 @@ type DeployerConfig struct { type DeployerProvider struct { config *DeployerConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client } var _ deployer.Deployer = (*DeployerProvider)(nil) @@ -36,7 +38,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } @@ -59,7 +61,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { // 设置面板 SSL 证书 - updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{ + updateSystemSSLReq := &onepanelsdk.UpdateSystemSSLRequest{ Cert: certPEM, Key: privkeyPEM, SSL: "enable", @@ -79,16 +81,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return &deployer.DeployResult{}, nil } -func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) if skipTlsVerify { client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go index abec586c..88bf961a 100644 --- a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go @@ -15,6 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string ) @@ -24,6 +25,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") } @@ -34,6 +36,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIVERSION="v1" \ --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" */ func TestDeploy(t *testing.T) { @@ -45,11 +48,13 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiVersion: fApiVersion, ApiKey: fApiKey, AllowInsecureConnections: true, AutoRestart: true, diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go index 7d360c77..afecaf74 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -12,12 +12,14 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 @@ -35,7 +37,7 @@ type DeployerConfig struct { type DeployerProvider struct { config *DeployerConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client sslUploader uploader.Uploader } @@ -46,14 +48,15 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ - ApiUrl: config.ApiUrl, - ApiKey: config.ApiKey, + ApiUrl: config.ApiUrl, + ApiVersion: config.ApiVersion, + ApiKey: config.ApiKey, }) if err != nil { return nil, fmt.Errorf("failed to create ssl uploader: %w", err) @@ -103,7 +106,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string, } // 获取网站 HTTPS 配置 - getHttpsConfReq := &opsdk.GetHttpsConfRequest{ + getHttpsConfReq := &onepanelsdk.GetHttpsConfRequest{ WebsiteID: d.config.WebsiteId, } getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq) @@ -122,7 +125,7 @@ func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPEM string, // 修改网站 HTTPS 配置 certId, _ := strconv.ParseInt(upres.CertId, 10, 64) - updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{ + updateHttpsConfReq := &onepanelsdk.UpdateHttpsConfRequest{ WebsiteID: d.config.WebsiteId, Type: "existed", WebsiteSSLID: certId, @@ -147,7 +150,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri } // 获取证书详情 - getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{ + getWebsiteSSLReq := &onepanelsdk.GetWebsiteSSLRequest{ SSLID: d.config.CertificateId, } getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq) @@ -157,7 +160,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri } // 更新证书 - uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{ Type: "paste", SSLID: d.config.CertificateId, Description: getWebsiteSSLResp.Data.Description, @@ -173,16 +176,20 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri return nil } -func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) if skipTlsVerify { client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go index 702584e3..1d5bafef 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go @@ -15,6 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string fWebsiteId int64 ) @@ -25,6 +26,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") flag.Int64Var(&fWebsiteId, argsPrefix+"WEBSITEID", 0, "") } @@ -36,6 +38,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELSITE_APIVERSION="v1" \ --CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \ --CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id" */ @@ -48,12 +51,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), fmt.Sprintf("WEBSITEID: %v", fWebsiteId), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiVersion: fApiVersion, ApiKey: fApiKey, AllowInsecureConnections: true, ResourceType: provider.RESOURCE_TYPE_WEBSITE, diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go index e5a0b0ba..63900125 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -10,12 +10,14 @@ import ( "time" "github.com/usual2970/certimate/internal/pkg/core/uploader" - opsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" + onepanelsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/1panel" ) type UploaderConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` + // 1Panel 版本。 + ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` } @@ -23,7 +25,7 @@ type UploaderConfig struct { type UploaderProvider struct { config *UploaderConfig logger *slog.Logger - sdkClient *opsdk.Client + sdkClient *onepanelsdk.Client } var _ uploader.Uploader = (*UploaderProvider)(nil) @@ -33,7 +35,7 @@ func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } @@ -67,7 +69,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) // 上传证书 - uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{ Type: "paste", Description: certName, Certificate: certPEM, @@ -99,7 +101,7 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, default: } - searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{ + searchWebsiteSSLReq := &onepanelsdk.SearchWebsiteSSLRequest{ Page: searchWebsiteSSLPageNumber, PageSize: searchWebsiteSSLPageSize, } @@ -130,15 +132,19 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, return nil, nil } -func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } + if apiVersion == "" { + return nil, errors.New("invalid 1panel api version") + } + if apiKey == "" { return nil, errors.New("invalid 1panel api key") } - client := opsdk.NewClient(apiUrl, apiKey) + client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) return client, nil } diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go index 257030f5..cfb250be 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go @@ -16,6 +16,7 @@ var ( fInputCertPath string fInputKeyPath string fApiUrl string + fApiVersion string fApiKey string ) @@ -25,6 +26,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "") flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") } @@ -35,6 +37,7 @@ Shell command to run this test: --CERTIMATE_UPLOADER_1PANELSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_UPLOADER_1PANELSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_UPLOADER_1PANELSSL_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_UPLOADER_1PANELSSL_APIVERSION="v1" \ --CERTIMATE_UPLOADER_1PANELSSL_APIKEY="your-api-key" */ func TestDeploy(t *testing.T) { @@ -46,12 +49,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), fmt.Sprintf("APIKEY: %v", fApiKey), }, "\n")) uploader, err := provider.NewUploader(&provider.UploaderConfig{ - ApiUrl: fApiUrl, - ApiKey: fApiKey, + ApiUrl: fApiUrl, + ApiVersion: fApiVersion, + ApiKey: fApiKey, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/sdk3rd/1panel/client.go b/internal/pkg/sdk3rd/1panel/client.go index e8946bae..4480ae49 100644 --- a/internal/pkg/sdk3rd/1panel/client.go +++ b/internal/pkg/sdk3rd/1panel/client.go @@ -14,19 +14,25 @@ import ( ) type Client struct { - apiHost string - apiKey string + apiHost string + apiVersion string + apiKey string client *resty.Client } -func NewClient(apiHost, apiKey string) *Client { +func NewClient(apiHost, apiVersion, apiKey string) *Client { client := resty.New() + if apiVersion == "" { + apiVersion = "v1" + } + return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - client: client, + apiHost: strings.TrimRight(apiHost, "/"), + apiVersion: apiVersion, + apiKey: apiKey, + client: client, } } @@ -49,7 +55,7 @@ func (c *Client) generateToken(timestamp string) string { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() req.Method = method - req.URL = c.apiHost + "/api/v1" + path + req.URL = c.apiHost + "/api/" + c.apiVersion + path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { diff --git a/migrations/1747389600_upgrade.go b/migrations/1747389600_upgrade.go new file mode 100644 index 00000000..aa5e2e3a --- /dev/null +++ b/migrations/1747389600_upgrade.go @@ -0,0 +1,44 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + // migrate data + { + accesses, err := app.FindAllRecords("access") + if err != nil { + return err + } + + for _, access := range accesses { + changed := false + + if access.GetString("provider") == "1panel" { + config := make(map[string]any) + if err := access.UnmarshalJSONField("config", &config); err != nil { + return err + } + + config["apiVersion"] = "v1" + access.Set("config", config) + changed = true + } + + if changed { + err = app.Save(access) + if err != nil { + return err + } + } + } + } + + return nil + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/src/components/access/AccessForm1PanelConfig.tsx b/ui/src/components/access/AccessForm1PanelConfig.tsx index c0762bbd..29481f15 100644 --- a/ui/src/components/access/AccessForm1PanelConfig.tsx +++ b/ui/src/components/access/AccessForm1PanelConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Switch } from "antd"; +import { Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -18,6 +18,7 @@ export type AccessForm1PanelConfigProps = { const initFormModel = (): AccessForm1PanelConfigFieldValues => { return { apiUrl: "http://:20410/", + apiVersion: "v1", apiKey: "", }; }; @@ -27,6 +28,7 @@ const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialVal const formSchema = z.object({ apiUrl: z.string().url(t("common.errmsg.url_invalid")), + apiVersion: z.string().nonempty(t("access.form.1panel_api_version.placeholder")), apiKey: z .string() .min(1, t("access.form.1panel_api_key.placeholder")) @@ -53,6 +55,10 @@ const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialVal + + + + + } + > + + + + } + > + + + + + + + + ); +}; + +export default AccessFormRatPanelConfig; diff --git a/ui/src/components/provider/DeploymentProviderPicker.tsx b/ui/src/components/provider/DeploymentProviderPicker.tsx index 0ea5a97c..b1bcd6fe 100644 --- a/ui/src/components/provider/DeploymentProviderPicker.tsx +++ b/ui/src/components/provider/DeploymentProviderPicker.tsx @@ -72,8 +72,10 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeho DEPLOYMENT_CATEGORIES.LOADBALANCE, DEPLOYMENT_CATEGORIES.FIREWALL, DEPLOYMENT_CATEGORIES.AV, + DEPLOYMENT_CATEGORIES.APIGATEWAY, DEPLOYMENT_CATEGORIES.SERVERLESS, DEPLOYMENT_CATEGORIES.WEBSITE, + DEPLOYMENT_CATEGORIES.SSL, DEPLOYMENT_CATEGORIES.NAS, DEPLOYMENT_CATEGORIES.OTHER, ].map((key) => ({ diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 9e4ade18..b77ff9cf 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -63,6 +63,7 @@ import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNCo import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig"; import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormRainYunRCDNConfig from "./DeployNodeConfigFormRainYunRCDNConfig"; +import DeployNodeConfigFormRatPanelSiteConfig from "./DeployNodeConfigFormRatPanelSiteConfig"; import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx"; @@ -273,6 +274,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.RAINYUN_RCDN: return ; + case DEPLOYMENT_PROVIDERS.RATPANEL_SITE: + return ; case DEPLOYMENT_PROVIDERS.SAFELINE: return ; case DEPLOYMENT_PROVIDERS.SSH: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx new file mode 100644 index 00000000..03f86913 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormRatPanelSiteConfig.tsx @@ -0,0 +1,63 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormRatPanelSiteConfigFieldValues = Nullish<{ + siteName: string; +}>; + +export type DeployNodeConfigFormRatPanelSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormRatPanelSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormRatPanelSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormRatPanelSiteConfigFieldValues => { + return { + siteName: "", + }; +}; + +const DeployNodeConfigFormRatPanelSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormRatPanelSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.ratpanel_site_name.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormRatPanelSiteConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx index 2da3ef16..c5905e14 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSafeLineConfig.tsx @@ -68,7 +68,12 @@ const DeployNodeConfigFormSafeLineConfig = ({ form: formInst, formName, disabled - + } + > diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 870b6490..fe912543 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -48,6 +48,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForProxmoxVE | AccessConfigForQiniu | AccessConfigForRainYun + | AccessConfigForRatPanel | AccessConfigForSafeLine | AccessConfigForSSH | AccessConfigForSSLCom @@ -293,6 +294,13 @@ export type AccessConfigForRainYun = { apiKey: string; }; +export type AccessConfigForRatPanel = { + apiUrl: string; + accessTokenId: number; + accessToken: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForSafeLine = { apiUrl: string; apiToken: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 878814f5..3d0678ba 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -51,6 +51,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ PROXMOXVE: "proxmoxve", QINIU: "qiniu", RAINYUN: "rainyun", + RATPANEL: "ratpanel", SAFELINE: "safeline", SSH: "ssh", SSLCOM: "sslcom", @@ -120,6 +121,7 @@ export const accessProvidersMap: Map [ diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 9ba645cb..b3f9167f 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -313,6 +313,17 @@ "access.form.rainyun_api_key.label": "Rain Yun API key", "access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key", "access.form.rainyun_api_key.tooltip": "For more information, see https://app.rainyun.com/account/settings/api-key", + "access.form.ratpanel_api_url.label": "RatPanel URL", + "access.form.ratpanel_api_url.placeholder": "Please enter RatPanel URL", + "access.form.ratpanel_access_token_id.label": "RatPanel access token ID", + "access.form.ratpanel_access_token_id.placeholder": "Please enter RatPanel access token ID", + "access.form.ratpanel_access_token_id.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_access_token.label": "RatPanel access token", + "access.form.ratpanel_access_token.placeholder": "Please enter RatPanel access token", + "access.form.ratpanel_access_token.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.ratpanel_allow_insecure_conns.switch.on": "Allow", + "access.form.ratpanel_allow_insecure_conns.switch.off": "Disallow", "access.form.safeline_api_url.label": "SafeLine URL", "access.form.safeline_api_url.placeholder": "Please enter SafeLine URL", "access.form.safeline_api_token.label": "SafeLine API token", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 041f5a63..13571036 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -103,6 +103,9 @@ "provider.qiniu.pili": "Qiniu - Pili", "provider.rainyun": "Rain Yun", "provider.rainyun.rcdn": "Rain Yun - RCDN (Rain Content Delivery Network)", + "provider.ratpanel": "RatPanel", + "provider.ratpanel.console": "RatPanel - Console", + "provider.ratpanel.site": "RatPanel - Website", "provider.safeline": "SafeLine", "provider.ssh": "SSH deployment", "provider.sslcom": "SSL.com", @@ -150,8 +153,10 @@ "provider.category.loadbalance": "Loadbalance", "provider.category.firewall": "Firewall", "provider.category.av": "Audio/Video", + "provider.category.apigw": "API Gateway", "provider.category.serverless": "Serverless", "provider.category.website": "Website", + "provider.category.ssl": "SSL", "provider.category.nas": "NAS", "provider.category.other": "Other", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index fce606e5..006979f1 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -325,7 +325,7 @@ "workflow_node.deploy.form.baotapanel_site_type.option.other.label": "Other sites", "workflow_node.deploy.form.baotapanel_site_name.label": "aaPanel site name", "workflow_node.deploy.form.baotapanel_site_name.placeholder": "Please enter aaPanel site name", - "workflow_node.deploy.form.baotapanel_site_name.tooltip": "Usually equal to the website domain name.", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "You can find it on aaPanel WebUI.", "workflow_node.deploy.form.baotapanel_site_names.label": "aaPanel site names", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter aaPanel site names (separated by semicolons)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid aaPanel site name", @@ -513,11 +513,15 @@ "workflow_node.deploy.form.rainyun_rcdn_domain.label": "Rain Yun RCDN domain", "workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "Please enter Rain Yun RCDN domain name", "workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "For more information, see https://app.rainyun.com/apps/rcdn/list", + "workflow_node.deploy.form.ratpanel_site_name.label": "RatPanel site name", + "workflow_node.deploy.form.ratpanel_site_name.placeholder": "Please enter RatPanel site name", + "workflow_node.deploy.form.ratpanel_site_name.tooltip": "You can find it on RatPanel WebUI.", "workflow_node.deploy.form.safeline_resource_type.label": "Resource type", "workflow_node.deploy.form.safeline_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.safeline_certificate_id.label": "SafeLine certificate ID", "workflow_node.deploy.form.safeline_certificate_id.placeholder": "Please enter SafeLine certificate ID", + "workflow_node.deploy.form.safeline_certificate_id.tooltip": "You can find it on SafeLine WebUI.", "workflow_node.deploy.form.ssh_format.label": "File format", "workflow_node.deploy.form.ssh_format.placeholder": "Please select file format", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 04af883a..5e3444fe 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -307,6 +307,17 @@ "access.form.rainyun_api_key.label": "雨云 API 密钥", "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥", "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://app.rainyun.com/account/settings/api-key", + "access.form.ratpanel_api_url.label": "耗子面板 URL", + "access.form.ratpanel_api_url.placeholder": "请输入耗子面板 URL", + "access.form.ratpanel_access_token_id.label": "耗子面板 AccessToken ID", + "access.form.ratpanel_access_token_id.placeholder": "请输入耗子面板 AccessToken ID", + "access.form.ratpanel_access_token_id.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_access_token.label": "耗子面板 AccessToken", + "access.form.ratpanel_access_token.placeholder": "请输入耗子面板 AccessToken", + "access.form.ratpanel_access_token.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html", + "access.form.ratpanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.ratpanel_allow_insecure_conns.switch.on": "允许", + "access.form.ratpanel_allow_insecure_conns.switch.off": "不允许", "access.form.safeline_api_url.label": "雷池 URL", "access.form.safeline_api_url.placeholder": "请输入雷池 URL", "access.form.safeline_api_token.label": "雷池 API Token", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e9ff69e0..bd04cc43 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -103,6 +103,9 @@ "provider.qiniu.pili": "七牛云 - 视频直播 Pili", "provider.rainyun": "雨云", "provider.rainyun.rcdn": "雨云 - 雨盾 CDN", + "provider.ratpanel": "耗子面板", + "provider.ratpanel.console": "耗子面板 - 面板", + "provider.ratpanel.site": "耗子面板 - 网站", "provider.safeline": "雷池", "provider.ssh": "SSH 部署", "provider.sslcom": "SSL.com", @@ -150,8 +153,10 @@ "provider.category.loadbalance": "负载均衡", "provider.category.firewall": "防火墙", "provider.category.av": "音视频", + "provider.category.apigw": "API 网关", "provider.category.serverless": "Serverless", "provider.category.website": "网站托管", + "provider.category.ssl": "证书托管", "provider.category.nas": "NAS", "provider.category.other": "其他", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index b7992acf..acc5657f 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -115,10 +115,10 @@ "workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", "workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID", - "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请在 1Panel 管理面板查看。", + "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请登录 1Panel 管理面板查看。", "workflow_node.deploy.form.1panel_site_certificate_id.label": "1Panel 证书 ID", "workflow_node.deploy.form.1panel_site_certificate_id.placeholder": "请输入 1Panel 证书 ID", - "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请在 1Panel 管理面板查看。", + "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请登录 1Panel 管理面板查看。", "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", @@ -324,7 +324,7 @@ "workflow_node.deploy.form.baotapanel_site_type.option.other.label": "其他", "workflow_node.deploy.form.baotapanel_site_name.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_name.placeholder": "请输入宝塔面板网站名称", - "workflow_node.deploy.form.baotapanel_site_name.tooltip": "通常为网站域名。", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看", "workflow_node.deploy.form.baotapanel_site_names.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "请输入宝塔面板网站名称(多个值请用半角分号隔开)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "请输入正确的宝塔面板网站名称", @@ -512,11 +512,15 @@ "workflow_node.deploy.form.rainyun_rcdn_domain.label": "雨云 RCDN 加速域名", "workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "请输入雨云 RCDN 加速域名(支持泛域名)", "workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "这是什么?请参阅 https://app.rainyun.com/apps/rcdn/list", + "workflow_node.deploy.form.ratpanel_site_name.label": "耗子面板网站名称", + "workflow_node.deploy.form.ratpanel_site_name.placeholder": "请输入耗子面板网站名称", + "workflow_node.deploy.form.ratpanel_site_name.tooltip": "请登录耗子面板查看。", "workflow_node.deploy.form.safeline_resource_type.label": "证书替换方式", "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.safeline_certificate_id.label": "雷池证书 ID", "workflow_node.deploy.form.safeline_certificate_id.placeholder": "请输入雷池证书 ID", + "workflow_node.deploy.form.safeline_certificate_id.tooltip": "请登录雷池控制台查看。", "workflow_node.deploy.form.ssh_format.label": "文件格式", "workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)", From 122d766cab7304ed28d4ff23e03063db0984cdea Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 16 May 2025 21:40:40 +0800 Subject: [PATCH 03/13] feat: new ca provider: custom acme ca --- internal/applicant/acme_ca.go | 33 ++++---- internal/applicant/acme_user.go | 54 ++++++++++--- internal/applicant/applicant.go | 33 +++++--- internal/applicant/providers.go | 1 + internal/domain/access.go | 6 ++ internal/domain/provider.go | 3 +- .../workflow/node-processor/apply_node.go | 4 +- ui/public/imgs/providers/acmeca.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormACMECAConfig.tsx | 77 +++++++++++++++++++ ui/src/domain/access.ts | 7 ++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 7 ++ ui/src/i18n/locales/en/nls.provider.json | 3 +- ui/src/i18n/locales/zh/nls.access.json | 7 ++ ui/src/i18n/locales/zh/nls.provider.json | 3 +- 16 files changed, 206 insertions(+), 40 deletions(-) create mode 100644 ui/public/imgs/providers/acmeca.svg create mode 100644 ui/src/components/access/AccessFormACMECAConfig.tsx diff --git a/internal/applicant/acme_ca.go b/internal/applicant/acme_ca.go index 67b7693e..36c0a0a4 100644 --- a/internal/applicant/acme_ca.go +++ b/internal/applicant/acme_ca.go @@ -3,25 +3,26 @@ package applicant import "github.com/usual2970/certimate/internal/domain" const ( - sslProviderLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt) - sslProviderLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging) - sslProviderBuypass = string(domain.CAProviderTypeBuypass) - sslProviderGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices) - sslProviderSSLCom = string(domain.CAProviderTypeSSLCom) - sslProviderZeroSSL = string(domain.CAProviderTypeZeroSSL) + caLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt) + caLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging) + caBuypass = string(domain.CAProviderTypeBuypass) + caGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices) + caSSLCom = string(domain.CAProviderTypeSSLCom) + caZeroSSL = string(domain.CAProviderTypeZeroSSL) + caCustom = string(domain.CAProviderTypeACMECA) - sslProviderDefault = sslProviderLetsEncrypt + caDefault = caLetsEncrypt ) -var sslProviderUrls = map[string]string{ - sslProviderLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory", - sslProviderLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory", - sslProviderBuypass: "https://api.buypass.com/acme/directory", - sslProviderGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory", - sslProviderSSLCom: "https://acme.ssl.com/sslcom-dv-rsa", - sslProviderSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa", - sslProviderSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc", - sslProviderZeroSSL: "https://acme.zerossl.com/v2/DV90", +var caDirUrls = map[string]string{ + caLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory", + caLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory", + caBuypass: "https://api.buypass.com/acme/directory", + caGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory", + caSSLCom: "https://acme.ssl.com/sslcom-dv-rsa", + caSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa", + caSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc", + caZeroSSL: "https://acme.zerossl.com/v2/DV90", } type acmeSSLProviderConfig struct { diff --git a/internal/applicant/acme_user.go b/internal/applicant/acme_user.go index 29ac80cd..e6e13cb7 100644 --- a/internal/applicant/acme_user.go +++ b/internal/applicant/acme_user.go @@ -7,6 +7,7 @@ import ( "crypto/elliptic" "crypto/rand" "fmt" + "strings" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/registration" @@ -19,22 +20,31 @@ import ( ) type acmeUser struct { - CA string - Email string + // 证书颁发机构标识。 + // 通常等同于 [CAProviderType] 的值。 + // 对于自定义 ACME CA,值为 "custom#{access_id}"。 + CA string + // 邮箱。 + Email string + // 注册信息。 Registration *registration.Resource + // CSR 私钥。 privkey string } -func newAcmeUser(ca, email string) (*acmeUser, error) { +func newAcmeUser(ca, caAccessId, email string) (*acmeUser, error) { repo := repository.NewAcmeAccountRepository() applyUser := &acmeUser{ CA: ca, Email: email, } + if ca == caCustom { + applyUser.CA = fmt.Sprintf("%s#%s", ca, caAccessId) + } - acmeAccount, err := repo.GetByCAAndEmail(ca, email) + acmeAccount, err := repo.GetByCAAndEmail(applyUser.CA, applyUser.Email) if err != nil { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { @@ -73,6 +83,10 @@ func (u *acmeUser) hasRegistration() bool { return u.Registration != nil } +func (u *acmeUser) getCAProvider() string { + return strings.Split(u.CA, "#")[0] +} + func (u *acmeUser) getPrivateKeyPEM() string { return u.privkey } @@ -94,16 +108,16 @@ func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userR func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) { var reg *registration.Resource var err error - switch user.CA { - case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging: + switch user.getCAProvider() { + case caLetsEncrypt, caLetsEncryptStaging: reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - case sslProviderBuypass: + case caBuypass: { reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) } - case sslProviderGoogleTrustServices: + case caGoogleTrustServices: { access := domain.AccessConfigForGoogleTrustServices{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -117,7 +131,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } - case sslProviderSSLCom: + case caSSLCom: { access := domain.AccessConfigForSSLCom{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -131,7 +145,7 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } - case sslProviderZeroSSL: + case caZeroSSL: { access := domain.AccessConfigForZeroSSL{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { @@ -145,6 +159,26 @@ func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions m }) } + case caCustom: + { + access := domain.AccessConfigForACMECA{} + if err := maputil.Populate(userRegisterOptions, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + if access.EabKid == "" && access.EabHmacKey == "" { + reg, err = client.Registration.Register(registration.RegisterOptions{ + TermsOfServiceAgreed: true, + }) + } else { + reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ + TermsOfServiceAgreed: true, + Kid: access.EabKid, + HmacEncoded: access.EabHmacKey, + }) + } + } + default: err = fmt.Errorf("unsupported ca provider '%s'", user.CA) } diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index e9ed4cb1..6d17e940 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -20,12 +20,13 @@ import ( "golang.org/x/time/rate" "github.com/usual2970/certimate/internal/domain" + maputil "github.com/usual2970/certimate/internal/pkg/utils/map" sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" "github.com/usual2970/certimate/internal/repository" ) type ApplyResult struct { - CertificateFullChain string + FullChainCertificate string IssuerCertificate string PrivateKey string ACMEAccountUrl string @@ -81,6 +82,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err if access, err := accessRepo.GetById(context.Background(), nodeConfig.CAProviderAccessId); err != nil { return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.CAProviderAccessId, err) } else { + options.CAProviderAccessId = access.Id options.CAProviderAccessConfig = access.Config } } @@ -91,13 +93,13 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err sslProviderConfig := &acmeSSLProviderConfig{ Config: make(map[domain.CAProviderType]map[string]any), - Provider: sslProviderDefault, + Provider: caDefault, } if settings != nil { if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil { return nil, err } else if sslProviderConfig.Provider == "" { - sslProviderConfig.Provider = sslProviderDefault + sslProviderConfig.Provider = caDefault } } @@ -163,7 +165,7 @@ func getLimiter(key string) *rate.Limiter { } func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) { - user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail) + user, err := newAcmeUser(string(options.CAProvider), options.CAProviderAccessId, options.ContactEmail) if err != nil { return nil, err } @@ -175,13 +177,26 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt // Create an ACME client config config := lego.NewConfig(user) config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) - config.CADirURL = sslProviderUrls[user.CA] - if user.CA == sslProviderSSLCom { + switch user.getCAProvider() { + case caSSLCom: if strings.HasPrefix(options.KeyAlgorithm, "RSA") { - config.CADirURL = sslProviderUrls[sslProviderSSLCom+"RSA"] + config.CADirURL = caDirUrls[caSSLCom+"RSA"] } else if strings.HasPrefix(options.KeyAlgorithm, "EC") { - config.CADirURL = sslProviderUrls[sslProviderSSLCom+"ECC"] + config.CADirURL = caDirUrls[caSSLCom+"ECC"] + } else { + config.CADirURL = caDirUrls[caSSLCom] } + + case caCustom: + caDirURL := maputil.GetString(options.CAProviderAccessConfig, "endpoint") + if caDirURL != "" { + config.CADirURL = caDirURL + } else { + return nil, fmt.Errorf("invalid ca provider endpoint") + } + + default: + config.CADirURL = caDirUrls[user.CA] } // Create an ACME client @@ -229,7 +244,7 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt } return &ApplyResult{ - CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)), + FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), ACMEAccountUrl: user.Registration.URI, diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 90a3cf72..bf27e7e3 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -48,6 +48,7 @@ type applicantProviderOptions struct { ProviderAccessConfig map[string]any ProviderExtendedConfig map[string]any CAProvider domain.CAProviderType + CAProviderAccessId string CAProviderAccessConfig map[string]any CAProviderExtendedConfig map[string]any KeyAlgorithm string diff --git a/internal/domain/access.go b/internal/domain/access.go index 9ab18b9f..0321cb41 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -22,6 +22,12 @@ type AccessConfigFor1Panel struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForACMECA struct { + Endpoint string `json:"endpoint"` + EabKid string `json:"eabKid,omitempty"` + EabHmacKey string `json:"eabHmacKey,omitempty"` +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode,omitempty"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 4a0c05dd..d4d9af2e 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -10,7 +10,7 @@ type AccessProviderType string */ const ( AccessProviderType1Panel = AccessProviderType("1panel") - AccessProviderTypeACMECA = AccessProviderType("acmeca") // ACME CA(预留) + AccessProviderTypeACMECA = AccessProviderType("acmeca") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") @@ -91,6 +91,7 @@ type CAProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( + CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA) CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass) CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices) CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt) diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index 97b7575d..ff8c573d 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -66,14 +66,14 @@ func (n *applyNode) Process(ctx context.Context) error { } // 解析证书并生成实体 - certX509, err := certutil.ParseCertificateFromPEM(applyResult.CertificateFullChain) + certX509, err := certutil.ParseCertificateFromPEM(applyResult.FullChainCertificate) if err != nil { n.logger.Warn("failed to parse certificate, may be the CA responded error") return err } certificate := &domain.Certificate{ Source: domain.CertificateSourceTypeWorkflow, - Certificate: applyResult.CertificateFullChain, + Certificate: applyResult.FullChainCertificate, PrivateKey: applyResult.PrivateKey, IssuerCertificate: applyResult.IssuerCertificate, ACMEAccountUrl: applyResult.ACMEAccountUrl, diff --git a/ui/public/imgs/providers/acmeca.svg b/ui/public/imgs/providers/acmeca.svg new file mode 100644 index 00000000..260530c9 --- /dev/null +++ b/ui/public/imgs/providers/acmeca.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 3e31dad4..4981a045 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -12,6 +12,7 @@ import { ACCESS_PROVIDERS, ACCESS_USAGES, type AccessProvider } from "@/domain/p import { useAntdForm, useAntdFormName } from "@/hooks"; import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; +import AccessFormACMECAConfig from "./AccessFormACMECAConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAWSConfig from "./AccessFormAWSConfig"; @@ -177,6 +178,8 @@ const AccessForm = forwardRef(({ className, switch (fieldProvider) { case ACCESS_PROVIDERS["1PANEL"]: return ; + case ACCESS_PROVIDERS.ACMECA: + return ; case ACCESS_PROVIDERS.ACMEHTTPREQ: return ; case ACCESS_PROVIDERS.ALIYUN: diff --git a/ui/src/components/access/AccessFormACMECAConfig.tsx b/ui/src/components/access/AccessFormACMECAConfig.tsx new file mode 100644 index 00000000..8c9eb2ac --- /dev/null +++ b/ui/src/components/access/AccessFormACMECAConfig.tsx @@ -0,0 +1,77 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForACMECA } from "@/domain/access"; + +type AccessFormACMECAConfigFieldValues = Nullish; + +export type AccessFormACMECAConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormACMECAConfigFieldValues; + onValuesChange?: (values: AccessFormACMECAConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormACMECAConfigFieldValues => { + return { + endpoint: "https://example.com/acme/directory", + }; +}; + +const AccessFormACMECAConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormACMECAConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + endpoint: z.string().url(t("common.errmsg.url_invalid")), + eabKid: z.string().trim().nullish(), + eabHmacKey: z.string().trim().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormACMECAConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index fe912543..c676b5f7 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -7,6 +7,7 @@ export interface AccessModel extends BaseModel { */ Record & ( | AccessConfigFor1Panel + | AccessConfigForACMECA | AccessConfigForACMEHttpReq | AccessConfigForAliyun | AccessConfigForAWS @@ -75,6 +76,12 @@ export type AccessConfigFor1Panel = { allowInsecureConnections?: boolean; }; +export type AccessConfigForACMECA = { + endpoint: string; + eabKid?: string; + eabHmacKey?: string; +}; + export type AccessConfigForACMEHttpReq = { endpoint: string; mode?: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 3d0678ba..2c6f0f0f 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -5,6 +5,7 @@ */ export const ACCESS_PROVIDERS = Object.freeze({ ["1PANEL"]: "1panel", + ACMECA: "acmeca", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", AWS: "aws", @@ -153,6 +154,7 @@ export const accessProvidersMap: Map = new [CA_PROVIDERS.GOOGLETRUSTSERVICES], [CA_PROVIDERS.SSLCOM], [CA_PROVIDERS.ZEROSSL], + [CA_PROVIDERS.ACMECA], ].map(([type, builtin]) => [ type, { diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index b3f9167f..4b178ad4 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -44,6 +44,13 @@ "access.form.1panel_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.1panel_allow_insecure_conns.switch.on": "Allow", "access.form.1panel_allow_insecure_conns.switch.off": "Disallow", + "access.form.acmeca_endpoint.label": "Endpoint", + "access.form.acmeca_endpoint.placeholder": "Please enter endpoint", + "access.form.acmeca_endpoint.tooltip": "For more information, see https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1", + "access.form.acmeca_eab_kid.label": "ACME EAB KID (Optional)", + "access.form.acmeca_eab_kid.placeholder": "Please enter ACME EAB KID", + "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC key (Optional)", + "access.form.acmeca_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 13571036..dad0ae6f 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -2,7 +2,8 @@ "provider.1panel": "1Panel", "provider.1panel.console": "1Panel - Console", "provider.1panel.site": "1Panel - Website", - "provider.acmehttpreq": "Http Request (ACME Proxy)", + "provider.acmeca": "ACME Custom CA Endpoint", + "provider.acmehttpreq": "ACME Custom HTTP Endpoint", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", "provider.aliyun.apigw": "Alibaba Cloud - API Gateway", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 5e3444fe..1ca78d07 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -44,6 +44,13 @@ "access.form.1panel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.1panel_allow_insecure_conns.switch.on": "允许", "access.form.1panel_allow_insecure_conns.switch.off": "不允许", + "access.form.acmeca_endpoint.label": "服务端点", + "access.form.acmeca_endpoint.placeholder": "请输入服务端点", + "access.form.acmeca_endpoint.tooltip": "这是什么?请参阅 https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1", + "access.form.acmeca_eab_kid.label": "ACME EAB KID(可选)", + "access.form.acmeca_eab_kid.placeholder": "请输入 ACME EAB KID", + "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC Key(可选)", + "access.form.acmeca_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index bd04cc43..e7c6ea31 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -2,7 +2,8 @@ "provider.1panel": "1Panel", "provider.1panel.console": "1Panel - 面板", "provider.1panel.site": "1Panel - 网站", - "provider.acmehttpreq": "Http Request (ACME Proxy)", + "provider.acmeca": "ACME 自定义 CA 端点", + "provider.acmehttpreq": "ACME 自定义 HTTP 端点", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", "provider.aliyun.apigw": "阿里云 - API 网关", From eabd16dd715e861f0f468144fa005f79f433fa87 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 16 May 2025 22:18:03 +0800 Subject: [PATCH 04/13] feat: new deployment provider: flexcdn --- internal/deployer/providers.go | 20 +++ internal/domain/access.go | 8 + internal/domain/provider.go | 6 +- .../core/deployer/providers/flexcdn/consts.go | 8 + .../deployer/providers/flexcdn/flexcdn.go | 144 ++++++++++++++++++ .../providers/flexcdn/flexcdn_test.go | 83 ++++++++++ .../deployer/providers/goedge/goedge_test.go | 1 + internal/pkg/sdk3rd/flexcdn/api.go | 46 ++++++ internal/pkg/sdk3rd/flexcdn/client.go | 103 +++++++++++++ internal/pkg/sdk3rd/flexcdn/models.go | 52 +++++++ internal/pkg/sdk3rd/goedge/client.go | 4 +- internal/pkg/sdk3rd/upyun/console/client.go | 4 +- ui/public/imgs/providers/flexcdn.png | Bin 0 -> 13243 bytes ui/src/components/access/AccessForm.tsx | 11 +- .../access/AccessFormFlexCDNConfig.tsx | 90 +++++++++++ .../access/AccessFormGoEdgeConfig.tsx | 12 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormCdnflyConfig.tsx | 14 +- .../DeployNodeConfigFormFlexCDNConfig.tsx | 84 ++++++++++ .../node/DeployNodeConfigFormGoEdgeConfig.tsx | 7 +- ui/src/domain/access.ts | 9 ++ ui/src/domain/provider.ts | 8 +- ui/src/i18n/locales/en/nls.access.json | 15 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 9 ++ ui/src/i18n/locales/zh/nls.access.json | 15 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 ++ 28 files changed, 741 insertions(+), 26 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/flexcdn/consts.go create mode 100644 internal/pkg/core/deployer/providers/flexcdn/flexcdn.go create mode 100644 internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go create mode 100644 internal/pkg/sdk3rd/flexcdn/api.go create mode 100644 internal/pkg/sdk3rd/flexcdn/client.go create mode 100644 internal/pkg/sdk3rd/flexcdn/models.go create mode 100644 ui/public/imgs/providers/flexcdn.png create mode 100644 ui/src/components/access/AccessFormFlexCDNConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 9db3a0fb..bf7483d9 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -40,6 +40,7 @@ import ( pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly" pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" pEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications" + pFlexCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" pGcoreCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/gcore-cdn" pGoEdge "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/goedge" pHuaweiCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn" @@ -557,6 +558,25 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return deployer, err } + case domain.DeploymentProviderTypeFlexCDN: + { + access := domain.AccessConfigForFlexCDN{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pFlexCDN.NewDeployer(&pFlexCDN.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiRole: access.ApiRole, + AccessKeyId: access.AccessKeyId, + AccessKey: access.AccessKey, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pFlexCDN.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeGcoreCDN: { access := domain.AccessConfigForGcore{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 0321cb41..30e52412 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -140,6 +140,14 @@ type AccessConfigForEmail struct { DefaultReceiverAddress string `json:"defaultReceiverAddress,omitempty"` } +type AccessConfigForFlexCDN struct { + ApiUrl string `json:"apiUrl"` + ApiRole string `json:"apiRole"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForGcore struct { ApiToken string `json:"apiToken"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index d4d9af2e..45c72c05 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -36,8 +36,8 @@ const ( AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeEmail = AccessProviderType("email") - AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) - AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") // FlexCDN(预留) + AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) + AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGcore = AccessProviderType("gcore") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") @@ -196,7 +196,7 @@ const ( DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly) DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications") - DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) // FlexCDN(预留) + DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn") DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge) DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn") diff --git a/internal/pkg/core/deployer/providers/flexcdn/consts.go b/internal/pkg/core/deployer/providers/flexcdn/consts.go new file mode 100644 index 00000000..be55a475 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/consts.go @@ -0,0 +1,8 @@ +package flexcdn + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go new file mode 100644 index 00000000..3d0f05e9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go @@ -0,0 +1,144 @@ +package flexcdn + +import ( + "context" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + flexcdnsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/flexcdn" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" +) + +type DeployerConfig struct { + // FlexCDN URL。 + ApiUrl string `json:"apiUrl"` + // FlexCDN 用户角色。 + ApiRole string `json:"apiRole"` + // FlexCDN AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // FlexCDN AccessKey。 + AccessKey string `json:"accessKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *flexcdnsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + // 修改证书 + // REF: https://flexcdn.cloud/dev/api/service/SSLCertService?role=user#updateSSLCert + updateSSLCertReq := &flexcdnsdk.UpdateSSLCertRequest{ + SSLCertId: d.config.CertificateId, + IsOn: true, + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + ServerName: certX509.Subject.CommonName, + IsCA: false, + CertData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + KeyData: base64.StdEncoding.EncodeToString([]byte(privkeyPEM)), + TimeBeginAt: certX509.NotBefore.Unix(), + TimeEndAt: certX509.NotAfter.Unix(), + DNSNames: certX509.DNSNames, + CommonNames: []string{certX509.Subject.CommonName}, + } + updateSSLCertResp, err := d.sdkClient.UpdateSSLCert(updateSSLCertReq) + d.logger.Debug("sdk request 'flexcdn.UpdateSSLCert'", slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'flexcdn.UpdateSSLCert': %w", err) + } + + return nil +} + +func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*flexcdnsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid flexcdn api url") + } + + if apiRole != "user" && apiRole != "admin" { + return nil, errors.New("invalid flexcdn api role") + } + + if accessKeyId == "" { + return nil, errors.New("invalid flexcdn access key id") + } + + if accessKey == "" { + return nil, errors.New("invalid flexcdn access key") + } + + client := flexcdnsdk.NewClient(apiUrl, apiRole, accessKeyId, accessKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go new file mode 100644 index 00000000..4a693dc9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go @@ -0,0 +1,83 @@ +package flexcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessKeyId string + fAccessKey string + fCertificateId int +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_FLEXCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./flexcdn_test.go -args \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_APIURL="http://127.0.0.1:7788" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_FLEXCDN_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiRole: "user", + AccessKeyId: fAccessKeyId, + AccessKey: fAccessKey, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: int64(fCertificateId), + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/goedge/goedge_test.go b/internal/pkg/core/deployer/providers/goedge/goedge_test.go index c8c32b37..928fb420 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge_test.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge_test.go @@ -58,6 +58,7 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiRole: "user", AccessKeyId: fAccessKeyId, AccessKey: fAccessKey, AllowInsecureConnections: true, diff --git a/internal/pkg/sdk3rd/flexcdn/api.go b/internal/pkg/sdk3rd/flexcdn/api.go new file mode 100644 index 00000000..07fb2b34 --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/api.go @@ -0,0 +1,46 @@ +package flexcdn + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +func (c *Client) getAccessToken() error { + req := &getAPIAccessTokenRequest{ + Type: c.apiRole, + AccessKeyId: c.accessKeyId, + AccessKey: c.accessKey, + } + res, err := c.sendRequest(http.MethodPost, "/APIAccessTokenService/getAPIAccessToken", req) + if err != nil { + return err + } + + resp := &getAPIAccessTokenResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("flexcdn get access token failed: code: %d, message: %s", resp.GetCode(), resp.GetMessage()) + } + + c.accessTokenMtx.Lock() + c.accessToken = resp.Data.Token + c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) + c.accessTokenMtx.Unlock() + + return nil +} + +func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { + if c.accessToken == "" || c.accessTokenExp.Before(time.Now()) { + if err := c.getAccessToken(); err != nil { + return nil, err + } + } + + resp := &UpdateSSLCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/SSLCertService/updateSSLCert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/flexcdn/client.go b/internal/pkg/sdk3rd/flexcdn/client.go new file mode 100644 index 00000000..6b5626ee --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/client.go @@ -0,0 +1,103 @@ +package flexcdn + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + apiRole string + accessKeyId string + accessKey string + + accessToken string + accessTokenExp time.Time + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + apiRole: apiRole, + accessKeyId: accessKeyId, + accessKey: accessKey, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.accessKeyId, c.accessKey) + req.Method = method + req.URL = c.apiHost + path + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req. + SetHeader("X-Cloud-Access-Token", c.accessToken). + SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("X-Cloud-Access-Token", c.accessToken). + SetBody(params) + } + + resp, err := req.Send() + if err != nil { + return resp, fmt.Errorf("flexcdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("flexcdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("flexcdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/flexcdn/models.go b/internal/pkg/sdk3rd/flexcdn/models.go new file mode 100644 index 00000000..c976eccc --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/models.go @@ -0,0 +1,52 @@ +package flexcdn + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type getAPIAccessTokenRequest struct { + Type string `json:"type"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` +} + +type getAPIAccessTokenResponse struct { + baseResponse + Data *struct { + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + } `json:"data,omitempty"` +} + +type UpdateSSLCertRequest struct { + SSLCertId int64 `json:"sslCertId"` + IsOn bool `json:"isOn"` + Name string `json:"name"` + Description string `json:"description"` + ServerName string `json:"serverName"` + IsCA bool `json:"isCA"` + CertData string `json:"certData"` + KeyData string `json:"keyData"` + TimeBeginAt int64 `json:"timeBeginAt"` + TimeEndAt int64 `json:"timeEndAt"` + DNSNames []string `json:"dnsNames"` + CommonNames []string `json:"commonNames"` +} + +type UpdateSSLCertResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/goedge/client.go b/internal/pkg/sdk3rd/goedge/client.go index e0a2ce49..39ad8900 100644 --- a/internal/pkg/sdk3rd/goedge/client.go +++ b/internal/pkg/sdk3rd/goedge/client.go @@ -65,8 +65,8 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } req = req. - SetQueryParams(qs). - SetHeader("X-Edge-Access-Token", c.accessToken) + SetHeader("X-Edge-Access-Token", c.accessToken). + SetQueryParams(qs) } else { req = req. SetHeader("Content-Type", "application/json"). diff --git a/internal/pkg/sdk3rd/upyun/console/client.go b/internal/pkg/sdk3rd/upyun/console/client.go index 7a7ea7de..6b560adc 100644 --- a/internal/pkg/sdk3rd/upyun/console/client.go +++ b/internal/pkg/sdk3rd/upyun/console/client.go @@ -52,8 +52,8 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } req = req. - SetQueryParams(qs). - SetHeader("Cookie", c.loginCookie) + SetHeader("Cookie", c.loginCookie). + SetQueryParams(qs) } else { req = req. SetHeader("Content-Type", "application/json"). diff --git a/ui/public/imgs/providers/flexcdn.png b/ui/public/imgs/providers/flexcdn.png new file mode 100644 index 0000000000000000000000000000000000000000..00805598cec426ed3c2d1809aeea328c7aea50fb GIT binary patch literal 13243 zcmcI~WmH_vwjk~r0tA9I76|Tc!Gi|~5L|xVw|!?jB?g z-*@kQ@2;6UYu=CPwR-jGs%^EccAeT0>ZTm!Hg|Gh zH+_U*hd4L`*a!%s5)fxob302g$js8p#!-y+u&Ip}WMd<HYFt6?P{s% zt*T}2ZD%fIK`S8+5`_o@1RN~ErXYxey`!5jM2z+?y28No<1hy;=r0hkofz%kLg}ig zgQT5YEkQ!;Tx{l?oLnFQA$C(i3tj;}9$q0<5H}|`4+kd?2R9EJ7mqL}pD-^U=pP?i zfT63!dtnV3xqq+&{)y3AgTc`ty$99%*|LL8jj9NgS&00f(xmm}B| z!sh5k_fHBkmTu;*HqKxhCr8jDMN=~;cd!^OAnf0(aB%(?Sx2{jXbR972gKBwgNvQ> zaaI2WR8jfAgE~0;3)&5=VfkP3{vQduX?ZzYa%fn(Ik~%<1Gall_sGgwSlZRn6zt@x z<>X}lPerO*JAs|tteu=e+`R1kASN9fM++xUx0in*sHh0Pc60-qI+|O)mJy=`NU+=3 zSP1h9$_U9y3G(o8^K)`>$#DvD^GnGJaLMvWaY+mDOY!{^SH{WQ-NDik{7+nq|H9?{ zkGPNS;NT2!ma%lT@vyXzb9Hh6{k3LcoBu2e*MFq<4_u4?EDQI4#N_~l;dr#{f3x&I zhXD6^eEgT|0)PJH{FaV@$GZZq{W(eQ6aj&g<+aQ!Ey&zn7Wxd8?Bqu^gl(n|OPJ)5 zXOdfpG6XW|PYKnxF%(l)Vz(+*Vv&%Zyh4>FB#2-78WE@dL_vxvSLaQ74FXM~A zf$`rS{%z*pK>juJuZMpkKMwpw<$s&`7yW;m`F|w)KSB0QBum$b7x%k4JF6HXvadAh zN86>gzi3~=xG;luhb)rr8N<59ED?StL=Z93g3_SAP___UoLXyWXviRi{glZnHVnc( zBMp;7ZQ`HFhb?{0K$e`}PDq$bsz;iAIkN;U6!AEJLpE)w|MDs~=L_u&c z2&z$$^uX}2hE6Ja?`gsgusr;rZD>Bh2lxmW3e_kw`Wi(j^Ml94OE@0#P?q3m)_X6} z4nzckIA2tYG!s&NQTmF{=xmNIcWg70u=~Lg1Oya%YQ%VJYdf3cbmPM$6X~eQ-WHN! zH3R@h2DKmu?oi zK!8#pj(9B#Mdm5}c&p@y>!A5W5@lq86=MX?Zy%3f_aHG&4{igq;6%q)_$ ztRNyFzLLyDS>ujcT30c*ujC#zZKfbwcRS(lzMURCb zpcJ^KlVI;=ux*=1PM^=Vqfi|q{KP6CWr<5`r@lnjuUc<=2@I72T=7sXqK~>c8<)F( z(osDUETjSXgi5Fu{ZS##O2FaJW_*%F$cu@9G^UvjMyq;i=#+kjdjW6%;OM^HSM93` zex}yWieSBtwU&9*J#k#^U8~%}FZI?LzZ*+aC(v0Xlqv+2cjRgBQ%v2>)6Fi=1_TO~bwx zjxH%#MG}|LPK};)r?2;E4_i}iUJsu{DsR7hK6>N~p^Px@#bWyjR>-|i)Fz_v7vse| zQbCys5*E@IRJb0o@G^GM6-08?1Z~iB5+a@1Omh1+GlsF{l~*mlsgC5k3sb-}@|^$yTXC9oRmqr$rnrGeA0#L7-l&@cVk98E*#C zGJOSPR>whb=!eyDSS@C)BQyw4YKs2FWjM9vUxGg&h$9N2+*7}0hYjugdYGP?Vutb8 zZk;d2nsU9R2c7W2=%^k&gdcH~Z|RgGQi5UTUhKB1nuO)^VDQUd`6o32!KPyj2|k?( ze*MP{pO z7$Gb|Spb^N+_2CJ|85brk~M8|1r{q07PA3FL_tAxc^YSoA~N!u@B~?Cx`F&j+$5_J z@s##-(KA&;?w9h-6wf%SA^lMl$AN($P|Fhnm9tGjQ8Q=)r+a8QgOjaGiIToiKUYm* zqm*xF4x-w(~>L*rAMXCN`Z-^Cs6gklJ z8oK+WM40M0D@a}x1j6XiLR}j%=@t>)2si0wSipgFdZPUePfbLSMU3MH`BX*a82_gF z7vVA)U_zweKwsydr84r^iMOenQ5?4}Wc-8xwTG$4$uTKJLeh5&eJ?X!!KZBw031KS zK~M@Tkz7-+Gj%8&#Wz{pgXMkESLgp4KSo+fGEp3|o#$`HU;QU=5E8ALj*qgcE$lAS8BDURzmC)P%jd38SXKj~U|9%<2=BOYUwUb&w})Fr)5 zcn*jZ__)9{%$^I_kSAoJ7hcTkCE$88tn?ZS>>* z+n#_NJ-jHLksRe^6jUuS<2z{HfSwh!3@AFubghUneRwNy?Rc=0W9Sch1kk;&DOW?%kN0pZqPFt zp0`z(OSF;fWlKrABmQ1m$svqO#_I8fQ8~}FkGQ|41fq;0fe&!OoJ6q|che0!xKo36 z#JX)Lcfq&ISrVdt8W7tP0+;HfL$Pv(qreqA)O19uW6A+f?Kb)5+Pqqv+6=hX)VPp@ z8gbNvv}70;KoTnEyeg|>&fqAWtfGcU9cE-Hu0 zIrr0@bRm`2_9&{Mw51qc2%Jao{>=>6Q1fZu>!J zCPKn!9l;a7_$8;~Ur7#Sp#iH8U`K!Q7a-?s2dH06n41auwrYI;g^!gX)f(Aq<||K zJdHj40PI0vwmTYm!K5Z`a}@6gU!^tkDy7Q2$-cE|Ig5oCUkW^;`=}?Th{J2KJ_nRJI{o>;GEzRZaWMC}qY%Kx($VPhOIw zIu);aPg<5?kjw;O5Ohg#`~=SuH%b2C`Qoiel-{TEmWpfRh-`wrY7#p91bq5qUl6ry zguSSSS5*#<3PTaau@y>25vlvzk;z>a$T0P~AY+{$Ds5u7c|e$sM&IBmxuDn9fOWB1 zLq*bj6iLg?A@ltQkN7J2TN)CcXBQ+{#5pAPp5305Kt|IBU6)CEfN$~yzrITNfTv0P z8a$!3BQmw(HRe4~Tqb*G5n>{b(ll$PkkhD5g)TI4Xy1M5S9itPZ?_~XDSkAY`fG&d zBd>+V$kN^s-cFg_fP^W#gV@}Yxbc;`U_+|CUM%>kj;i|@ZvQ;R@*lfL3*3P$q|Z4(Ce0M?lq183 ztmi@UQ_dyQyod^a>cNwvyHn@OgD)RK$D5X)+Lvc;EN*Z0Q~x5?b@`z@Z(g_}YF=xU z>=r0%{5-HXJ|QoR=(NX#RQu?@Tko<0*|R+omfW))CL>8(CzRCmzHq-XC5iOqnKj)C zqHpS8R-af&-etg)S;1;m8r_T(AUV@s509$S`7DoJ6j|AV-C(2q18U>5r(1=ijd<%j z%Ii+VQ>)xz+*Xp3Cl<|cR#QtJ<4V@Sh@{9@MlYU^!@rnl77-U~bc*Ymik&rIQuG-< zL+mB0U=41#mMz^=FU%vl%#C_wVpZfCCaAJym`fS8Nzs=@gxH(V1jGbiwZ~Do0<+UP z*|u4kJT+s9NRpp4-2DwfSEa+lE5f*kyryjqk;D?V+%z8BFtV&0G8TfF`C*^{n)SrHzu7Jc_qsUyb8g@V#vG05x8D7dxHI{*2Q3GxK=Og)E~Qp zQc3bIg)r_SfgCqf4k{15#TKdG2hT5xqrL-x^q>PV7@$+7;r9fP1@cs z{WK@}Td8j#$eGB{?)%wGac*9k-U!Xg*=&4|I{5Qo6%+g-S9cN@2i)`PPuCXqzU)QR z#E93Nb!a~GeQKP1-%{ya!^c?}i&;Yaz80L+@IlB7?IM}12EkcD~7N$FRm`5_{a>mm6##qo6U5KKl{scT!muQjCQ?PChf)eV+uZ-n;KR ziElr#;CMG+X~f4OI4RF{y+N01wzROV<>=^D5Q}+{Vsv{aZ+9!KOcuJgR2>$eGn&QG zW6yA+a*AD19+=~PWd@Pe9{Ii@TMIn_G8RLT{I~7gG*w85kGc+OdIo9P)ia;Oi4_0T z*M6HEwU*7b6+4k0exaIv7lRI}a+2aPcq7!{EsBcNWj;A44_58>Uy1lnXb-DM)`y>d2qZl-2U!YaJQX(*N}OsdWL%?&fJ)9z8avL;S^+qdiNK?Cx=`1GCHEX{_c6!Gq7Da-a0Q7uw|DRfv79lhrTNV5bqaaAUrC zqE=q%*d&tVQ)fxRM1;7VzB$MU(|VAS`C^!9=<2meuib{rO2zF%vs}tz`#qzbz-#f# zfUpK168FC9?&wDUcuko!p-n%Q$h~zil53{hwM6yJ7|q^}?gbn}!L4Dk*kI_@T$n~V zm9EA+EcH~4yqA-9U(ByPbq|qzF3=3;&lTY%8QGU0(4g#6u9HY&6Tiy5;t;ln6i5G6 zE4*V(OI`3hpIO&$0{()8l1{9i=?|3(C*0BR4nm*0)9pK_r@Cd@CPWWwyzRS1deJ90 zZP1phdaluV_|-KhfiTX7XQmk0FUhNBlBwOrGhdvAe?A#=>ecUeOP(Zg4tAx>b1~rRsGMuwR{df7IK+{W5_Qt;@dK$BwY@>@xqDt%2%ElZLt|gl7fW(v zbj3~gQ*18{er)tVp{G3TC2vGK#Z^~m?yYfqGdf77YTld;bK$Ksvm)Vz#?dkAHT$iH zio@EB&F{eOo}S$&!*+`Dl9G*KIwtT*YrURNgTa3AYqBC;)to0>=0lmT#gu?SyA-8wZ5FSG3r6N_Vw+$ zCR?ud9!B-VuC3RcZKQA^K^hYWx16Ykxvu|iq#eX2gSK90V`F6T;gwg$G-`prnO|G! zi4{W#<5241#RlakQaf9b$7IB!n+Q?N7T~q~eU?Z7^?*!z;c68IYv@-itZ=iQ{kU5U1zaG}ACU91O?*0WqA-NH-IMR_rSCR=fM>oi z+M3$PNnbK0h#Puiz#1#vh6sR#w{~)HK2!^?!)EA$ZWNiUTv#7u>=cIZQ3?#G_;N&c z6*>7N2C?~aB_puk9%a&%-P7C7vEQegUk~decO-*A>qj1ySTCP5RKAPY2qnKNf9qy> zSta#tr%QzX&IM*9E%}8-PB1A-nnr7IM_c6{SGHz}Bj|?NBEssnA*D2(WduW^3&)+= zlRyQVIL|Hi`&h}r^SvC*iA$7CtJ@qtRtpg%4RPRrohjXLqL3545gIw7^35O6=!r>y z@|RZOfMZdsiP^1+`H3Kcxa{M?>w<>2n?|7X{7`()`cu7BE=$Xl;)fVe^ z8NWL%O2HAY-$c_ZYT z+GnK+!G?*FS#4DIQCQm4Wg)t{V)FAUeZNILd)LLE+M`AtS0ePu#JDLA8ibC2zd4SC zEfqHmDNbELps18Nxyq3_93`A)razGP_r2*;H3A#ft&N<2Wkz;NQ%i_2f~XR-UttSN ztRB^RFrgH9e)70cNiMR+7uhGctBK(|rqHlG+;$ZP>%x)`D?N6hZ zee9{yXCA+Qv$28euB z{lMU(G!6J;FkQCx@6!u`oG*D|uYRwzIC0!}eD5WT4XnYb_!(I;S~H@O_-tndE64NW zSE5FPx?I)rUqVlcE5Upy1@_IY?aoXe4xkVycJQFq=qcF!ZXR>eSYKzUuDrYo0>Q&j z*k&r|8XVzNDmqdh4bLWnb2C zeKe{ek2Lf&#ILa3>cEUE&Kj#nXGb1zVe~22ST~f;9DSfrjx7vjA4%)(A{IZCRI++* zr5)v^(!2?M14A31Bp`4)04kHW=_G}Lj`|L7exD90+tZ4%&wK`m=!1V0f2n6tfuf9f zBph_8vCgiUbMuH1zgX1@^XKPlV%N?2u+yd~doG?X&>=MGbO*zzN zrHX`#s7amYOm9m&C{jD}(1J7tYN35_y2?+%D`m>#!SDf3iLxWFM!%Y3c!o~#QIUxs z3uMSKSxydK0KC?=tk-NnwwasC$K0PGuZ=uV`i7gh4UJSn7B+53VxNYtYoO`tMxprh zn+mZ>B#56)O;x45ZE|2xVjEw&A)fS6UZkGN464zQeBof8v{?Kj(J0faCdpnW1d{n# z_xV@~*Z4*i0om}fL^*}Ysx=Jg}RSucZD0Z9^!4ilD9Vwfiyc!uB#rhH@ZL) z-fmhBS0crHc+J&byueI}rN(Ufu0j-7X_T@tRHGx?xI_GQI%F|hSAVP7=?*J9KZ@FC z?N@<6Y>=~pIDwebG3IH`Rt z*3!e==Vc#M-aI9b@2^Gu<4p9KLQk(EcOK-$cL?LOP-P6(CXT1w{hb&X)74Kzs6$ni z=*XT2{ItbNoZ&_Mjnf%xs=Dq?4cS>R_YPwdyW;G7d7nX^?c)tS zWSpV0(gUlP+4jdtMlb{hUU7{TFYsk8{~5=#yr|w%!6)<*ohD>oi-mt@4f^}_K%oTbVHp*KG=o&+Q$%P@2OCj|7%hi zN!MRqr{%B$(JHp8CW> zU8OBqXUBg3fWk5A+zlovHN$lx4c?fF7<6WSqE;wJqCMcu*~-16SoR8gvV8t2IWp{| zPSiL7vSrugl(+9H6}s=-2FIoV-rIjJ3&imtr&!Xb6-Yda;dpBN+d&$AssgYI@ZObk z)MK`(-D5<4ig^#~w?e9jQP57a*jSR{d8JWuWtr^Z3V$x=Mc>Y4F?n{G0jpx~jeYQb zl?`;9*>}lN)(62hF73iJ5BD=Cech(-v@3p##l=732jN)rE7aPHlO`ziGdO5(zvy$#3ni8uO$%sgh0SAY_AObm$*ErWOf^#blO2)S872@$^z38 zs$97cOcqMxiF<(^UO!=TR66Kuv>o=7j4!JDI-+j$6>(j+yYIv*`WTv{3$v$KdhHYN z*XtpZrs*rZBE7lk{3QC_0$^i`{c*Y?#|;SedwrsYS!FkdU2?&?)%2_=)c5&?QhhXU zyiOXuodHeZ>Q**u${aNG>(L}fS}gMHz?p?WOsQADqKC|iL(My=CyPD4A>DG^qe%Ri z;q|kooZB}mkprh+>CpR>o)E;|5jhnh!j?+wRW8=@vjL!$ct*qQ1D5fcuT^g<*X!K} z4|8?RUKW`oL3;3u`$`77^_yvNFara%`hgcG=wmfsUB1W;pD@_^qKDqc#dgCn>k>p; zlkD>QGU*z7A%cd9>l*rX(6!3FcoRiDEx)b7mZhpjrXaQwF1{!oPj%mt?$R3y#vBvX z_t+(ax7DejzKv>w^Mli?B;lR|DK2(rhSV9XT$Ql*Z4Jq6Y z4~^p3e(EZpX+K)@JL|)|T})of_os81vQ46jp0bi4?#5@&EO`P8_xYQ2aY9<(A|0L> zM*Wu1kc&@QS9o`B8BJN%XgxkcBq&osfeP~(>65US&I=dKAIKYfSO1G03&)+DelJj_;HjNh~T z#2sK%+NiNF{oz^i;|)?fiFV5;kGW)nR+vK}vJGY9J!s|=W=o1|xbM>KP;H-_6;BHK zuKuwb@!@Vi+zBHsBygf1D5v?4DP(AN-Ftk#PEre9<|_<5Mg4WHJg3o063N9U46H}HM9X_3;W@1!A5Bx2Flw&XIzq3hFpsSp`r zagGQDT1hSDjiq?ZduVFO>NZNKv_~tvq(aC=H+rR#Z|w1kcAaO&X2hRwmH*8}17QSK z*o%%k9{cfpJNs&fcvRrZ_1ILt=#`J+M~7Uu4{Z$06hzo>^u;1v6AD=@)4 zV3wJ)7%kBj~ z_`i-aI&%LpKOK(*Fe(0sor!iV7cu3@7nLjif}Wi!pi-2BuJp;?Z}RzlmL5qF6cmX# z-Xm9WeYQ&PzCTxcKtsoTwXWaS@aAe_9lJ0SHH)c~r_s9ivUhfnT5*gudWg7mBjtc3 z5k1U7b-i#!{aezA5v;cQEKA3Ut#h>%=TH3H)meJ>T{(Q-Erl!d9eZWyTefOl96Q4c z?O@UTUE!hA`zswNGia?ton^UL zW!lcO4&r1y?dblkRjXr8rKFTvAICUbXWWN!Ny)KQ)gsqe8*885 zSn0YFfAeEoyMwJK@SMo~fyR+RFsC9{{lPeo=4@^kxBQ| zEnbE0Kmhj#cC_(4HO(RXo3ril#AS;@6Hj#g^n+Q3Hf)d2a2W#?lOU52Ho5Au1V*-v zllla9ZEB&xUf=b?YfCY5#Or5=`U6IgD)_>h%B$a!;H5X77Yd=3I_&W&+qNTy7=@`X z=&XbBa|Z%XRIa@{=Hxatbm&1;r09QWR}f1j(_!lAza-jod16d)OQyMDs+C)d#sZDL zP!2;}ZzL;_4YwV%oAn^Nj?j;ASNYcl+LVgEw(CF!N08^64fuE}+Bj{IP#Rk)uh;nP z*g|2X=#jf%IdAhkzq;sqW|`wiluqe1W&vr@FLlt+B=))5*+SIwM7R84lpWA@?#HP& zdEd(FpMJJ~sWqq#JFpB#L>e;y;~U$}isP0oCy;blY-O$*C73xl|j=$dDI<<1$C82-R^m1@_nAO{@C~)ppp9KE^ zu`kT|p!e9XUnH|p`*~wwPuN z98)F6^N!1_@fa%RL~}hgz$+c8k*CCQ_8W#M^ki$If{m$hu9B6-2i5h99@5YzJ;A9Y zj@1CRtZKZnd+@#Mi*ZgQ4b;q_f(}?(c$kc8uJ%y%vy|`ALRCo_1`)~p%3E34HO~f9 z1Z{y{C`%Rs%2sicQGFr4#8~~`>q*;c^LBMLkmjvRp*&sp0t!bVST?vWv^5a!P zGHkLtmsC{KmS1W|0!?9hjYw${Gjy-ZsMaWg*KqKf+3 zA=eSQK~SWiEaoTphShH`n%c(KG%brwZl(eivX>IrN9iW<=*i2njWKks=mHGqAmgIY|fCELOhka7o{Lf1La-vOU+X z!RLgC22@P0uX|3zSo1EkZ_4y4nWW-(d*W>2Ot~Jz=%prW$sL<81`<8yONU)^_wFiu zS6%`)DvR-GEs1cEp{ieF0cpiF%;INYD}2Vn`5+iNBX{)p=RAz=n~MwNkDo$ zv%X4Uwe@$Yz$@~S=zKylUeCO6LxI`0sTM-nJB%s>R39DO8$^w3J0>00OZd8-E4Vd= zA%jUrKDG>v0&UMhpbFQ-yP`zp+tfCkGf?i}f~SkyoA*z>NHCQYO)G1uIXEWv&0%ehRn`ZjmIV*XT3&q2jFa7Q{>Bm7jKf~P=ywjd>u=@PVAyyM&q+c zihNeGUnoSGG1&=Z~4{b9O#H4O7NbD76_Y_a^jx?_fPA!B2Z^c9M; zDeKT+UgvXEFCSz92vJ`mg^Eo+;aru9)z>zwU z1Bn$bSO`W1fHvNZT&3g~3VvdO{YdjOwcB}%MW{N$8^jzMW#`?}Y##?U^KVbrV zK+(|#LCR8f`TTkM7ta{R8z)xCy7zg}p-8VJ=jcI7D9;^^71_Yl*A2Hrd1`|pyy!sr zsqh?ChUxh-15Uo9i#`px@Y8_k4Fq6GHcf%y)Ot8Yh}2fTG~}Cl1O14PB{4>i{0Qt& zhfMY1_koFy?%6UV@h@Osdt_Ggb=^6L%eYRCB}2bJmmVx!1JOEtejPwJOKZ~sSxK;=3+GXpfJNs3|UL{5SQH3yxb2e z9Gof$kE`@!B0d;Re$Vde&dgA%p-Y%$#iX*=uZ#;M2B28wL78Wq@z;K+O{Ta_EU?jn z_J=IKyA?oqY%BioW^R@Zi|ftVEOQ=fn;jS0qzXyJ0`o^wmv4_P$@J!l!v>plc4*Ia zU1u+K2lVqYGzIhq3VN84AD2(wBMiGi*B6=Df!&~H2+e#Nzw+rzxmSd8!4B%`DbEAp z`^A^m+J9*r-!@a0?Y*c0{Y+8Fpka6~WALB_mwmXSeDsfczOT|pn;|UGzkF$ z!Z#9)IiTbHzc;S`)r|gc?dgyG@c*A$+W#kH|4pXt(+4CR1X!A6edqjOKhSQE@LE(({ className, return ; case ACCESS_PROVIDERS.DYNV6: return ; + case ACCESS_PROVIDERS.EDGIO: + return ; + case ACCESS_PROVIDERS.EMAIL: + return ; + case ACCESS_PROVIDERS.FLEXCDN: + return ; case ACCESS_PROVIDERS.GCORE: return ; case ACCESS_PROVIDERS.GNAME: @@ -228,10 +235,6 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES: return ; - case ACCESS_PROVIDERS.EDGIO: - return ; - case ACCESS_PROVIDERS.EMAIL: - return ; case ACCESS_PROVIDERS.HUAWEICLOUD: return ; case ACCESS_PROVIDERS.JDCLOUD: diff --git a/ui/src/components/access/AccessFormFlexCDNConfig.tsx b/ui/src/components/access/AccessFormFlexCDNConfig.tsx new file mode 100644 index 00000000..6ca020bf --- /dev/null +++ b/ui/src/components/access/AccessFormFlexCDNConfig.tsx @@ -0,0 +1,90 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Radio, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForFlexCDN } from "@/domain/access"; + +type AccessFormFlexCDNConfigFieldValues = Nullish; + +export type AccessFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormFlexCDNConfigFieldValues; + onValuesChange?: (values: AccessFormFlexCDNConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormFlexCDNConfigFieldValues => { + return { + apiUrl: "http://:8000/", + apiRole: "user", + accessKeyId: "", + accessKey: "", + }; +}; + +const AccessFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + role: z.union([z.literal("user"), z.literal("admin")], { + message: t("access.form.flexcdn_api_role.placeholder"), + }), + accessKeyId: z.string().nonempty(t("access.form.flexcdn_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.flexcdn_access_key.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + ({ label: t(`access.form.flexcdn_api_role.option.${s}.label`), value: s }))} /> + + + } + > + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormFlexCDNConfig; diff --git a/ui/src/components/access/AccessFormGoEdgeConfig.tsx b/ui/src/components/access/AccessFormGoEdgeConfig.tsx index ced9b09a..9c03f2be 100644 --- a/ui/src/components/access/AccessFormGoEdgeConfig.tsx +++ b/ui/src/components/access/AccessFormGoEdgeConfig.tsx @@ -32,16 +32,8 @@ const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialVal role: z.union([z.literal("user"), z.literal("admin")], { message: t("access.form.goedge_api_role.placeholder"), }), - accessKeyId: z - .string() - .min(1, t("access.form.goedge_access_key_id.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), - accessKey: z - .string() - .min(1, t("access.form.goedge_access_key.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + accessKeyId: z.string().nonempty(t("access.form.goedge_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.goedge_access_key.placeholder")).trim(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index b77ff9cf..981d7183 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -46,6 +46,7 @@ import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlu import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig"; +import DeployNodeConfigFormFlexCDNConfig from "./DeployNodeConfigFormFlexCDNConfig"; import DeployNodeConfigFormGcoreCDNConfig from "./DeployNodeConfigFormGcoreCDNConfig"; import DeployNodeConfigFormGoEdgeConfig from "./DeployNodeConfigFormGoEdgeConfig"; import DeployNodeConfigFormHuaweiCloudCDNConfig from "./DeployNodeConfigFormHuaweiCloudCDNConfig"; @@ -240,6 +241,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS: return ; + case DEPLOYMENT_PROVIDERS.FLEXCDN: + return ; case DEPLOYMENT_PROVIDERS.GCORE_CDN: return ; case DEPLOYMENT_PROVIDERS.GOEDGE: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx index 45662e75..4d6ae25c 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx @@ -79,13 +79,23 @@ const DeployNodeConfigFormCdnflyConfig = ({ form: formInst, formName, disabled, - + } + > - + } + > diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx new file mode 100644 index 00000000..e24652be --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx @@ -0,0 +1,84 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormFlexCDNConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; +}>; + +export type DeployNodeConfigFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormFlexCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormFlexCDNConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormFlexCDNConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.flexcdn_resource_type.placeholder"), + }), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.flexcdn_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormFlexCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx index 89dffb5f..5e0f7f85 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx @@ -68,7 +68,12 @@ const DeployNodeConfigFormGoEdgeConfig = ({ form: formInst, formName, disabled, - + } + > diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index c676b5f7..48882eb7 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -29,6 +29,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForDynv6 | AccessConfigForEdgio | AccessConfigForEmail + | AccessConfigForFlexCDN | AccessConfigForGcore | AccessConfigForGname | AccessConfigForGoDaddy @@ -194,6 +195,14 @@ export type AccessConfigForEmail = { defaultReceiverAddress?: string; }; +export type AccessConfigForFlexCDN = { + apiUrl: string; + apiRole: string; + accessKeyId: string; + accessKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForGcore = { apiToken: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 2c6f0f0f..4cad8606 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -28,6 +28,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ DYNV6: "dynv6", EDGIO: "edgio", EMAIL: "email", + FLEXCDN: "flexcdn", GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", @@ -126,6 +127,7 @@ export const accessProvidersMap: Maphttps://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "Please enter FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "For more information, see https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.flexcdn_allow_insecure_conns.switch.on": "Allow", + "access.form.flexcdn_allow_insecure_conns.switch.off": "Disallow", "access.form.gcore_api_token.label": "Gcore API token", "access.form.gcore_api_token.placeholder": "Please enter Gcore API token", "access.form.gcore_api_token.tooltip": "For more information, see https://api.gcore.com/docs/iam#section/Authentication", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index dad0ae6f..337820a3 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -63,6 +63,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "Email", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - CDN (Content Delivery Network)", "provider.gname": "GNAME", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 006979f1..a998c26c 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -347,14 +347,22 @@ "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly site ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "Please enter Cdnfly site ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly certificate ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "Please enter Cdnfly certificate ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "Doge Cloud CDN domain", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "Please enter Doge Cloud CDN domain name", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "For more information, see https://console.dogecloud.com/", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "Please enter Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "For more information, see https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "Resource type", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "Please enter FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "You can find it on FlexCDN WebUI.", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "Please enter Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "For more information, see https://cdn.gcore.com/resources/list", @@ -366,6 +374,7 @@ "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge certificate ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "Please enter GoEdge certificate ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "You can find it on GoEdge WebUI.", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "Huawei Cloud CDN region", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "Please enter Huawei Cloud CDN region (e.g. cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 1ca78d07..649d44f5 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -187,6 +187,21 @@ "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", + "access.form.flexcdn_api_url.label": "FlexCDN API URL", + "access.form.flexcdn_api_url.placeholder": "请输入 FlexCDN API URL", + "access.form.flexcdn_api_role.label": "FlexCDN 用户角色", + "access.form.flexcdn_api_role.placeholder": "请选择 FlexCDN 用户角色", + "access.form.flexcdn_api_role.option.user.label": "平台用户", + "access.form.flexcdn_api_role.option.admin.label": "系统管理员", + "access.form.flexcdn_access_key_id.label": "FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.placeholder": "请输入 FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "请输入 FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.flexcdn_allow_insecure_conns.switch.on": "允许", + "access.form.flexcdn_allow_insecure_conns.switch.off": "不允许", "access.form.gcore_api_token.label": "Gcore API Token", "access.form.gcore_api_token.placeholder": "请输入 Gcore API Token", "access.form.gcore_api_token.tooltip": "这是什么?请参阅 https://api.gcore.com/docs/iam#section/Authentication", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e7c6ea31..27abe56c 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -63,6 +63,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "邮件", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - 内容分发网络 CDN", "provider.gname": "GNAME", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index acc5657f..e462f34a 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -346,14 +346,22 @@ "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly 网站 ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "请输入 Cdnfly 网站 ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "请登录 Cdnfly 管理平台查看。", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly 证书 ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "请输入 Cdnfly 证书 ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "请登录 Cdnfly 管理平台查看。", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "请输入多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.dogecloud.com", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "请输入 Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "这是什么?请参阅 https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "证书替换方式", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "请输入 FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 管理平台查看。", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "请输入 Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", @@ -365,6 +373,7 @@ "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge 证书 ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "请输入 GoEdge 证书 ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 管理平台查看。", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "华为云 CDN 服务区域", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云 CDN 服务区域(例如:cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", From 3462e454d017b2aa4cc910d93b0fe5ba9a739ac7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 16 May 2025 23:30:03 +0800 Subject: [PATCH 05/13] feat: new deployment provider: aliyun ga --- go.mod | 1 + go.sum | 3 + internal/applicant/applicant.go | 28 +- internal/applicant/providers.go | 46 +- internal/deployer/deployer.go | 6 +- internal/deployer/providers.go | 410 +++++++++--------- internal/domain/provider.go | 2 +- internal/notify/notifier.go | 6 +- internal/notify/providers.go | 18 +- .../providers/aliyun-alb/aliyun_alb.go | 13 +- .../providers/aliyun-alb/aliyun_alb_test.go | 1 + .../providers/aliyun-clb/aliyun_clb.go | 1 - .../deployer/providers/aliyun-ga/aliyun_ga.go | 322 ++++++++++++++ .../providers/aliyun-ga/aliyun_ga_test.go | 118 +++++ .../deployer/providers/aliyun-ga/consts.go | 10 + .../providers/aliyun-nlb/aliyun_nlb.go | 3 +- .../huaweicloud-elb/huaweicloud_elb.go | 1 - .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormAliyunGAConfig.tsx | 118 +++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 13 + ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 13 + 24 files changed, 880 insertions(+), 260 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-ga/consts.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx diff --git a/go.mod b/go.mod index a4ab4379..37686d8c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/alibabacloud-go/esa-20240910/v2 v2.32.0 github.com/alibabacloud-go/fc-20230330/v4 v4.3.5 github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 + github.com/alibabacloud-go/ga-20191120/v3 v3.1.8 github.com/alibabacloud-go/live-20161101 v1.1.1 github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 github.com/alibabacloud-go/slb-20140515/v4 v4.0.10 diff --git a/go.sum b/go.sum index b9d4247d..5a64f77a 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,7 @@ github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+M github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 h1:ASXSBga98QrGMxbIThCD6jAti09gedLfvry6yJtsoBE= @@ -137,6 +138,8 @@ github.com/alibabacloud-go/fc-20230330/v4 v4.3.5 h1:nDNjVzGwkQPbQnAuxAmxvS9x8QGL github.com/alibabacloud-go/fc-20230330/v4 v4.3.5/go.mod h1:vEJimQ6E/e+m2z0/oXdeQWlFw/Pi/Ar6NKcMrSvcILE= github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 h1:A3D8Mp6qf8DfR6Dt5MpS8aDVaWfS4N85T5CvGUvgrjM= github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12/go.mod h1:F5c0E5UB3k8v6neTtw3FBcJ1YCNFzVoL1JPRHTe33u4= +github.com/alibabacloud-go/ga-20191120/v3 v3.1.8 h1:5GF0PXijDhxRQ3gTg9Ee/CVPtglkxuVdz4yIQgYLPgw= +github.com/alibabacloud-go/ga-20191120/v3 v3.1.8/go.mod h1:RVpR9VL4YECKoZCQijTYfPk8k52O61v6hSRekjxF0kw= github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8= github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4= github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko= diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index 6d17e940..e6a04bcd 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -54,20 +54,20 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err nodeConfig := config.Node.GetConfigForApply() options := &applicantProviderOptions{ - Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), - ContactEmail: nodeConfig.ContactEmail, - Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, - CAProvider: domain.CAProviderType(nodeConfig.CAProvider), - CAProviderAccessConfig: make(map[string]any), - CAProviderExtendedConfig: nodeConfig.CAProviderConfig, - KeyAlgorithm: nodeConfig.KeyAlgorithm, - Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }), - DnsPropagationWait: nodeConfig.DnsPropagationWait, - DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout, - DnsTTL: nodeConfig.DnsTTL, - DisableFollowCNAME: nodeConfig.DisableFollowCNAME, + Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), + ContactEmail: nodeConfig.ContactEmail, + Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, + CAProvider: domain.CAProviderType(nodeConfig.CAProvider), + CAProviderAccessConfig: make(map[string]any), + CAProviderServiceConfig: nodeConfig.CAProviderConfig, + KeyAlgorithm: nodeConfig.KeyAlgorithm, + Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }), + DnsPropagationWait: nodeConfig.DnsPropagationWait, + DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout, + DnsTTL: nodeConfig.DnsTTL, + DisableFollowCNAME: nodeConfig.DisableFollowCNAME, } accessRepo := repository.NewAccessRepository() diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index bf27e7e3..de47ae18 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -42,23 +42,23 @@ import ( ) type applicantProviderOptions struct { - Domains []string - ContactEmail string - Provider domain.ACMEDns01ProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any - CAProvider domain.CAProviderType - CAProviderAccessId string - CAProviderAccessConfig map[string]any - CAProviderExtendedConfig map[string]any - KeyAlgorithm string - Nameservers []string - DnsPropagationWait int32 - DnsPropagationTimeout int32 - DnsTTL int32 - DisableFollowCNAME bool - ReplacedARIAcct string - ReplacedARICert string + Domains []string + ContactEmail string + Provider domain.ACMEDns01ProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any + CAProvider domain.CAProviderType + CAProviderAccessId string + CAProviderAccessConfig map[string]any + CAProviderServiceConfig map[string]any + KeyAlgorithm string + Nameservers []string + DnsPropagationWait int32 + DnsPropagationTimeout int32 + DnsTTL int32 + DisableFollowCNAME bool + ReplacedARIAcct string + ReplacedARICert string } func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { @@ -105,7 +105,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pAliyunESA.NewChallengeProvider(&pAliyunESA.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -126,8 +126,8 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pAWSRoute53.NewChallengeProvider(&pAWSRoute53.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - HostedZoneId: maputil.GetString(options.ProviderExtendedConfig, "hostedZoneId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + HostedZoneId: maputil.GetString(options.ProviderServiceConfig, "hostedZoneId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -334,7 +334,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -351,7 +351,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), + RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) @@ -521,7 +521,7 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), + ZoneId: maputil.GetString(options.ProviderServiceConfig, "zoneId"), DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index bdacf08e..e4a28746 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error nodeConfig := config.Node.GetConfigForDeploy() options := &deployerProviderOptions{ - Provider: domain.DeploymentProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, + Provider: domain.DeploymentProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index bf7483d9..8870fe97 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -19,6 +19,7 @@ import ( pAliyunDDoS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ddos" pAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa" pAliyunFC "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-fc" + pAliyunGA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ga" pAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live" pAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" @@ -92,9 +93,9 @@ import ( ) type deployerProviderOptions struct { - Provider domain.DeploymentProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any + Provider domain.DeploymentProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any } func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer, error) { @@ -117,7 +118,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err @@ -127,9 +128,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), - WebsiteId: maputil.GetInt64(options.ProviderExtendedConfig, "websiteId"), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), + WebsiteId: maputil.GetInt64(options.ProviderServiceConfig, "websiteId"), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -138,7 +139,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeAliyunALB, domain.DeploymentProviderTypeAliyunAPIGW, domain.DeploymentProviderTypeAliyunCAS, domain.DeploymentProviderTypeAliyunCASDeploy, domain.DeploymentProviderTypeAliyunCDN, domain.DeploymentProviderTypeAliyunCLB, domain.DeploymentProviderTypeAliyunDCDN, domain.DeploymentProviderTypeAliyunDDoS, domain.DeploymentProviderTypeAliyunESA, domain.DeploymentProviderTypeAliyunFC, domain.DeploymentProviderTypeAliyunLive, domain.DeploymentProviderTypeAliyunNLB, domain.DeploymentProviderTypeAliyunOSS, domain.DeploymentProviderTypeAliyunVOD, domain.DeploymentProviderTypeAliyunWAF: + case domain.DeploymentProviderTypeAliyunALB, domain.DeploymentProviderTypeAliyunAPIGW, domain.DeploymentProviderTypeAliyunCAS, domain.DeploymentProviderTypeAliyunCASDeploy, domain.DeploymentProviderTypeAliyunCDN, domain.DeploymentProviderTypeAliyunCLB, domain.DeploymentProviderTypeAliyunDCDN, domain.DeploymentProviderTypeAliyunDDoS, domain.DeploymentProviderTypeAliyunESA, domain.DeploymentProviderTypeAliyunFC, domain.DeploymentProviderTypeAliyunGA, domain.DeploymentProviderTypeAliyunLive, domain.DeploymentProviderTypeAliyunNLB, domain.DeploymentProviderTypeAliyunOSS, domain.DeploymentProviderTypeAliyunVOD, domain.DeploymentProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -150,11 +151,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunALB.NewDeployer(&pAliyunALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -162,11 +163,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderExtendedConfig, "serviceType")), - GatewayId: maputil.GetString(options.ProviderExtendedConfig, "gatewayId"), - GroupId: maputil.GetString(options.ProviderExtendedConfig, "groupId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderServiceConfig, "serviceType")), + GatewayId: maputil.GetString(options.ProviderServiceConfig, "gatewayId"), + GroupId: maputil.GetString(options.ProviderServiceConfig, "groupId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -174,7 +175,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), }) return deployer, err @@ -182,9 +183,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), - ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -192,7 +193,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCDN.NewDeployer(&pAliyunCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -200,11 +201,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunCLB.NewDeployer(&pAliyunCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetOrDefaultInt32(options.ProviderExtendedConfig, "listenerPort", 443), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetOrDefaultInt32(options.ProviderServiceConfig, "listenerPort", 443), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -212,7 +213,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunDCDN.NewDeployer(&pAliyunDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -220,8 +221,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunDDoS.NewDeployer(&pAliyunDDoS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -229,8 +230,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunESA.NewDeployer(&pAliyunESA.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - SiteId: maputil.GetInt64(options.ProviderExtendedConfig, "siteId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + SiteId: maputil.GetInt64(options.ProviderServiceConfig, "siteId"), }) return deployer, err @@ -238,9 +239,20 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderServiceConfig, "serviceVersion", "3.0"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + }) + return deployer, err + + case domain.DeploymentProviderTypeAliyunGA: + deployer, err := pAliyunGA.NewDeployer(&pAliyunGA.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + ResourceType: pAliyunGA.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + AcceleratorId: maputil.GetString(options.ProviderServiceConfig, "acceleratorId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -248,8 +260,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -257,10 +269,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunNLB.NewDeployer(&pAliyunNLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -268,9 +280,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunOSS.NewDeployer(&pAliyunOSS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -278,8 +290,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunVOD.NewDeployer(&pAliyunVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -287,10 +299,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAliyunWAF.NewDeployer(&pAliyunWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderServiceConfig, "serviceVersion", "3.0"), + InstanceId: maputil.GetString(options.ProviderServiceConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -311,8 +323,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSACM.NewDeployer(&pAWSACM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - CertificateArn: maputil.GetString(options.ProviderExtendedConfig, "certificateArn"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + CertificateArn: maputil.GetString(options.ProviderServiceConfig, "certificateArn"), }) return deployer, err @@ -320,8 +332,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - DistributionId: maputil.GetString(options.ProviderExtendedConfig, "distributionId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + DistributionId: maputil.GetString(options.ProviderServiceConfig, "distributionId"), }) return deployer, err @@ -344,8 +356,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ClientId: access.ClientId, ClientSecret: access.ClientSecret, CloudName: access.CloudName, - KeyVaultName: maputil.GetString(options.ProviderExtendedConfig, "keyvaultName"), - CertificateName: maputil.GetString(options.ProviderExtendedConfig, "certificateName"), + KeyVaultName: maputil.GetString(options.ProviderServiceConfig, "keyvaultName"), + CertificateName: maputil.GetString(options.ProviderServiceConfig, "certificateName"), }) return deployer, err @@ -366,11 +378,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudAppBLB.NewDeployer(&pBaiduCloudAppBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderServiceConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -378,11 +390,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudBLB.NewDeployer(&pBaiduCloudBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderServiceConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -390,7 +402,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBaiduCloudCDN.NewDeployer(&pBaiduCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -417,8 +429,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeBaishanCDN: deployer, err := pBaishanCDN.NewDeployer(&pBaishanCDN.DeployerConfig{ ApiToken: access.ApiToken, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -440,7 +452,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err @@ -449,9 +461,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - SiteType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "siteType", "other"), - SiteName: maputil.GetString(options.ProviderExtendedConfig, "siteName"), - SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + SiteType: maputil.GetOrDefaultString(options.ProviderServiceConfig, "siteType", "other"), + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), + SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -469,8 +481,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBunnyCDN.NewDeployer(&pBunnyCDN.DeployerConfig{ ApiKey: access.ApiKey, - PullZoneId: maputil.GetString(options.ProviderExtendedConfig, "pullZoneId"), - Hostname: maputil.GetString(options.ProviderExtendedConfig, "hostname"), + PullZoneId: maputil.GetString(options.ProviderServiceConfig, "pullZoneId"), + Hostname: maputil.GetString(options.ProviderServiceConfig, "hostname"), }) return deployer, err } @@ -487,7 +499,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pBytePlusCDN.NewDeployer(&pBytePlusCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -521,9 +533,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), - SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), + SiteId: maputil.GetString(options.ProviderServiceConfig, "siteId"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -538,7 +550,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pDogeCDN.NewDeployer(&pDogeCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err } @@ -553,7 +565,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pEdgioApplications.NewDeployer(&pEdgioApplications.DeployerConfig{ ClientId: access.ClientId, ClientSecret: access.ClientSecret, - EnvironmentId: maputil.GetString(options.ProviderExtendedConfig, "environmentId"), + EnvironmentId: maputil.GetString(options.ProviderServiceConfig, "environmentId"), }) return deployer, err } @@ -571,8 +583,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessKeyId: access.AccessKeyId, AccessKey: access.AccessKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pFlexCDN.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pFlexCDN.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -588,8 +600,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeGcoreCDN: deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{ ApiToken: access.ApiToken, - ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceId: maputil.GetInt64(options.ProviderServiceConfig, "resourceId"), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err @@ -611,8 +623,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessKeyId: access.AccessKeyId, AccessKey: access.AccessKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pGoEdge.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pGoEdge.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -629,8 +641,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudCDN.NewDeployer(&pHuaweiCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -638,11 +650,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudELB.NewDeployer(&pHuaweiCloudELB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -657,10 +669,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pHuaweiCloudWAF.NewDeployer(&pHuaweiCloudWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -681,10 +693,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudALB.NewDeployer(&pJDCloudALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), - ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + RegionId: maputil.GetString(options.ProviderServiceConfig, "regionId"), + ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -692,7 +704,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudCDN.NewDeployer(&pJDCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -700,7 +712,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudLive.NewDeployer(&pJDCloudLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -708,7 +720,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pJDCloudVOD.NewDeployer(&pJDCloudVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -720,18 +732,18 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ - ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderServiceConfig, "shellEnv")), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } @@ -745,11 +757,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pK8sSecret.NewDeployer(&pK8sSecret.DeployerConfig{ KubeConfig: access.KubeConfig, - Namespace: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), - SecretName: maputil.GetString(options.ProviderExtendedConfig, "secretName"), - SecretType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), - SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), - SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), + Namespace: maputil.GetOrDefaultString(options.ProviderServiceConfig, "namespace", "default"), + SecretName: maputil.GetString(options.ProviderServiceConfig, "secretName"), + SecretType: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretType", "kubernetes.io/tls"), + SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderServiceConfig, "secretDataKeyForKey", "tls.key"), }) return deployer, err } @@ -763,7 +775,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pNetlifySite.NewDeployer(&pNetlifySite.DeployerConfig{ ApiToken: access.ApiToken, - SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), + SiteId: maputil.GetString(options.ProviderServiceConfig, "siteId"), }) return deployer, err } @@ -780,8 +792,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiToken: access.ApiToken, ApiTokenSecret: access.ApiTokenSecret, AllowInsecureConnections: access.AllowInsecureConnections, - NodeName: maputil.GetString(options.ProviderExtendedConfig, "nodeName"), - AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), + NodeName: maputil.GetString(options.ProviderServiceConfig, "nodeName"), + AutoRestart: maputil.GetBool(options.ProviderServiceConfig, "autoRestart"), }) return deployer, err } @@ -798,7 +810,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -806,8 +818,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pQiniuPili.NewDeployer(&pQiniuPili.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Hub: maputil.GetString(options.ProviderExtendedConfig, "hub"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Hub: maputil.GetString(options.ProviderServiceConfig, "hub"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -827,8 +839,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeTencentCloudCDN: deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{ ApiKey: access.ApiKey, - InstanceId: maputil.GetInt32(options.ProviderExtendedConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + InstanceId: maputil.GetInt32(options.ProviderServiceConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -860,7 +872,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessTokenId: access.AccessTokenId, AccessToken: access.AccessToken, AllowInsecureConnections: access.AllowInsecureConnections, - SiteName: maputil.GetString(options.ProviderExtendedConfig, "siteName"), + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), }) return deployer, err @@ -880,8 +892,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer ApiUrl: access.ApiUrl, ApiToken: access.ApiToken, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - CertificateId: maputil.GetInt32(options.ProviderExtendedConfig, "certificateId"), + ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt32(options.ProviderServiceConfig, "certificateId"), }) return deployer, err } @@ -900,18 +912,18 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + UseSCP: maputil.GetBool(options.ProviderServiceConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } @@ -928,7 +940,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCDN.NewDeployer(&pTencentCloudCDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -936,11 +948,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCLB.NewDeployer(&pTencentCloudCLB.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -948,9 +960,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCOS.NewDeployer(&pTencentCloudCOS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -958,7 +970,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudCSS.NewDeployer(&pTencentCloudCSS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -966,7 +978,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudECDN.NewDeployer(&pTencentCloudECDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -974,8 +986,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudEO.NewDeployer(&pTencentCloudEO.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + ZoneId: maputil.GetString(options.ProviderServiceConfig, "zoneId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -983,8 +995,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -999,9 +1011,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: maputil.GetString(options.ProviderExtendedConfig, "resourceType"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: maputil.GetString(options.ProviderServiceConfig, "resourceType"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -1009,8 +1021,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudVOD.NewDeployer(&pTencentCloudVOD.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - SubAppId: maputil.GetInt64(options.ProviderExtendedConfig, "subAppId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + SubAppId: maputil.GetInt64(options.ProviderServiceConfig, "subAppId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1018,9 +1030,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pTencentCloudWAF.NewDeployer(&pTencentCloudWAF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), - InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + DomainId: maputil.GetString(options.ProviderServiceConfig, "domainId"), + InstanceId: maputil.GetString(options.ProviderServiceConfig, "instanceId"), }) return deployer, err @@ -1042,7 +1054,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), + DomainId: maputil.GetString(options.ProviderServiceConfig, "domainId"), }) return deployer, err @@ -1051,9 +1063,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1074,7 +1086,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ Username: access.Username, Password: access.Password, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1095,11 +1107,11 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineALB.NewDeployer(&pVolcEngineALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1107,7 +1119,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCDN.NewDeployer(&pVolcEngineCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1115,7 +1127,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCertCenter.NewDeployer(&pVolcEngineCertCenter.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), }) return deployer, err @@ -1123,10 +1135,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineCLB.NewDeployer(&pVolcEngineCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderServiceConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderServiceConfig, "listenerId"), }) return deployer, err @@ -1134,7 +1146,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineDCDN.NewDeployer(&pVolcEngineDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1142,9 +1154,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineImageX.NewDeployer(&pVolcEngineImageX.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - ServiceId: maputil.GetString(options.ProviderExtendedConfig, "serviceId"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + ServiceId: maputil.GetString(options.ProviderServiceConfig, "serviceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1152,7 +1164,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineLive.NewDeployer(&pVolcEngineLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1160,9 +1172,9 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pVolcEngineTOS.NewDeployer(&pVolcEngineTOS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderExtendedConfig, "region"), - Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + Bucket: maputil.GetString(options.ProviderServiceConfig, "bucket"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), }) return deployer, err @@ -1184,10 +1196,10 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, ApiKey: access.ApiKey, - Environment: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), - Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), - WebhookId: maputil.GetString(options.ProviderExtendedConfig, "webhookId"), + Environment: maputil.GetOrDefaultString(options.ProviderServiceConfig, "environment", "production"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + WebhookId: maputil.GetString(options.ProviderServiceConfig, "webhookId"), }) return deployer, err @@ -1213,7 +1225,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) } } - if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + if extendedHeadersString := maputil.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" { h, err := httputil.ParseHeaders(extendedHeadersString) if err != nil { return nil, fmt.Errorf("failed to parse webhook headers: %w", err) @@ -1225,7 +1237,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForDeployment), + WebhookData: maputil.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForDeployment), Method: access.Method, Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 45c72c05..fcbaa031 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -174,7 +174,7 @@ const ( DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos") DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa") DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc") - DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") // 阿里云全球加速(预留) + DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live") DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb") DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss") diff --git a/internal/notify/notifier.go b/internal/notify/notifier.go index 955e88c3..ee3fbd2f 100644 --- a/internal/notify/notifier.go +++ b/internal/notify/notifier.go @@ -31,9 +31,9 @@ func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error nodeConfig := config.Node.GetConfigForNotify() options := ¬ifierProviderOptions{ - Provider: domain.NotificationProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderExtendedConfig: nodeConfig.ProviderConfig, + Provider: domain.NotificationProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderServiceConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() diff --git a/internal/notify/providers.go b/internal/notify/providers.go index 70787480..3a8c575d 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -18,9 +18,9 @@ import ( ) type notifierProviderOptions struct { - Provider domain.NotificationProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any + Provider domain.NotificationProviderType + ProviderAccessConfig map[string]any + ProviderServiceConfig map[string]any } func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) { @@ -55,8 +55,8 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier SmtpTls: access.SmtpTls, Username: access.Username, Password: access.Password, - SenderAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "senderAddress", access.DefaultSenderAddress), - ReceiverAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", access.DefaultReceiverAddress), + SenderAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "senderAddress", access.DefaultSenderAddress), + ReceiverAddress: maputil.GetOrDefaultString(options.ProviderServiceConfig, "receiverAddress", access.DefaultReceiverAddress), }) } @@ -83,7 +83,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier ServerUrl: access.ServerUrl, Username: access.Username, Password: access.Password, - ChannelId: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "channelId", access.DefaultChannelId), + ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId), }) } @@ -96,7 +96,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return pTelegramBot.NewNotifier(&pTelegramBot.NotifierConfig{ BotToken: access.BotToken, - ChatId: maputil.GetOrDefaultInt64(options.ProviderExtendedConfig, "chatId", access.DefaultChatId), + ChatId: maputil.GetOrDefaultInt64(options.ProviderServiceConfig, "chatId", access.DefaultChatId), }) } @@ -117,7 +117,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) } } - if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + if extendedHeadersString := maputil.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" { h, err := httputil.ParseHeaders(extendedHeadersString) if err != nil { return nil, fmt.Errorf("failed to parse webhook headers: %w", err) @@ -129,7 +129,7 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForNotification), + WebhookData: maputil.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DefaultDataForNotification), Method: access.Method, Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index 3dca4a9d..35b4997c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -157,7 +157,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -192,7 +192,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -211,8 +211,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId d.logger.Info("found https/quic listeners to deploy", slog.Any("listenerIds", listenerIds)) for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go index c75119e9..11d5b565 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -96,6 +96,7 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index d443514e..34c3a49e 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -171,7 +171,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId select { case <-ctx.Done(): return ctx.Err() - default: if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { errs = append(errs, err) diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go new file mode 100644 index 00000000..f69660a8 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga.go @@ -0,0 +1,322 @@ +package aliyunga + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + + aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliga "github.com/alibabacloud-go/ga-20191120/v3/client" + "github.com/alibabacloud-go/tea/tea" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" +) + +type DeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 全球加速实例 ID。 + AcceleratorId string `json:"acceleratorId"` + // 全球加速监听 ID。 + // 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` + // SNI 域名(不支持泛域名)。 + // 部署资源类型为 [RESOURCE_TYPE_ACCELERATOR]、[RESOURCE_TYPE_LISTENER] 时选填。 + Domain string `json:"domain,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *aliga.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_ACCELERATOR: + if err := d.deployToAccelerator(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { + return nil, err + } + + case RESOURCE_TYPE_LISTENER: + if err := d.deployToListener(ctx, upres.ExtendedData["certIdentifier"].(string)); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToAccelerator(ctx context.Context, cloudCertId string) error { + if d.config.AcceleratorId == "" { + return errors.New("config `acceleratorId` is required") + } + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlisteners + listenerIds := make([]string, 0) + listListenersPageNumber := int32(1) + listListenersPageSize := int32(50) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + listListenersReq := &aliga.ListListenersRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(d.config.AcceleratorId), + PageNumber: tea.Int32(listListenersPageNumber), + PageSize: tea.Int32(listListenersPageSize), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + d.logger.Debug("sdk request 'ga.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.ListListeners': %w", err) + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) + } + } + } + + if len(listListenersResp.Body.Listeners) < int(listListenersPageSize) { + break + } else { + listListenersPageNumber++ + } + } + + // 遍历更新监听证书 + if len(listenerIds) == 0 { + d.logger.Info("no ga listeners to deploy") + } else { + var errs []error + d.logger.Info("found https listeners to deploy", slog.Any("listenerIds", listenerIds)) + + for _, listenerId := range listenerIds { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.AcceleratorId == "" { + return errors.New("config `acceleratorId` is required") + } + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.AcceleratorId, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudAcceleratorId string, cloudListenerId string, cloudCertId string) error { + // 查询监听绑定的证书列表 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlistenercertificates + var listenerDefaultCertificate *aliga.ListListenerCertificatesResponseBodyCertificates + var listenerAdditionalCertificates []*aliga.ListListenerCertificatesResponseBodyCertificates = make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) + var listListenerCertificatesNextToken *string + for { + listListenerCertificatesReq := &aliga.ListListenerCertificatesRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(d.config.AcceleratorId), + NextToken: listListenerCertificatesNextToken, + MaxResults: tea.Int32(20), + } + listListenerCertificatesResp, err := d.sdkClient.ListListenerCertificates(listListenerCertificatesReq) + d.logger.Debug("sdk request 'ga.ListListenerCertificates'", slog.Any("request", listListenerCertificatesReq), slog.Any("response", listListenerCertificatesResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.ListListenerCertificates': %w", err) + } + + if listListenerCertificatesResp.Body.Certificates != nil { + for _, certificate := range listListenerCertificatesResp.Body.Certificates { + if tea.BoolValue(certificate.IsDefault) { + listenerDefaultCertificate = certificate + } else { + listenerAdditionalCertificates = append(listenerAdditionalCertificates, certificate) + } + } + } + + if listListenerCertificatesResp.Body.NextToken == nil { + break + } else { + listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken + } + } + + if d.config.Domain == "" { + // 未指定 SNI,只需部署到监听器 + if listenerDefaultCertificate != nil && tea.StringValue(listenerDefaultCertificate.CertificateId) == cloudCertId { + d.logger.Info("no need to update ga listener default certificate") + return nil + } + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updatelistener + updateListenerReq := &aliga.UpdateListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliga.UpdateListenerRequestCertificates{{ + Id: tea.String(cloudCertId), + }}, + } + updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq) + d.logger.Debug("sdk request 'ga.UpdateListener'", slog.Any("request", updateListenerReq), slog.Any("response", updateListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.UpdateListener': %w", err) + } + } else { + // 指定 SNI,需部署到扩展域名 + if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool { + return tea.StringValue(item.CertificateId) == cloudCertId + }) { + d.logger.Info("no need to update ga listener additional certificate") + return nil + } + + if sliceutil.Some(listenerAdditionalCertificates, func(item *aliga.ListListenerCertificatesResponseBodyCertificates) bool { + return tea.StringValue(item.Domain) == d.config.Domain + }) { + // 为监听替换扩展证书 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-updateadditionalcertificatewithlistener + updateAdditionalCertificateWithListenerReq := &aliga.UpdateAdditionalCertificateWithListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(cloudAcceleratorId), + ListenerId: tea.String(cloudListenerId), + CertificateId: tea.String(cloudCertId), + Domain: tea.String(d.config.Domain), + } + updateAdditionalCertificateWithListenerResp, err := d.sdkClient.UpdateAdditionalCertificateWithListener(updateAdditionalCertificateWithListenerReq) + d.logger.Debug("sdk request 'ga.UpdateAdditionalCertificateWithListener'", slog.Any("request", updateAdditionalCertificateWithListenerReq), slog.Any("response", updateAdditionalCertificateWithListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.UpdateAdditionalCertificateWithListener': %w", err) + } + } else { + // 为监听绑定扩展证书 + // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-associateadditionalcertificateswithlistener + associateAdditionalCertificatesWithListenerReq := &aliga.AssociateAdditionalCertificatesWithListenerRequest{ + RegionId: tea.String("cn-hangzhou"), + AcceleratorId: tea.String(cloudAcceleratorId), + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliga.AssociateAdditionalCertificatesWithListenerRequestCertificates{{ + Id: tea.String(cloudCertId), + Domain: tea.String(d.config.Domain), + }}, + } + associateAdditionalCertificatesWithListenerResp, err := d.sdkClient.AssociateAdditionalCertificatesWithListener(associateAdditionalCertificatesWithListenerReq) + d.logger.Debug("sdk request 'ga.AssociateAdditionalCertificatesWithListener'", slog.Any("request", associateAdditionalCertificatesWithListenerReq), slog.Any("response", associateAdditionalCertificatesWithListenerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'ga.AssociateAdditionalCertificatesWithListener': %w", err) + } + } + } + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliga.Client, error) { + // 接入点一览 https://api.aliyun.com/product/Ga + config := &aliopen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("ga.cn-hangzhou.aliyuncs.com"), + } + + client, err := aliga.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} + +func createSslUploader(accessKeyId, accessKeySecret string) (uploader.Uploader, error) { + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: "cn-hangzhou", + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go new file mode 100644 index 00000000..611ddc41 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/aliyun_ga_test.go @@ -0,0 +1,118 @@ +package aliyunga_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-ga" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fAcceleratorId string + fListenerId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNGA_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fAcceleratorId, argsPrefix+"ACCELERATORID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_ga_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNGA_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_ACCELERATORID="your-ga-accelerator-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_LISTENERID="your-ga-listener-id" \ + --CERTIMATE_DEPLOYER_ALIYUNGA_DOMAIN="your-ga-sni-domain" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToAccelerator", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("ACCELERATORID: %v", fAcceleratorId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + ResourceType: provider.RESOURCE_TYPE_ACCELERATOR, + AcceleratorId: fAcceleratorId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + ResourceType: provider.RESOURCE_TYPE_LISTENER, + ListenerId: fListenerId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-ga/consts.go b/internal/pkg/core/deployer/providers/aliyun-ga/consts.go new file mode 100644 index 00000000..f96d98d5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-ga/consts.go @@ -0,0 +1,10 @@ +package aliyunga + +type ResourceType string + +const ( + // 资源类型:部署到指定全球加速器。 + RESOURCE_TYPE_ACCELERATOR = ResourceType("accelerator") + // 资源类型:部署到指定监听器。 + RESOURCE_TYPE_LISTENER = ResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index b8391144..58015f3d 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -145,7 +145,7 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId if listListenersResp.Body.Listeners != nil { for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, *listener.ListenerId) + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } @@ -167,7 +167,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId select { case <-ctx.Done(): return ctx.Err() - default: if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { errs = append(errs, err) diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index 748111dd..23ec4a92 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -210,7 +210,6 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str select { case <-ctx.Done(): return ctx.Err() - default: if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { errs = append(errs, err) diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 981d7183..ff2f96b3 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -27,6 +27,7 @@ import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDC import DeployNodeConfigFormAliyunDDoSConfig from "./DeployNodeConfigFormAliyunDDoSConfig"; import DeployNodeConfigFormAliyunESAConfig from "./DeployNodeConfigFormAliyunESAConfig"; import DeployNodeConfigFormAliyunFCConfig from "./DeployNodeConfigFormAliyunFCConfig"; +import DeployNodeConfigFormAliyunGAConfig from "./DeployNodeConfigFormAliyunGAConfig"; import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig"; import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; @@ -203,6 +204,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.ALIYUN_FC: return ; + case DEPLOYMENT_PROVIDERS.ALIYUN_GA: + return ; case DEPLOYMENT_PROVIDERS.ALIYUN_LIVE: return ; case DEPLOYMENT_PROVIDERS.ALIYUN_NLB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx new file mode 100644 index 00000000..ba2633c4 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx @@ -0,0 +1,118 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormAliyunGAConfigFieldValues = Nullish<{ + resourceType: string; + acceleratorId?: string; + listenerId?: string; + domain?: string; +}>; + +export type DeployNodeConfigFormAliyunGAConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunGAConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunGAConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_ACCELERATOR = "accelerator" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const initFormModel = (): DeployNodeConfigFormAliyunGAConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunGAConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAliyunGAConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.union([z.literal(RESOURCE_TYPE_ACCELERATOR), z.literal(RESOURCE_TYPE_LISTENER)], { + message: t("workflow_node.deploy.form.aliyun_ga_resource_type.placeholder"), + }), + acceleratorId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + listenerId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim() + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_ga_listener_id.placeholder")), + domain: z + .string() + .nullish() + .refine((v) => { + if (![RESOURCE_TYPE_ACCELERATOR, RESOURCE_TYPE_LISTENER].includes(fieldResourceType)) return true; + return !v || validDomainName(v!); + }, t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + } + > + + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunGAConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 4cad8606..a2c432ce 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -353,6 +353,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ ALIYUN_DDOS: `${ACCESS_PROVIDERS.ALIYUN}-ddospro`, ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`, ALIYUN_FC: `${ACCESS_PROVIDERS.ALIYUN}-fc`, + ALIYUN_GA: `${ACCESS_PROVIDERS.ALIYUN}-ga`, ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`, ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`, ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`, @@ -475,6 +476,7 @@ export const deploymentProvidersMap: Maphttps://fcnext.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_resource_type.label": "Resource type", + "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.accelerator.label": "GA accelerator", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.listener.label": "GA listener", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.label": "Alibaba Cloud GA accelerator ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.placeholder": "Please enter Alibaba Cloud GA accelerator ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.tooltip": "For more information, https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_listener_id.label": "Alibaba Cloud GA listener ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.placeholder": "Please enter Alibaba Cloud GA listener ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.tooltip": "For more information, https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_snidomain.label": "Alibaba Cloud GA SNI domain (Optional)", + "workflow_node.deploy.form.aliyun_ga_snidomain.placeholder": "Please enter Alibaba Cloud GA SNI domain name", + "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "For more information, https://ga.console.aliyun.com", "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud Live region", "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud Live region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 27abe56c..45f3fd53 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -16,6 +16,7 @@ "provider.aliyun.dns": "阿里云 - 云解析 DNS", "provider.aliyun.esa": "阿里云 - 边缘安全加速 ESA", "provider.aliyun.fc": "阿里云 - 函数计算 FC", + "provider.aliyun.ga": "阿里云 - 全球加速 GA", "provider.aliyun.live": "阿里云 - 视频直播 Live", "provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", "provider.aliyun.oss": "阿里云 - 对象存储 OSS", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index e462f34a..ff2f0522 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -212,6 +212,19 @@ "workflow_node.deploy.form.aliyun_fc_domain.label": "阿里云 FC 自定义域名", "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "请输入阿里云 FC 自定义域名(支持泛域名)", "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "这是什么?请参阅 see https://fcnext.console.aliyun.com/", + "workflow_node.deploy.form.aliyun_ga_resource_type.label": "证书替换方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.accelerator.label": "替换指定全球加速器下的全部 HTTPS 监听的证书", + "workflow_node.deploy.form.aliyun_ga_resource_type.option.listener.label": "替换指定全球加速器监听器的证书", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.label": "阿里云全球加速实例 ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.placeholder": "请输入阿里云全球加速实例 ID", + "workflow_node.deploy.form.aliyun_ga_accelerator_id.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_listener_id.label": "阿里云全球加速监听 ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.placeholder": "请输入阿里云全球加速监听 ID", + "workflow_node.deploy.form.aliyun_ga_listener_id.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com", + "workflow_node.deploy.form.aliyun_ga_snidomain.label": "阿里云全球加速扩展域名(可选)", + "workflow_node.deploy.form.aliyun_ga_snidomain.placeholder": "请输入阿里云全球加速扩展域名", + "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com

不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_live_region.label": "阿里云视频直播服务地域", "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云视频直播服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", From 3098f6a82fccb3489034f99231b6bd2d0eaa6486 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 19 May 2025 10:35:05 +0800 Subject: [PATCH 06/13] feat: rename certificate props 'issuer' to 'issuerOrg' --- internal/domain/certificate.go | 4 +-- internal/repository/certificate.go | 4 +-- migrations/1747389600_upgrade.go | 29 +++++++++++++++++++ .../certificate/CertificateDetail.tsx | 2 +- ui/src/domain/certificate.ts | 2 +- ui/src/pages/certificates/CertificateList.tsx | 4 +-- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go index d2a998da..710ce268 100644 --- a/internal/domain/certificate.go +++ b/internal/domain/certificate.go @@ -20,7 +20,7 @@ type Certificate struct { SerialNumber string `json:"serialNumber" db:"serialNumber"` Certificate string `json:"certificate" db:"certificate"` PrivateKey string `json:"privateKey" db:"privateKey"` - Issuer string `json:"issuer" db:"issuer"` + IssuerOrg string `json:"issuerOrg" db:"issuerOrg"` IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"` KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"` EffectAt time.Time `json:"effectAt" db:"effectAt"` @@ -38,7 +38,7 @@ type Certificate struct { func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate { c.SubjectAltNames = strings.Join(certX509.DNSNames, ";") c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16)) - c.Issuer = strings.Join(certX509.Issuer.Organization, ";") + c.IssuerOrg = strings.Join(certX509.Issuer.Organization, ";") c.EffectAt = certX509.NotBefore c.ExpireAt = certX509.NotAfter diff --git a/internal/repository/certificate.go b/internal/repository/certificate.go index 13d2c094..95bfd713 100644 --- a/internal/repository/certificate.go +++ b/internal/repository/certificate.go @@ -101,7 +101,7 @@ func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Ce record.Set("serialNumber", certificate.SerialNumber) record.Set("certificate", certificate.Certificate) record.Set("privateKey", certificate.PrivateKey) - record.Set("issuer", certificate.Issuer) + record.Set("issuerOrg", certificate.IssuerOrg) record.Set("issuerCertificate", certificate.IssuerCertificate) record.Set("keyAlgorithm", string(certificate.KeyAlgorithm)) record.Set("effectAt", certificate.EffectAt) @@ -162,7 +162,7 @@ func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain. SerialNumber: record.GetString("serialNumber"), Certificate: record.GetString("certificate"), PrivateKey: record.GetString("privateKey"), - Issuer: record.GetString("issuer"), + IssuerOrg: record.GetString("issuerOrg"), IssuerCertificate: record.GetString("issuerCertificate"), KeyAlgorithm: domain.CertificateKeyAlgorithmType(record.GetString("keyAlgorithm")), EffectAt: record.GetDateTime("effectAt").Time(), diff --git a/migrations/1747389600_upgrade.go b/migrations/1747389600_upgrade.go index aa5e2e3a..a145679a 100644 --- a/migrations/1747389600_upgrade.go +++ b/migrations/1747389600_upgrade.go @@ -7,6 +7,35 @@ import ( func init() { m.Register(func(app core.App) error { + // update collection `certificate` + { + collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{ + "autogeneratePattern": "", + "hidden": false, + "id": "text2910474005", + "max": 0, + "min": 0, + "name": "issuerOrg", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }`)); err != nil { + return err + } + + if err := app.Save(collection); err != nil { + return err + } + } + // migrate data { accesses, err := app.FindAllRecords("access") diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index 1023bf16..2fc0d9d0 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -42,7 +42,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { - + diff --git a/ui/src/domain/certificate.ts b/ui/src/domain/certificate.ts index 563d2476..c4bc8710 100644 --- a/ui/src/domain/certificate.ts +++ b/ui/src/domain/certificate.ts @@ -6,7 +6,7 @@ export interface CertificateModel extends BaseModel { serialNumber: string; certificate: string; privateKey: string; - issuer: string; + issuerOrg: string; keyAlgorithm: string; effectAt: ISO8601String; expireAt: ISO8601String; diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index 46bc1745..97eab0ef 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -126,11 +126,11 @@ const CertificateList = () => { }, }, { - key: "issuer", + key: "brand", title: t("certificate.props.brand"), render: (_, record) => ( - {record.issuer} + {record.issuerOrg} {record.keyAlgorithm} ), From a66e1c04c94e9d67d308eb33fac1d1727a73e460 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 19 May 2025 11:39:45 +0800 Subject: [PATCH 07/13] feat(ui): allow clear input when field is optional --- .../components/access/AccessFormACMECAConfig.tsx | 4 ++-- .../access/AccessFormACMEHttpReqConfig.tsx | 4 ++-- .../access/AccessFormCloudflareConfig.tsx | 2 +- .../access/AccessFormProxmoxVEConfig.tsx | 2 +- .../components/access/AccessFormUCloudConfig.tsx | 2 +- .../components/access/AccessFormVercelConfig.tsx | 2 +- .../components/access/AccessFormWebhookConfig.tsx | 14 ++++++++++---- .../node/DeployNodeConfigFormAWSACMConfig.tsx | 2 +- .../node/DeployNodeConfigFormAliyunALBConfig.tsx | 2 +- .../node/DeployNodeConfigFormAliyunCLBConfig.tsx | 2 +- .../node/DeployNodeConfigFormAliyunGAConfig.tsx | 2 +- .../node/DeployNodeConfigFormAliyunWAFConfig.tsx | 2 +- .../DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx | 2 +- .../DeployNodeConfigFormBaiduCloudBLBConfig.tsx | 2 +- .../node/DeployNodeConfigFormBaishanCDNConfig.tsx | 2 +- .../node/DeployNodeConfigFormGcoreCDNConfig.tsx | 2 +- .../node/DeployNodeConfigFormJDCloudALBConfig.tsx | 2 +- .../node/DeployNodeConfigFormLocalConfig.tsx | 4 ++-- .../node/DeployNodeConfigFormSSHConfig.tsx | 4 ++-- .../DeployNodeConfigFormTencentCloudCLBConfig.tsx | 2 +- .../DeployNodeConfigFormVolcEngineALBConfig.tsx | 2 +- .../DeployNodeConfigFormWangsuCDNProConfig.tsx | 4 ++-- 22 files changed, 36 insertions(+), 30 deletions(-) diff --git a/ui/src/components/access/AccessFormACMECAConfig.tsx b/ui/src/components/access/AccessFormACMECAConfig.tsx index 8c9eb2ac..ac4a32c2 100644 --- a/ui/src/components/access/AccessFormACMECAConfig.tsx +++ b/ui/src/components/access/AccessFormACMECAConfig.tsx @@ -59,7 +59,7 @@ const AccessFormACMECAConfig = ({ form: formInst, formName, disabled, initialVal rules={[formRule]} tooltip={} > - + } > - + ); diff --git a/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx b/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx index 03cf163a..57cbc22d 100644 --- a/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx +++ b/ui/src/components/access/AccessFormACMEHttpReqConfig.tsx @@ -84,7 +84,7 @@ const AccessFormACMEHttpReqConfig = ({ form: formInst, formName, disabled, initi rules={[formRule]} tooltip={} > - + } > - + ); diff --git a/ui/src/components/access/AccessFormCloudflareConfig.tsx b/ui/src/components/access/AccessFormCloudflareConfig.tsx index a06d753d..79b33e3a 100644 --- a/ui/src/components/access/AccessFormCloudflareConfig.tsx +++ b/ui/src/components/access/AccessFormCloudflareConfig.tsx @@ -66,7 +66,7 @@ const AccessFormCloudflareConfig = ({ form: formInst, formName, disabled, initia rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormProxmoxVEConfig.tsx b/ui/src/components/access/AccessFormProxmoxVEConfig.tsx index afdc02de..d0a66745 100644 --- a/ui/src/components/access/AccessFormProxmoxVEConfig.tsx +++ b/ui/src/components/access/AccessFormProxmoxVEConfig.tsx @@ -65,7 +65,7 @@ const AccessFormProxmoxVEConfig = ({ form: formInst, formName, disabled, initial rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/access/AccessFormUCloudConfig.tsx b/ui/src/components/access/AccessFormUCloudConfig.tsx index 495d21b9..fd623925 100644 --- a/ui/src/components/access/AccessFormUCloudConfig.tsx +++ b/ui/src/components/access/AccessFormUCloudConfig.tsx @@ -81,7 +81,7 @@ const AccessFormUCloudConfig = ({ form: formInst, formName, disabled, initialVal rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormVercelConfig.tsx b/ui/src/components/access/AccessFormVercelConfig.tsx index b1ed7b6f..4483a9f9 100644 --- a/ui/src/components/access/AccessFormVercelConfig.tsx +++ b/ui/src/components/access/AccessFormVercelConfig.tsx @@ -66,7 +66,7 @@ const AccessFormVercelConfig = ({ form: formInst, formName, disabled, initialVal rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 0dea7f7c..6e6ec87a 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -67,7 +67,6 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa } return true; }, t("access.form.webhook_headers.errmsg.invalid")), - allowInsecureConnections: z.boolean().nullish(), defaultDataForDeployment: z .string() .nullish() @@ -96,11 +95,12 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa return false; } }, t("access.form.webhook_default_data.errmsg.json_invalid")), + allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); - const handleWebhookHeadersBlur = (e: React.FocusEvent) => { - let value = e.target.value; + const handleWebhookHeadersBlur = () => { + let value = formInst.getFieldValue("headers"); value = value.trim(); value = value.replace(/(?} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx index f0964493..f007bf87 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx @@ -60,7 +60,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled, rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx index 3afcb7a1..bbfca5e6 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunALBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormAliyunALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx index 2c2e43b6..e666800e 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormAliyunCLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx index ba2633c4..20dd1ae1 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunGAConfig.tsx @@ -108,7 +108,7 @@ const DeployNodeConfigFormAliyunGAConfig = ({ form: formInst, formName, disabled rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx index 5f81cf71..a46c7327 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx @@ -102,7 +102,7 @@ const DeployNodeConfigFormAliyunWAFConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx index 7bd40b82..875d254b 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudAppBLBConfig.tsx @@ -135,7 +135,7 @@ const DeployNodeConfigFormBaiduCloudAppBLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx index 20bb22f1..99c0b059 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaiduCloudBLBConfig.tsx @@ -135,7 +135,7 @@ const DeployNodeConfigFormBaiduCloudBLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx index ad05b6a8..7d32bef5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaishanCDNConfig.tsx @@ -73,7 +73,7 @@ const DeployNodeConfigFormBaishanCDNConfig = ({ rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx index 4d548949..f21a4bb9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGcoreCDNConfig.tsx @@ -67,7 +67,7 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled rules={[formRule]} tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx index f54477ce..22c5bf08 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormJDCloudALBConfig.tsx @@ -132,7 +132,7 @@ const DeployNodeConfigFormJDCloudALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx index 75853eb7..282503e5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx @@ -351,7 +351,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i rules={[formRule]} tooltip={} > - + } > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index e99a2431..49110ce9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -363,7 +363,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini rules={[formRule]} tooltip={} > - + } > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx index 2e7dc127..760c6fac 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx @@ -144,7 +144,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx index 348f4d8d..650323ab 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormVolcEngineALBConfig.tsx @@ -140,7 +140,7 @@ const DeployNodeConfigFormVolcEngineALBConfig = ({ rules={[formRule]} tooltip={} > - + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx index 90bdb064..a86b34c0 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx @@ -89,7 +89,7 @@ const DeployNodeConfigFormBaishanCDNConfig = ({ rules={[formRule]} tooltip={} > - + } > - + ); From c9e6bd0c2f178812042097e1487282942e098186 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 19 May 2025 22:51:17 +0800 Subject: [PATCH 08/13] feat: new deployment provider: lecdn --- internal/deployer/providers.go | 22 +++ internal/domain/access.go | 9 + internal/domain/provider.go | 4 +- .../1panel-console/1panel_console.go | 1 + .../providers/1panel-site/1panel_site.go | 1 + .../providers/aliyun-clb/aliyun_clb_test.go | 4 +- .../deployer/providers/aliyun-fc/aliyun_fc.go | 1 + .../deployer/providers/flexcdn/flexcdn.go | 1 + .../providers/flexcdn/flexcdn_test.go | 8 +- .../core/deployer/providers/goedge/goedge.go | 1 + .../deployer/providers/goedge/goedge_test.go | 8 +- .../core/deployer/providers/lecdn/consts.go | 8 + .../core/deployer/providers/lecdn/lecdn.go | 176 ++++++++++++++++++ .../deployer/providers/lecdn/lecdn_test.go | 87 +++++++++ .../providers/safeline/safeline_test.go | 4 +- .../core/deployer/providers/ssh/ssh_test.go | 4 +- .../notifier/providers/email/email_test.go | 4 +- internal/pkg/sdk3rd/flexcdn/api.go | 18 +- internal/pkg/sdk3rd/goedge/api.go | 18 +- internal/pkg/sdk3rd/lecdn/v3/client/api.go | 49 +++++ internal/pkg/sdk3rd/lecdn/v3/client/client.go | 98 ++++++++++ internal/pkg/sdk3rd/lecdn/v3/client/models.go | 46 +++++ internal/pkg/sdk3rd/lecdn/v3/master/api.go | 49 +++++ internal/pkg/sdk3rd/lecdn/v3/master/client.go | 98 ++++++++++ internal/pkg/sdk3rd/lecdn/v3/master/models.go | 47 +++++ internal/pkg/sdk3rd/upyun/console/api.go | 36 ++-- internal/pkg/utils/map/getter.go | 110 ++++++----- ui/public/imgs/providers/lecdn.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormLeCDNConfig.tsx | 85 +++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormLeCDNConfig.tsx | 103 ++++++++++ ui/src/domain/access.ts | 10 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 31 ++- ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 9 + ui/src/i18n/locales/zh/nls.access.json | 31 ++- ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 89 +++++---- 40 files changed, 1128 insertions(+), 155 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/lecdn/consts.go create mode 100644 internal/pkg/core/deployer/providers/lecdn/lecdn.go create mode 100644 internal/pkg/core/deployer/providers/lecdn/lecdn_test.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/client/api.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/client/client.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/client/models.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/master/api.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/master/client.go create mode 100644 internal/pkg/sdk3rd/lecdn/v3/master/models.go create mode 100644 ui/public/imgs/providers/lecdn.svg create mode 100644 ui/src/components/access/AccessFormLeCDNConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 8870fe97..53951300 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -53,6 +53,7 @@ import ( pJDCloudLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-live" pJDCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-vod" pK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" + pLeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/lecdn" pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" pNetlifySite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/netlify-site" pProxmoxVE "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/proxmoxve" @@ -729,6 +730,27 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeLeCDN: + { + access := domain.AccessConfigForLeCDN{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pLeCDN.NewDeployer(&pLeCDN.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiVersion: access.ApiVersion, + ApiRole: access.ApiRole, + Username: access.Username, + Password: access.Password, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pLeCDN.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderServiceConfig, "certificateId"), + ClientId: maputil.GetInt64(options.ProviderServiceConfig, "clientId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ diff --git a/internal/domain/access.go b/internal/domain/access.go index 30e52412..a2fd8a4a 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -193,6 +193,15 @@ type AccessConfigForLarkBot struct { WebhookUrl string `json:"webhookUrl"` } +type AccessConfigForLeCDN struct { + ApiUrl string `json:"apiUrl"` + ApiVersion string `json:"apiVersion"` + ApiRole string `json:"apiRole"` + Username string `json:"username"` + Password string `json:"password"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForMattermost struct { ServerUrl string `json:"serverUrl"` Username string `json:"username"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index fcbaa031..d8091eb8 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -49,7 +49,7 @@ const ( AccessProviderTypeLarkBot = AccessProviderType("larkbot") AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt") AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging") - AccessProviderTypeLeCDN = AccessProviderType("lecdn") // LeCDN(预留) + AccessProviderTypeLeCDN = AccessProviderType("lecdn") AccessProviderTypeLocal = AccessProviderType("local") AccessProviderTypeMattermost = AccessProviderType("mattermost") AccessProviderTypeNamecheap = AccessProviderType("namecheap") @@ -208,7 +208,7 @@ const ( DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live") DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod") DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret") - DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) // LeCDN(预留) + DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal) DeploymentProviderTypeNetlifySite = DeploymentProviderType(AccessProviderTypeNetlify + "-site") DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE) diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go index 98073585..e81b264f 100644 --- a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go @@ -16,6 +16,7 @@ type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` // 1Panel 版本。 + // 可取值 "v1"、"v2"。 ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go index afecaf74..690e5242 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -19,6 +19,7 @@ type DeployerConfig struct { // 1Panel 地址。 ApiUrl string `json:"apiUrl"` // 1Panel 版本。 + // 可取值 "v1"、"v2"。 ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go index 3b8ce12d..dfa46173 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -18,7 +18,7 @@ var ( fAccessKeySecret string fRegion string fLoadbalancerId string - fListenerPort int + fListenerPort int64 fDomain string ) @@ -31,7 +31,7 @@ func init() { flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") - flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") + flag.Int64Var(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } diff --git a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go index 8557068c..426aa3a6 100644 --- a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go +++ b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go @@ -22,6 +22,7 @@ type DeployerConfig struct { // 阿里云地域。 Region string `json:"region"` // 服务版本。 + // 可取值 "2.0"、"3.0"。 ServiceVersion string `json:"serviceVersion"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go index 3d0f05e9..a12ed164 100644 --- a/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go @@ -19,6 +19,7 @@ type DeployerConfig struct { // FlexCDN URL。 ApiUrl string `json:"apiUrl"` // FlexCDN 用户角色。 + // 可取值 "user"、"admin"。 ApiRole string `json:"apiRole"` // FlexCDN AccessKeyId。 AccessKeyId string `json:"accessKeyId"` diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go index 4a693dc9..b9b8de07 100644 --- a/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go @@ -17,7 +17,7 @@ var ( fApiUrl string fAccessKeyId string fAccessKey string - fCertificateId int + fCertificateId int64 ) func init() { @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") - flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") } /* @@ -45,7 +45,7 @@ Shell command to run this test: func TestDeploy(t *testing.T) { flag.Parse() - t.Run("Deploy", func(t *testing.T) { + t.Run("Deploy_ToCertificate", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), @@ -63,7 +63,7 @@ func TestDeploy(t *testing.T) { AccessKey: fAccessKey, AllowInsecureConnections: true, ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, - CertificateId: int64(fCertificateId), + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/goedge/goedge.go b/internal/pkg/core/deployer/providers/goedge/goedge.go index 73eade64..ecae774e 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge.go @@ -19,6 +19,7 @@ type DeployerConfig struct { // GoEdge URL。 ApiUrl string `json:"apiUrl"` // GoEdge 用户角色。 + // 可取值 "user"、"admin"。 ApiRole string `json:"apiRole"` // GoEdge AccessKeyId。 AccessKeyId string `json:"accessKeyId"` diff --git a/internal/pkg/core/deployer/providers/goedge/goedge_test.go b/internal/pkg/core/deployer/providers/goedge/goedge_test.go index 928fb420..d10f931c 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge_test.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge_test.go @@ -17,7 +17,7 @@ var ( fApiUrl string fAccessKeyId string fAccessKey string - fCertificateId int + fCertificateId int64 ) func init() { @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") - flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") } /* @@ -45,7 +45,7 @@ Shell command to run this test: func TestDeploy(t *testing.T) { flag.Parse() - t.Run("Deploy", func(t *testing.T) { + t.Run("Deploy_ToCertificate", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), @@ -63,7 +63,7 @@ func TestDeploy(t *testing.T) { AccessKey: fAccessKey, AllowInsecureConnections: true, ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, - CertificateId: int64(fCertificateId), + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/lecdn/consts.go b/internal/pkg/core/deployer/providers/lecdn/consts.go new file mode 100644 index 00000000..f5b7c0c9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/consts.go @@ -0,0 +1,8 @@ +package lecdn + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/lecdn/lecdn.go b/internal/pkg/core/deployer/providers/lecdn/lecdn.go new file mode 100644 index 00000000..1ad88dcf --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/lecdn.go @@ -0,0 +1,176 @@ +package lecdn + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + leclientsdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/client" + lemastersdkv3 "github.com/usual2970/certimate/internal/pkg/sdk3rd/lecdn/v3/master" +) + +type DeployerConfig struct { + // LeCDN URL。 + ApiUrl string `json:"apiUrl"` + // LeCDN 版本。 + // 可取值 "v3"。 + ApiVersion string `json:"apiVersion"` + // LeCDN 用户角色。 + // 可取值 "client"、"master"。 + ApiRole string `json:"apiRole"` + // LeCDN 用户名。 + Username string `json:"accessKeyId"` + // LeCDN 用户密码。 + Password string `json:"accessKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` + // 客户 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时选填。 + ClientId int64 `json:"clientId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient interface{} +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +const ( + apiVersionV3 = "v3" + + apiRoleClient = "client" + apiRoleMaster = "master" +) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiRole, config.Username, config.Password, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 修改证书 + // REF: https://wdk0pwf8ul.feishu.cn/wiki/YE1XwCRIHiLYeKkPupgcXrlgnDd + switch sdkClient := d.sdkClient.(type) { + case *leclientsdkv3.Client: + updateSSLCertReq := &leclientsdkv3.UpdateCertificateRequest{ + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + Type: "upload", + SSLPEM: certPEM, + SSLKey: privkeyPEM, + AutoRenewal: false, + } + updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq) + d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err) + } + + case *lemastersdkv3.Client: + updateSSLCertReq := &lemastersdkv3.UpdateCertificateRequest{ + ClientId: d.config.ClientId, + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + Type: "upload", + SSLPEM: certPEM, + SSLKey: privkeyPEM, + AutoRenewal: false, + } + updateSSLCertResp, err := sdkClient.UpdateCertificate(d.config.CertificateId, updateSSLCertReq) + d.logger.Debug("sdk request 'lecdn.UpdateCertificate'", slog.Int64("certId", d.config.CertificateId), slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lecdn.UpdateCertificate': %w", err) + } + + default: + panic("sdk client is not implemented") + } + + return nil +} + +func createSdkClient(apiUrl, apiVersion, apiRole, username, password string, skipTlsVerify bool) (interface{}, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid lecdn api url") + } + + if username == "" { + return nil, errors.New("invalid lecdn username") + } + + if password == "" { + return nil, errors.New("invalid lecdn password") + } + + if apiVersion == apiVersionV3 && apiRole == apiRoleClient { + // v3 版客户端 + client := leclientsdkv3.NewClient(apiUrl, username, password) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil + } else if apiVersion == apiVersionV3 && apiRole == apiRoleMaster { + // v3 版主控端 + client := lemastersdkv3.NewClient(apiUrl, username, password) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil + } + + return nil, fmt.Errorf("invalid lecdn api version or user role") +} diff --git a/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go b/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go new file mode 100644 index 00000000..cbaa4523 --- /dev/null +++ b/internal/pkg/core/deployer/providers/lecdn/lecdn_test.go @@ -0,0 +1,87 @@ +package lecdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/lecdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiVersion string + fUsername string + fPassword string + fCertificateId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_LECDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v3", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./lecdn_test.go -args \ + --CERTIMATE_DEPLOYER_LECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_LECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_LECDN_APIURL="http://127.0.0.1:5090" \ + --CERTIMATE_DEPLOYER_LECDN_USERNAME="your-username" \ + --CERTIMATE_DEPLOYER_LECDN_PASSWORD="your-password" \ + --CERTIMATE_DEPLOYER_LECDN_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToCertificate", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIVERSION: %v", fApiVersion), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiVersion: fApiVersion, + ApiRole: "user", + Username: fUsername, + Password: fPassword, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/safeline/safeline_test.go b/internal/pkg/core/deployer/providers/safeline/safeline_test.go index 294086c8..67fe6755 100644 --- a/internal/pkg/core/deployer/providers/safeline/safeline_test.go +++ b/internal/pkg/core/deployer/providers/safeline/safeline_test.go @@ -16,7 +16,7 @@ var ( fInputKeyPath string fApiUrl string fApiToken string - fCertificateId int + fCertificateId int64 ) func init() { @@ -26,7 +26,7 @@ func init() { flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") - flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") + flag.Int64Var(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") } /* diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index b63471d1..ae908185 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -15,7 +15,7 @@ var ( fInputCertPath string fInputKeyPath string fSshHost string - fSshPort int + fSshPort int64 fSshUsername string fSshPassword string fOutputCertPath string @@ -28,7 +28,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fSshHost, argsPrefix+"SSHHOST", "", "") - flag.IntVar(&fSshPort, argsPrefix+"SSHPORT", 0, "") + flag.Int64Var(&fSshPort, argsPrefix+"SSHPORT", 0, "") flag.StringVar(&fSshUsername, argsPrefix+"SSHUSERNAME", "", "") flag.StringVar(&fSshPassword, argsPrefix+"SSHPASSWORD", "", "") flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "") diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 30bfba07..cf0669ca 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -17,7 +17,7 @@ const ( var ( fSmtpHost string - fSmtpPort int + fSmtpPort int64 fSmtpTLS bool fUsername string fPassword string @@ -29,7 +29,7 @@ func init() { argsPrefix := "CERTIMATE_NOTIFIER_EMAIL_" flag.StringVar(&fSmtpHost, argsPrefix+"SMTPHOST", "", "") - flag.IntVar(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "") + flag.Int64Var(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "") flag.BoolVar(&fSmtpTLS, argsPrefix+"SMTPTLS", false, "") flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") diff --git a/internal/pkg/sdk3rd/flexcdn/api.go b/internal/pkg/sdk3rd/flexcdn/api.go index 07fb2b34..5008fdf4 100644 --- a/internal/pkg/sdk3rd/flexcdn/api.go +++ b/internal/pkg/sdk3rd/flexcdn/api.go @@ -7,7 +7,13 @@ import ( "time" ) -func (c *Client) getAccessToken() error { +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" && c.accessTokenExp.After(time.Now()) { + return nil + } + req := &getAPIAccessTokenRequest{ Type: c.apiRole, AccessKeyId: c.accessKeyId, @@ -22,22 +28,18 @@ func (c *Client) getAccessToken() error { if err := json.Unmarshal(res.Body(), &resp); err != nil { return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) } else if resp.GetCode() != 200 { - return fmt.Errorf("flexcdn get access token failed: code: %d, message: %s", resp.GetCode(), resp.GetMessage()) + return fmt.Errorf("flexcdn get access token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) } - c.accessTokenMtx.Lock() c.accessToken = resp.Data.Token c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) - c.accessTokenMtx.Unlock() return nil } func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { - if c.accessToken == "" || c.accessTokenExp.Before(time.Now()) { - if err := c.getAccessToken(); err != nil { - return nil, err - } + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err } resp := &UpdateSSLCertResponse{} diff --git a/internal/pkg/sdk3rd/goedge/api.go b/internal/pkg/sdk3rd/goedge/api.go index f0f60944..4589f70c 100644 --- a/internal/pkg/sdk3rd/goedge/api.go +++ b/internal/pkg/sdk3rd/goedge/api.go @@ -7,7 +7,13 @@ import ( "time" ) -func (c *Client) getAccessToken() error { +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" && c.accessTokenExp.After(time.Now()) { + return nil + } + req := &getAPIAccessTokenRequest{ Type: c.apiRole, AccessKeyId: c.accessKeyId, @@ -22,22 +28,18 @@ func (c *Client) getAccessToken() error { if err := json.Unmarshal(res.Body(), &resp); err != nil { return fmt.Errorf("goedge api error: failed to unmarshal response: %w", err) } else if resp.GetCode() != 200 { - return fmt.Errorf("goedge get access token failed: code: %d, message: %s", resp.GetCode(), resp.GetMessage()) + return fmt.Errorf("goedge get access token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) } - c.accessTokenMtx.Lock() c.accessToken = resp.Data.Token c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) - c.accessTokenMtx.Unlock() return nil } func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { - if c.accessToken == "" || c.accessTokenExp.Before(time.Now()) { - if err := c.getAccessToken(); err != nil { - return nil, err - } + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err } resp := &UpdateSSLCertResponse{} diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/api.go b/internal/pkg/sdk3rd/lecdn/v3/client/api.go new file mode 100644 index 00000000..ffdc70e3 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/api.go @@ -0,0 +1,49 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" { + return nil + } + + req := &loginRequest{ + Username: c.username, + Password: c.password, + } + res, err := c.sendRequest(http.MethodPost, "/login", req) + if err != nil { + return err + } + + resp := &loginResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("lecdn get token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) + } + + c.accessToken = resp.Data.Token + + return nil +} + +func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certId == 0 { + return nil, fmt.Errorf("lecdn api error: invalid parameter: CertId") + } + + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err + } + + resp := &UpdateCertificateResponse{} + err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/certificate/%d", certId), req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/client.go b/internal/pkg/sdk3rd/lecdn/v3/client/client.go new file mode 100644 index 00000000..218499fa --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/client.go @@ -0,0 +1,98 @@ +package client + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + username string + password string + + accessToken string + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, username, password string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + username: username, + password: password, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.username, c.password) + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req. + SetHeader("Authorization", "Bearer "+c.accessToken). + SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bearer "+c.accessToken). + SetBody(params) + } + + resp, err := req.Execute(method, c.apiHost+"/prod-api"+path) + if err != nil { + return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("lecdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("lecdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/models.go b/internal/pkg/sdk3rd/lecdn/v3/client/models.go new file mode 100644 index 00000000..a4fecf1c --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/client/models.go @@ -0,0 +1,46 @@ +package client + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"msg"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type loginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type loginResponse struct { + baseResponse + Data *struct { + UserId int64 `json:"user_id"` + Username string `json:"username"` + Token string `json:"token"` + } `json:"data,omitempty"` +} + +type UpdateCertificateRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + SSLPEM string `json:"ssl_pem"` + SSLKey string `json:"ssl_key"` + AutoRenewal bool `json:"auto_renewal"` +} + +type UpdateCertificateResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/api.go b/internal/pkg/sdk3rd/lecdn/v3/master/api.go new file mode 100644 index 00000000..00f24a70 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/api.go @@ -0,0 +1,49 @@ +package master + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func (c *Client) ensureAccessTokenExists() error { + c.accessTokenMtx.Lock() + defer c.accessTokenMtx.Unlock() + if c.accessToken != "" { + return nil + } + + req := &loginRequest{ + Username: c.username, + Password: c.password, + } + res, err := c.sendRequest(http.MethodPost, "/auth/login", req) + if err != nil { + return err + } + + resp := &loginResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("lecdn get token failed: code='%d', message='%s'", resp.GetCode(), resp.GetMessage()) + } + + c.accessToken = resp.Data.Token + + return nil +} + +func (c *Client) UpdateCertificate(certId int64, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certId == 0 { + return nil, fmt.Errorf("lecdn api error: invalid parameter: CertId") + } + + if err := c.ensureAccessTokenExists(); err != nil { + return nil, err + } + + resp := &UpdateCertificateResponse{} + err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/certificate/%d", certId), req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/client.go b/internal/pkg/sdk3rd/lecdn/v3/master/client.go new file mode 100644 index 00000000..2f0cae4e --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/client.go @@ -0,0 +1,98 @@ +package master + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + username string + password string + + accessToken string + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, username, password string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + username: username, + password: password, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.username, c.password) + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req. + SetHeader("Authorization", "Bearer "+c.accessToken). + SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bearer "+c.accessToken). + SetBody(params) + } + + resp, err := req.Execute(method, c.apiHost+"/prod-api"+path) + if err != nil { + return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("lecdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("lecdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("lecdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/models.go b/internal/pkg/sdk3rd/lecdn/v3/master/models.go new file mode 100644 index 00000000..2e896f42 --- /dev/null +++ b/internal/pkg/sdk3rd/lecdn/v3/master/models.go @@ -0,0 +1,47 @@ +package master + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type loginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type loginResponse struct { + baseResponse + Data *struct { + UserId int64 `json:"user_id"` + Username string `json:"username"` + Token string `json:"token"` + } `json:"data,omitempty"` +} + +type UpdateCertificateRequest struct { + ClientId int64 `json:"client_id"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + SSLPEM string `json:"ssl_pem"` + SSLKey string `json:"ssl_key"` + AutoRenewal bool `json:"auto_renewal"` +} + +type UpdateCertificateResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/upyun/console/api.go b/internal/pkg/sdk3rd/upyun/console/api.go index 32b4626e..ce62d3a6 100644 --- a/internal/pkg/sdk3rd/upyun/console/api.go +++ b/internal/pkg/sdk3rd/upyun/console/api.go @@ -7,7 +7,11 @@ import ( "net/http" ) -func (c *Client) getCookie() error { +func (c *Client) ensureCookieExists() error { + if c.loginCookie != "" { + return nil + } + req := &signinRequest{Username: c.username, Password: c.password} res, err := c.sendRequest(http.MethodPost, "/accounts/signin/", req) if err != nil { @@ -27,10 +31,8 @@ func (c *Client) getCookie() error { } func (c *Client) UploadHttpsCertificate(req *UploadHttpsCertificateRequest) (*UploadHttpsCertificateResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &UploadHttpsCertificateResponse{} @@ -39,10 +41,8 @@ func (c *Client) UploadHttpsCertificate(req *UploadHttpsCertificateRequest) (*Up } func (c *Client) GetHttpsCertificateManager(certificateId string) (*GetHttpsCertificateManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } req := &GetHttpsCertificateManagerRequest{CertificateId: certificateId} @@ -52,10 +52,8 @@ func (c *Client) GetHttpsCertificateManager(certificateId string) (*GetHttpsCert } func (c *Client) UpdateHttpsCertificateManager(req *UpdateHttpsCertificateManagerRequest) (*UpdateHttpsCertificateManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &UpdateHttpsCertificateManagerResponse{} @@ -64,10 +62,8 @@ func (c *Client) UpdateHttpsCertificateManager(req *UpdateHttpsCertificateManage } func (c *Client) GetHttpsServiceManager(domain string) (*GetHttpsServiceManagerResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } req := &GetHttpsServiceManagerRequest{Domain: domain} @@ -77,10 +73,8 @@ func (c *Client) GetHttpsServiceManager(domain string) (*GetHttpsServiceManagerR } func (c *Client) MigrateHttpsDomain(req *MigrateHttpsDomainRequest) (*MigrateHttpsDomainResponse, error) { - if c.loginCookie == "" { - if err := c.getCookie(); err != nil { - return nil, err - } + if err := c.ensureCookieExists(); err != nil { + return nil, err } resp := &MigrateHttpsDomainResponse{} diff --git a/internal/pkg/utils/map/getter.go b/internal/pkg/utils/map/getter.go index f30f6d33..512da3ee 100644 --- a/internal/pkg/utils/map/getter.go +++ b/internal/pkg/utils/map/getter.go @@ -68,31 +68,42 @@ func GetOrDefaultInt32(dict map[string]any, key string, defaultValue int32) int3 } if value, ok := dict[key]; ok { - if result, ok := value.(int32); ok { - if result != 0 { - return result + var result int32 + + switch v := value.(type) { + case int: + result = int32(v) + case int8: + result = int32(v) + case int16: + result = int32(v) + case int32: + result = v + case int64: + result = int32(v) + case uint: + result = int32(v) + case uint8: + result = int32(v) + case uint16: + result = int32(v) + case uint32: + result = int32(v) + case uint64: + result = int32(v) + case float32: + result = int32(v) + case float64: + result = int32(v) + case string: + // 兼容字符串类型的值 + if t, err := strconv.ParseInt(v, 10, 32); err == nil { + result = int32(t) } } - if result, ok := value.(int64); ok { - if result != 0 { - return int32(result) - } - } - - if result, ok := value.(int); ok { - if result != 0 { - return int32(result) - } - } - - // 兼容字符串类型的值 - if str, ok := value.(string); ok { - if result, err := strconv.ParseInt(str, 10, 32); err == nil { - if result != 0 { - return int32(result) - } - } + if result != 0 { + return int32(result) } } @@ -126,31 +137,42 @@ func GetOrDefaultInt64(dict map[string]any, key string, defaultValue int64) int6 } if value, ok := dict[key]; ok { - if result, ok := value.(int64); ok { - if result != 0 { - return result + var result int64 + + switch v := value.(type) { + case int: + result = int64(v) + case int8: + result = int64(v) + case int16: + result = int64(v) + case int32: + result = int64(v) + case int64: + result = v + case uint: + result = int64(v) + case uint8: + result = int64(v) + case uint16: + result = int64(v) + case uint32: + result = int64(v) + case uint64: + result = int64(v) + case float32: + result = int64(v) + case float64: + result = int64(v) + case string: + // 兼容字符串类型的值 + if t, err := strconv.ParseInt(v, 10, 32); err == nil { + result = t } } - if result, ok := value.(int32); ok { - if result != 0 { - return int64(result) - } - } - - if result, ok := value.(int); ok { - if result != 0 { - return int64(result) - } - } - - // 兼容字符串类型的值 - if str, ok := value.(string); ok { - if result, err := strconv.ParseInt(str, 10, 64); err == nil { - if result != 0 { - return result - } - } + if result != 0 { + return int64(result) } } diff --git a/ui/public/imgs/providers/lecdn.svg b/ui/public/imgs/providers/lecdn.svg new file mode 100644 index 00000000..f9c18fa7 --- /dev/null +++ b/ui/public/imgs/providers/lecdn.svg @@ -0,0 +1 @@ +LeCDN \ No newline at end of file diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 7f3d8e8e..5bc6cab0 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -44,6 +44,7 @@ import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLarkBotConfig from "./AccessFormLarkBotConfig"; +import AccessFormLeCDNConfig from "./AccessFormLeCDNConfig"; import AccessFormMattermostConfig from "./AccessFormMattermostConfig"; import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; @@ -243,6 +244,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.LARKBOT: return ; + case ACCESS_PROVIDERS.LECDN: + return ; case ACCESS_PROVIDERS.MATTERMOST: return ; case ACCESS_PROVIDERS.NAMECHEAP: diff --git a/ui/src/components/access/AccessFormLeCDNConfig.tsx b/ui/src/components/access/AccessFormLeCDNConfig.tsx new file mode 100644 index 00000000..4af5a639 --- /dev/null +++ b/ui/src/components/access/AccessFormLeCDNConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Radio, Select, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForLeCDN } from "@/domain/access"; + +type AccessFormLeCDNConfigFieldValues = Nullish; + +export type AccessFormLeCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormLeCDNConfigFieldValues; + onValuesChange?: (values: AccessFormLeCDNConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormLeCDNConfigFieldValues => { + return { + apiUrl: "http://:5090/", + apiVersion: "v3", + apiRole: "user", + username: "", + password: "", + }; +}; + +const AccessFormLeCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormLeCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + role: z.union([z.literal("client"), z.literal("master")], { + message: t("access.form.lecdn_api_role.placeholder"), + }), + username: z.string().nonempty(t("access.form.lecdn_username.placeholder")).trim(), + password: z.string().nonempty(t("access.form.lecdn_password.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + + + + + + + + + + +
+ ); +}; + +export default AccessFormLeCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index ff2f96b3..3bc1774d 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -58,6 +58,7 @@ import DeployNodeConfigFormJDCloudCDNConfig from "./DeployNodeConfigFormJDCloudC import DeployNodeConfigFormJDCloudLiveConfig from "./DeployNodeConfigFormJDCloudLiveConfig"; import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudVODConfig"; import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig"; +import DeployNodeConfigFormLeCDNConfig from "./DeployNodeConfigFormLeCDNConfig"; import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig"; import DeployNodeConfigFormNetlifySiteConfig from "./DeployNodeConfigFormNetlifySiteConfig"; import DeployNodeConfigFormProxmoxVEConfig from "./DeployNodeConfigFormProxmoxVEConfig"; @@ -266,6 +267,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET: return ; + case DEPLOYMENT_PROVIDERS.LECDN: + return ; case DEPLOYMENT_PROVIDERS.LOCAL: return ; case DEPLOYMENT_PROVIDERS.NETLIFY_SITE: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx new file mode 100644 index 00000000..0636cedf --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLeCDNConfig.tsx @@ -0,0 +1,103 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormLeCDNConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; + clientId?: string | number; +}>; + +export type DeployNodeConfigFormLeCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormLeCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormLeCDNConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormLeCDNConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + clientId: "", + }; +}; + +const DeployNodeConfigFormLeCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormLeCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.lecdn_resource_type.placeholder"), + }), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.lecdn_certificate_id.placeholder")), + clientId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + if (v == null || v === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.lecdn_client_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormLeCDNConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 48882eb7..4326aa75 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -39,6 +39,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForJDCloud | AccessConfigForKubernetes | AccessConfigForLarkBot + | AccessConfigForLeCDN | AccessConfigForMattermost | AccessConfigForNamecheap | AccessConfigForNameDotCom @@ -248,6 +249,15 @@ export type AccessConfigForLarkBot = { webhookUrl: string; }; +export type AccessConfigForLeCDN = { + apiUrl: string; + apiVersion: string; + apiRole: string; + username: string; + password: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForMattermost = { serverUrl: string; username: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index a2c432ce..4111f054 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -38,6 +38,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ JDCLOUD: "jdcloud", KUBERNETES: "k8s", LARKBOT: "larkbot", + LECDN: "lecdn", LETSENCRYPT: "letsencrypt", LETSENCRYPTSTAGING: "letsencryptstaging", LOCAL: "local", @@ -129,6 +130,7 @@ export const accessProvidersMap: Maphttps://kb.cachefly.com/kb/guide/en/generating-tokens-and-keys-Oll9Irt5TI/Steps/2460228", - "access.form.cdnfly_api_url.label": "Cdnfly API URL", - "access.form.cdnfly_api_url.placeholder": "Please enter Cdnfly API URL", + "access.form.cdnfly_api_url.label": "Cdnfly URL", + "access.form.cdnfly_api_url.placeholder": "Please enter Cdnfly URL", "access.form.cdnfly_api_key.label": "Cdnfly user API key", "access.form.cdnfly_api_key.placeholder": "Please enter Cdnfly user API key", "access.form.cdnfly_api_key.tooltip": "For more information, see https://doc.cdnfly.cn/shiyongjieshao.html", @@ -193,8 +193,8 @@ "access.form.email_default_sender_address.placeholder": "Please enter default sender email address", "access.form.email_default_receiver_address.label": "Default receiver email address (Optional)", "access.form.email_default_receiver_address.placeholder": "Please enter default receiver email address", - "access.form.flexcdn_api_url.label": "FlexCDN API URL", - "access.form.flexcdn_api_url.placeholder": "Please enter FlexCDN API URL", + "access.form.flexcdn_api_url.label": "FlexCDN URL", + "access.form.flexcdn_api_url.placeholder": "Please enter FlexCDN URL", "access.form.flexcdn_api_role.label": "FlexCDN user role", "access.form.flexcdn_api_role.placeholder": "Please select FlexCDN user role", "access.form.flexcdn_api_role.option.user.label": "Platform user", @@ -223,8 +223,8 @@ "access.form.godaddy_api_secret.label": "GoDaddy API secret", "access.form.godaddy_api_secret.placeholder": "Please enter GoDaddy API secret", "access.form.godaddy_api_secret.tooltip": "For more information, see https://developer.godaddy.com/", - "access.form.goedge_api_url.label": "GoEdge API URL", - "access.form.goedge_api_url.placeholder": "Please enter GoEdge API URL", + "access.form.goedge_api_url.label": "GoEdge URL", + "access.form.goedge_api_url.placeholder": "Please enter GoEdge URL", "access.form.goedge_api_role.label": "GoEdge user role", "access.form.goedge_api_role.placeholder": "Please select GoEdge user role", "access.form.goedge_api_role.option.user.label": "Platform user", @@ -262,6 +262,21 @@ "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", "access.form.larkbot_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", + "access.form.lecdn_api_url.label": "LeCDN URL", + "access.form.lecdn_api_url.placeholder": "Please enter LeCDN URL", + "access.form.lecdn_api_version.label": "LeCDN version", + "access.form.lecdn_api_version.placeholder": "Please select LeCDN version", + "access.form.lecdn_api_role.label": "LeCDN user role", + "access.form.lecdn_api_role.placeholder": "Please select LeCDN user role", + "access.form.lecdn_api_role.option.client.label": "Client", + "access.form.lecdn_api_role.option.master.label": "Master", + "access.form.lecdn_username.label": "LeCDN username", + "access.form.lecdn_username.placeholder": "Please enter LeCDN username", + "access.form.lecdn_password.label": "LeCDN password", + "access.form.lecdn_password.placeholder": "Please enter GoEdge password", + "access.form.lecdn_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.lecdn_allow_insecure_conns.switch.on": "Allow", + "access.form.lecdn_allow_insecure_conns.switch.off": "Disallow", "access.form.mattermost_server_url.label": "Mattermost server URL", "access.form.mattermost_server_url.placeholder": "Please enter Mattermost server URL", "access.form.mattermost_username.label": "Mattermost username", @@ -307,8 +322,8 @@ "access.form.porkbun_secret_api_key.label": "Porkbun secret API key", "access.form.porkbun_secret_api_key.placeholder": "Please enter Porkbun secret API key", "access.form.porkbun_secret_api_key.tooltip": "For more information, see https://porkbun.com/api/json/v3/documentation", - "access.form.powerdns_api_url.label": "PowerDNS API URL", - "access.form.powerdns_api_url.placeholder": "Please enter PowerDNS API URL", + "access.form.powerdns_api_url.label": "PowerDNS URL", + "access.form.powerdns_api_url.placeholder": "Please enter PowerDNS URL", "access.form.powerdns_api_key.label": "PowerDNS API key", "access.form.powerdns_api_key.placeholder": "Please enter PowerDNS API key", "access.form.powerdns_api_key.tooltip": "For more information, see https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 07f727b4..48906a74 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -86,6 +86,7 @@ "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "Lark Bot", + "provider.lecdn": "LeCDN", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt Staging Environment", "provider.local": "Local deployment", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index ec9a2082..2e3fee98 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -465,6 +465,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.lecdn_resource_type.label": "Resource type", + "workflow_node.deploy.form.lecdn_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN certificate ID", + "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "Please enter LeCDN certificate ID", + "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "You can find it on LeCDN WebUI.", + "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN user ID (Optional)", + "workflow_node.deploy.form.lecdn_client_id.placeholder": "Please enter LeCDN user ID", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "You can find it on LeCDN WebUI.

Required when using administrator's authorization. It Must be the same as the user to which the certificate belongs.", "workflow_node.deploy.form.local.guide": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", "workflow_node.deploy.form.local_format.label": "File format", "workflow_node.deploy.form.local_format.placeholder": "Please select file format", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 649d44f5..a3d059e1 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -115,8 +115,8 @@ "access.form.cachefly_api_token.label": "CacheFly API Token", "access.form.cachefly_api_token.placeholder": "请输入 CacheFly API Token", "access.form.cachefly_api_token.tooltip": "这是什么?请参阅 https://kb.cachefly.com/kb/guide/en/generating-tokens-and-keys-Oll9Irt5TI/Steps/2460228", - "access.form.cdnfly_api_url.label": "Cdnfly API URL", - "access.form.cdnfly_api_url.placeholder": "请输入 Cdnfly API URL", + "access.form.cdnfly_api_url.label": "Cdnfly URL", + "access.form.cdnfly_api_url.placeholder": "请输入 Cdnfly URL", "access.form.cdnfly_api_key.label": "Cdnfly 用户端 API Key", "access.form.cdnfly_api_key.placeholder": "请输入 Cdnfly 用户端 API Key", "access.form.cdnfly_api_key.tooltip": "这是什么?请参阅 https://doc.cdnfly.cn/shiyongjieshao.html", @@ -187,8 +187,8 @@ "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", - "access.form.flexcdn_api_url.label": "FlexCDN API URL", - "access.form.flexcdn_api_url.placeholder": "请输入 FlexCDN API URL", + "access.form.flexcdn_api_url.label": "FlexCDN URL", + "access.form.flexcdn_api_url.placeholder": "请输入 FlexCDN URL", "access.form.flexcdn_api_role.label": "FlexCDN 用户角色", "access.form.flexcdn_api_role.placeholder": "请选择 FlexCDN 用户角色", "access.form.flexcdn_api_role.option.user.label": "平台用户", @@ -217,8 +217,8 @@ "access.form.godaddy_api_secret.label": "GoDaddy API Secret", "access.form.godaddy_api_secret.placeholder": "请输入 GoDaddy API Secret", "access.form.godaddy_api_secret.tooltip": "这是什么?请参阅 https://developer.godaddy.com/", - "access.form.goedge_api_url.label": "GoEdge API URL", - "access.form.goedge_api_url.placeholder": "请输入 GoEdge API URL", + "access.form.goedge_api_url.label": "GoEdge URL", + "access.form.goedge_api_url.placeholder": "请输入 GoEdge URL", "access.form.goedge_api_role.label": "GoEdge 用户角色", "access.form.goedge_api_role.placeholder": "请选择 GoEdge 用户角色", "access.form.goedge_api_role.option.user.label": "平台用户", @@ -256,6 +256,21 @@ "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", + "access.form.lecdn_api_url.label": "LeCDN URL", + "access.form.lecdn_api_url.placeholder": "请输入 LeCDN URL", + "access.form.lecdn_api_version.label": "LeCDN 版本", + "access.form.lecdn_api_version.placeholder": "请选择 LeCDN 版本", + "access.form.lecdn_api_role.label": "LeCDN 用户角色", + "access.form.lecdn_api_role.placeholder": "请选择 LeCDN 用户角色", + "access.form.lecdn_api_role.option.client.label": "客户用户", + "access.form.lecdn_api_role.option.master.label": "主控管理员", + "access.form.lecdn_username.label": "LeCDN 用户名", + "access.form.lecdn_username.placeholder": "请输入 LeCDN 用户名", + "access.form.lecdn_password.label": "LeCDN 用户密码", + "access.form.lecdn_password.placeholder": "请输入 LeCDN 用户密码", + "access.form.lecdn_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.lecdn_allow_insecure_conns.switch.on": "允许", + "access.form.lecdn_allow_insecure_conns.switch.off": "不允许", "access.form.mattermost_server_url.label": "Mattermost 服务地址", "access.form.mattermost_server_url.placeholder": "请输入 Mattermost 服务地址", "access.form.mattermost_username.label": "Mattermost 用户名", @@ -301,8 +316,8 @@ "access.form.porkbun_secret_api_key.label": "Porkbun Secret API Key", "access.form.porkbun_secret_api_key.placeholder": "请输入 Porkbun Secret API Key", "access.form.porkbun_secret_api_key.tooltip": "这是什么?请参阅 https://porkbun.com/api/json/v3/documentation", - "access.form.powerdns_api_url.label": "PowerDNS API URL", - "access.form.powerdns_api_url.placeholder": "请输入 PowerDNS API URL", + "access.form.powerdns_api_url.label": "PowerDNS URL", + "access.form.powerdns_api_url.placeholder": "请输入 PowerDNS URL", "access.form.powerdns_api_key.label": "PowerDNS API Key", "access.form.powerdns_api_key.placeholder": "请输入 PowerDNS API Key", "access.form.powerdns_api_key.tooltip": "这是什么?请参阅 https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 45f3fd53..9d997ca0 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -86,6 +86,7 @@ "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.larkbot": "飞书群机器人", + "provider.lecdn": "LeCDN", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt 测试环境", "provider.local": "本地部署", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index ff2f0522..356c3d64 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -109,18 +109,18 @@ "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.params_config.label": "参数设置", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启宝塔面板服务", - "workflow_node.deploy.form.1panel_site_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.1panel_site_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "替换指定网站的证书", "workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", "workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID", - "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请登录 1Panel 管理面板查看。", + "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请登录 1Panel 面板查看。", "workflow_node.deploy.form.1panel_site_certificate_id.label": "1Panel 证书 ID", "workflow_node.deploy.form.1panel_site_certificate_id.placeholder": "请输入 1Panel 证书 ID", - "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请登录 1Panel 管理面板查看。", - "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请登录 1Panel 面板查看。", + "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.aliyun_alb_region.label": "阿里云 ALB 服务地域", @@ -170,8 +170,8 @@ "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

不填写时,将使用系统联系人列表中的第一个。", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "修改阿里云联系人 ID", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "请输入阿里云联系人 ID", - "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.aliyun_clb_region.label": "阿里云 CLB 服务地域", @@ -212,8 +212,8 @@ "workflow_node.deploy.form.aliyun_fc_domain.label": "阿里云 FC 自定义域名", "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "请输入阿里云 FC 自定义域名(支持泛域名)", "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "这是什么?请参阅 see https://fcnext.console.aliyun.com/", - "workflow_node.deploy.form.aliyun_ga_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_ga_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_ga_resource_type.option.accelerator.label": "替换指定全球加速器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.aliyun_ga_resource_type.option.listener.label": "替换指定全球加速器监听器的证书", "workflow_node.deploy.form.aliyun_ga_accelerator_id.label": "阿里云全球加速实例 ID", @@ -231,8 +231,8 @@ "workflow_node.deploy.form.aliyun_live_domain.label": "阿里云视频直播流域名", "workflow_node.deploy.form.aliyun_live_domain.placeholder": "请输入阿里云视频直播流域名(支持泛域名)", "workflow_node.deploy.form.aliyun_live_domain.tooltip": "这是什么?请参阅 https://live.console.aliyun.com", - "workflow_node.deploy.form.aliyun_nlb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.aliyun_nlb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.aliyun_nlb_region.label": "阿里云 NLB 服务地域", @@ -289,8 +289,8 @@ "workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder": "请输入 Azure KeyVault 证书名称", "workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip": "不填写时,将由 Certimate 自动生成证书名称。", "workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid": "证书名称只能包含字母、数字和连字符(-),长度限制为 1 到 127 个字符", - "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.baiducloud_appblb_region.label": "百度智能云 BLB 服务地域", @@ -305,8 +305,8 @@ "workflow_node.deploy.form.baiducloud_appblb_snidomain.label": "百度智能云 BLB 扩展域名(可选)", "workflow_node.deploy.form.baiducloud_appblb_snidomain.placeholder": "请输入百度智能云 BLB 扩展域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_appblb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/appblb/list

不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", - "workflow_node.deploy.form.baiducloud_blb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.baiducloud_blb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.baiducloud_blb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.baiducloud_blb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.baiducloud_blb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书", "workflow_node.deploy.form.baiducloud_blb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "workflow_node.deploy.form.baiducloud_blb_region.label": "百度智能云 BLB 服务地域", @@ -353,48 +353,48 @@ "workflow_node.deploy.form.byteplus_cdn_domain.label": "BytePlus CDN 域名", "workflow_node.deploy.form.byteplus_cdn_domain.placeholder": "请输入 BytePlus CDN 域名(支持泛域名)", "workflow_node.deploy.form.byteplus_cdn_domain.tooltip": "这是什么?请参阅 https://console.byteplus.com/cdn", - "workflow_node.deploy.form.cdnfly_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.cdnfly_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.cdnfly_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.cdnfly_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.cdnfly_resource_type.option.site.label": "替换指定网站的证书", "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly 网站 ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "请输入 Cdnfly 网站 ID", - "workflow_node.deploy.form.cdnfly_site_id.tooltip": "请登录 Cdnfly 管理平台查看。", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "请登录 Cdnfly 控制台查看。", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly 证书 ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "请输入 Cdnfly 证书 ID", - "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "请登录 Cdnfly 管理平台查看。", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "请登录 Cdnfly 控制台查看。", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "请输入多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.dogecloud.com", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "请输入 Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "这是什么?请参阅 https://edgio.app/", - "workflow_node.deploy.form.flexcdn_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.flexcdn_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN 证书 ID", "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "请输入 FlexCDN 证书 ID", - "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 管理平台查看。", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 控制台查看。", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "请输入 Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", "workflow_node.deploy.form.gcore_cdn_certificate_id.label": "Gcore CDN 原证书 ID(可选)", "workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder": "请输入 Gcore CDN 原证书 ID", "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/ssl

不填写时,将上传新证书;否则,将替换原证书。", - "workflow_node.deploy.form.goedge_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.goedge_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge 证书 ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "请输入 GoEdge 证书 ID", - "workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 管理平台查看。", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 控制台查看。", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "华为云 CDN 服务区域", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云 CDN 服务区域(例如:cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "请输入华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/cdn", - "workflow_node.deploy.form.huaweicloud_elb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.huaweicloud_elb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听器的证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.listener.label": "替换指定监听器的证书", @@ -410,8 +410,8 @@ "workflow_node.deploy.form.huaweicloud_elb_listener_id.label": "华为云 ELB 监听器 ID", "workflow_node.deploy.form.huaweicloud_elb_listener_id.placeholder": "请输入华为云 ELB 监听器 ID", "workflow_node.deploy.form.huaweicloud_elb_listener_id.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/vpc/#/elb/list/grid", - "workflow_node.deploy.form.huaweicloud_waf_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.huaweicloud_waf_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.huaweicloud_waf_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.huaweicloud_waf_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.cloudserver.label": "替换指定云模式防护网站的证书", "workflow_node.deploy.form.huaweicloud_waf_resource_type.option.premiumhost.label": "替换指定独享模式防护网站的证书", @@ -424,8 +424,8 @@ "workflow_node.deploy.form.huaweicloud_waf_domain.label": "华为云 WAF 防护域名", "workflow_node.deploy.form.huaweicloud_waf_domain.placeholder": "请输入华为云 WAF 防护域名(支持泛域名)", "workflow_node.deploy.form.huaweicloud_waf_domain.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/console/#/waf/domain/list", - "workflow_node.deploy.form.jdcloud_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.jdcloud_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.jdcloud_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.jdcloud_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.jdcloud_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/TLS 监听的证书", "workflow_node.deploy.form.jdcloud_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", "workflow_node.deploy.form.jdcloud_alb_region_id.label": "京东云 ALB 服务地域 ID", @@ -464,6 +464,15 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.lecdn_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.lecdn_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN 证书 ID", + "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "请输入 LeCDN 证书 ID", + "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "请登录 LeCDN 控制台查看。", + "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN 客户 ID(可选)", + "workflow_node.deploy.form.lecdn_client_id.placeholder": "请输入 LeCDN 客户 ID", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "请登录 LeCDN 控制台查看。

使用的是系统管理员的授权信息时必填,需与证书所属客户相同。", "workflow_node.deploy.form.local.guide": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。", "workflow_node.deploy.form.local_format.label": "文件格式", "workflow_node.deploy.form.local_format.placeholder": "请选择文件格式", @@ -537,8 +546,8 @@ "workflow_node.deploy.form.ratpanel_site_name.label": "耗子面板网站名称", "workflow_node.deploy.form.ratpanel_site_name.placeholder": "请输入耗子面板网站名称", "workflow_node.deploy.form.ratpanel_site_name.tooltip": "请登录耗子面板查看。", - "workflow_node.deploy.form.safeline_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.safeline_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.safeline_certificate_id.label": "雷池证书 ID", "workflow_node.deploy.form.safeline_certificate_id.placeholder": "请输入雷池证书 ID", @@ -590,8 +599,8 @@ "workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名", "workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn", - "workflow_node.deploy.form.tencentcloud_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.tencentcloud_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ssl_deploy.label": "通过 SSL 服务部署到云资源实例", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.loadbalancer.label": "替换指定实例下的全部 HTTPS/TCPSSL/QUIC 监听器的证书", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.listener.label": "替换指定监听器的证书", @@ -687,8 +696,8 @@ "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/file/", - "workflow_node.deploy.form.volcengine_alb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.volcengine_alb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.volcengine_alb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.volcengine_alb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.volcengine_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.volcengine_alb_resource_type.option.listener.label": "替换指定监听器的证书", "workflow_node.deploy.form.volcengine_alb_region.label": "火山引擎 ALB 服务地域", @@ -708,8 +717,8 @@ "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage", "workflow_node.deploy.form.volcengine_certcenter_region.label": "火山引擎证书中心服务地域", "workflow_node.deploy.form.volcengine_certcenter_region.placeholder": "请输入火山引擎证书中心服务地域(例如:cn-beijing)", - "workflow_node.deploy.form.volcengine_clb_resource_type.label": "证书替换方式", - "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.volcengine_clb_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.volcengine_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.volcengine_clb_resource_type.option.listener.label": "替换指定监听器的证书", "workflow_node.deploy.form.volcengine_clb_region.label": "火山引擎 CLB 服务地域", From e6fc92eccbb341e89d5495ebc9b193833c2a755b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 19 May 2025 23:49:06 +0800 Subject: [PATCH 09/13] feat: new deployment provider: wangsu certificate management --- internal/deployer/providers.go | 11 +- internal/domain/provider.go | 2 +- .../providers/wangsu-cdnpro/wangsu_cdnpro.go | 2 +- .../wangsu-certificate/wangsu_certificate.go | 109 +++++++++++++ .../wangsu_certificate_test.go | 75 +++++++++ .../wangsu-certificate/wangsu_certificate.go | 143 ++++++++++++++++++ .../wangsu_certificate_test.go | 72 +++++++++ internal/pkg/sdk3rd/dogecloud/client.go | 4 +- internal/pkg/sdk3rd/lecdn/v3/client/api.go | 1 + internal/pkg/sdk3rd/lecdn/v3/client/models.go | 1 + .../pkg/sdk3rd/wangsu/{cdn => cdnpro}/api.go | 14 +- .../sdk3rd/wangsu/{cdn => cdnpro}/client.go | 2 +- .../sdk3rd/wangsu/{cdn => cdnpro}/models.go | 4 +- internal/pkg/sdk3rd/wangsu/certificate/api.go | 42 +++++ .../pkg/sdk3rd/wangsu/certificate/client.go | 20 +++ .../pkg/sdk3rd/wangsu/certificate/models.go | 52 +++++++ internal/pkg/sdk3rd/wangsu/openapi/client.go | 6 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormAWSACMConfig.tsx | 2 +- ...eployNodeConfigFormAzureKeyVaultConfig.tsx | 2 +- ...DeployNodeConfigFormWangsuCDNProConfig.tsx | 16 +- ...yNodeConfigFormWangsuCertificateConfig.tsx | 61 ++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 3 + ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 3 + 27 files changed, 627 insertions(+), 27 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go create mode 100644 internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go create mode 100644 internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go create mode 100644 internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go rename internal/pkg/sdk3rd/wangsu/{cdn => cdnpro}/api.go (76%) rename internal/pkg/sdk3rd/wangsu/{cdn => cdnpro}/client.go (96%) rename internal/pkg/sdk3rd/wangsu/{cdn => cdnpro}/models.go (98%) create mode 100644 internal/pkg/sdk3rd/wangsu/certificate/api.go create mode 100644 internal/pkg/sdk3rd/wangsu/certificate/client.go create mode 100644 internal/pkg/sdk3rd/wangsu/certificate/models.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 53951300..09cda5fe 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -87,6 +87,7 @@ import ( pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro" + pWangsuCertificate "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate" pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" @@ -1205,7 +1206,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeWangsuCDNPro: + case domain.DeploymentProviderTypeWangsuCDNPro, domain.DeploymentProviderTypeWangsuCertificate: { access := domain.AccessConfigForWangsu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -1225,6 +1226,14 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer }) return deployer, err + case domain.DeploymentProviderTypeWangsuCertificate: + deployer, err := pWangsuCertificate.NewDeployer(&pWangsuCertificate.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + }) + return deployer, err + default: break } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index d8091eb8..c518c3b9 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -245,7 +245,7 @@ const ( DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos") DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") // 网宿 CDN(预留) DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro") - DeploymentProviderTypeWangsuCert = DeploymentProviderType(AccessProviderTypeWangsu + "-cert") // 网宿证书管理(预留) + DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate") DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook) ) diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index ee16b08a..436ea5a5 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -17,7 +17,7 @@ import ( "time" "github.com/usual2970/certimate/internal/pkg/core/deployer" - wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn" + wangsucdn "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdnpro" certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" ) diff --git a/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go new file mode 100644 index 00000000..3f691489 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate.go @@ -0,0 +1,109 @@ +package wangsucertificate + +import ( + "context" + "errors" + "fmt" + "log/slog" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/certificate" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 证书 ID。 + // 选填。零值时表示新建证书;否则表示更新证书。 + CertificateId string `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *wangsusdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + if d.config.CertificateId == "" { + // 上传证书到证书管理 + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + } else { + // 修改证书 + // REF: https://www.wangsu.com/document/api-doc/25568?productCode=certificatemanagement + updateCertificateReq := &wangsusdk.UpdateCertificateRequest{ + Name: typeutil.ToPtr(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), + Certificate: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + Comment: typeutil.ToPtr("upload from certimate"), + } + updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq) + d.logger.Debug("sdk request 'certificatemanagement.UpdateCertificate'", slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err) + } + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go new file mode 100644 index 00000000..a6805ec9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-certificate/wangsu_certificate_test.go @@ -0,0 +1,75 @@ +package wangsucertificate_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fCertificateId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_certificate_test.go -args \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_CERTIFICATEID="your-certificate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go new file mode 100644 index 00000000..b512be09 --- /dev/null +++ b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate.go @@ -0,0 +1,143 @@ +package jdcloudssl + +import ( + "context" + "errors" + "fmt" + "log/slog" + "regexp" + "strings" + "time" + + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/certificate" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type UploaderConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *wangsusdk.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &UploaderProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader { + if logger == nil { + u.logger = slog.Default() + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (res *uploader.UploadResult, err error) { + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + // 查询证书列表,避免重复上传 + // REF: https://www.wangsu.com/document/api-doc/26426 + listCertificatesResp, err := u.sdkClient.ListCertificates() + u.logger.Debug("sdk request 'certificatemanagement.ListCertificates'", slog.Any("response", listCertificatesResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.ListCertificates': %w", err) + } + + if listCertificatesResp.Certificates != nil { + for _, certificate := range listCertificatesResp.Certificates { + // 对比证书序列号 + if !strings.EqualFold(certX509.SerialNumber.Text(16), certificate.Serial) { + continue + } + + // 再对比证书有效期 + cstzone := time.FixedZone("CST", 8*60*60) + oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certificate.ValidityFrom, cstzone) + oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certificate.ValidityTo, cstzone) + if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + u.logger.Info("ssl certificate already exists") + return &uploader.UploadResult{ + CertId: certificate.CertificateId, + CertName: certificate.Name, + }, nil + } + } + + // 生成新证书名(需符合网宿云命名规则) + var certId string + certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli()) + + // 新增证书 + // REF: https://www.wangsu.com/document/api-doc/25199?productCode=certificatemanagement + createCertificateReq := &wangsusdk.CreateCertificateRequest{ + Name: typeutil.ToPtr(certName), + Certificate: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + Comment: typeutil.ToPtr("upload from certimate"), + } + createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq) + u.logger.Debug("sdk request 'certificatemanagement.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err) + } + + // 网宿云证书 URL 中包含证书 ID + // 格式: + // https://open.chinanetcenter.com/api/certificate/100001 + wangsuCertIdMatches := regexp.MustCompile(`/certificate/([0-9]+)`).FindStringSubmatch(createCertificateResp.CertificateUrl) + if len(wangsuCertIdMatches) > 1 { + certId = wangsuCertIdMatches[1] + } else { + return nil, fmt.Errorf("received empty certificate id") + } + + return &uploader.UploadResult{ + CertId: certId, + CertName: certName, + }, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go new file mode 100644 index 00000000..bdec8cfe --- /dev/null +++ b/internal/pkg/core/uploader/providers/wangsu-certificate/wangsu_certificate_test.go @@ -0,0 +1,72 @@ +package jdcloudssl_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_JDCLOUDSSL_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_certificate_test.go -args \ + --CERTIMATE_UPLOADER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCERTIFICATE_ACCESSKEYSECRET="your-access-key-secret" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + sres, _ := json.Marshal(res) + t.Logf("ok: %s", string(sres)) + }) +} diff --git a/internal/pkg/sdk3rd/dogecloud/client.go b/internal/pkg/sdk3rd/dogecloud/client.go index 46f3513d..806b0ea9 100644 --- a/internal/pkg/sdk3rd/dogecloud/client.go +++ b/internal/pkg/sdk3rd/dogecloud/client.go @@ -174,10 +174,10 @@ func (c *Client) sendReq(method string, path string, data map[string]interface{} } defer resp.Body.Close() - r, err := io.ReadAll(resp.Body) + bytes, err := io.ReadAll(resp.Body) if err != nil { return nil, err } - return r, nil + return bytes, nil } diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/api.go b/internal/pkg/sdk3rd/lecdn/v3/client/api.go index ffdc70e3..89f9cdc0 100644 --- a/internal/pkg/sdk3rd/lecdn/v3/client/api.go +++ b/internal/pkg/sdk3rd/lecdn/v3/client/api.go @@ -14,6 +14,7 @@ func (c *Client) ensureAccessTokenExists() error { } req := &loginRequest{ + Email: c.username, Username: c.username, Password: c.password, } diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/models.go b/internal/pkg/sdk3rd/lecdn/v3/client/models.go index a4fecf1c..6d63ea79 100644 --- a/internal/pkg/sdk3rd/lecdn/v3/client/models.go +++ b/internal/pkg/sdk3rd/lecdn/v3/client/models.go @@ -19,6 +19,7 @@ func (r *baseResponse) GetMessage() string { } type loginRequest struct { + Email string `json:"email"` Username string `json:"username"` Password string `json:"password"` } diff --git a/internal/pkg/sdk3rd/wangsu/cdn/api.go b/internal/pkg/sdk3rd/wangsu/cdnpro/api.go similarity index 76% rename from internal/pkg/sdk3rd/wangsu/cdn/api.go rename to internal/pkg/sdk3rd/wangsu/cdnpro/api.go index 0da647c8..c6f8da04 100644 --- a/internal/pkg/sdk3rd/wangsu/cdn/api.go +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/api.go @@ -1,4 +1,4 @@ -package cdn +package cdnpro import ( "fmt" @@ -10,14 +10,14 @@ import ( func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { resp := &CreateCertificateResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) { + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) { r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) }) if err != nil { return resp, err } - resp.CertificateUrl = r.Header().Get("Location") + resp.CertificateUrl = rres.Header().Get("Location") return resp, err } @@ -27,14 +27,14 @@ func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateR } resp := &UpdateCertificateResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) { + rres, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) { r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp)) }) if err != nil { return resp, err } - resp.CertificateUrl = r.Header().Get("Location") + resp.CertificateUrl = rres.Header().Get("Location") return resp, err } @@ -50,12 +50,12 @@ func (c *Client) GetHostnameDetail(hostname string) (*GetHostnameDetailResponse, func (c *Client) CreateDeploymentTask(req *CreateDeploymentTaskRequest) (*CreateDeploymentTaskResponse, error) { resp := &CreateDeploymentTaskResponse{} - r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp) + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp) if err != nil { return resp, err } - resp.DeploymentTaskUrl = r.Header().Get("Location") + resp.DeploymentTaskUrl = rres.Header().Get("Location") return resp, err } diff --git a/internal/pkg/sdk3rd/wangsu/cdn/client.go b/internal/pkg/sdk3rd/wangsu/cdnpro/client.go similarity index 96% rename from internal/pkg/sdk3rd/wangsu/cdn/client.go rename to internal/pkg/sdk3rd/wangsu/cdnpro/client.go index ac53e171..b5c0f530 100644 --- a/internal/pkg/sdk3rd/wangsu/cdn/client.go +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/client.go @@ -1,4 +1,4 @@ -package cdn +package cdnpro import ( "time" diff --git a/internal/pkg/sdk3rd/wangsu/cdn/models.go b/internal/pkg/sdk3rd/wangsu/cdnpro/models.go similarity index 98% rename from internal/pkg/sdk3rd/wangsu/cdn/models.go rename to internal/pkg/sdk3rd/wangsu/cdnpro/models.go index a9a9ec74..9cb1e648 100644 --- a/internal/pkg/sdk3rd/wangsu/cdn/models.go +++ b/internal/pkg/sdk3rd/wangsu/cdnpro/models.go @@ -1,11 +1,11 @@ -package cdn +package cdnpro import ( "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" ) type baseResponse struct { - RequestId *string `json:"-"` + RequestId *string `json:"requestId,omitempty"` Code *string `json:"code,omitempty"` Message *string `json:"message,omitempty"` } diff --git a/internal/pkg/sdk3rd/wangsu/certificate/api.go b/internal/pkg/sdk3rd/wangsu/certificate/api.go new file mode 100644 index 00000000..037fb6e7 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/api.go @@ -0,0 +1,42 @@ +package certificate + +import ( + "fmt" + "net/http" + "net/url" +) + +func (c *Client) ListCertificates() (*ListCertificatesResponse, error) { + resp := &ListCertificatesResponse{} + _, err := c.client.SendRequestWithResult(http.MethodGet, "/api/certificate", nil, resp) + if err != nil { + return resp, err + } + + return resp, err +} + +func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + resp := &CreateCertificateResponse{} + rres, err := c.client.SendRequestWithResult(http.MethodPost, "/api/certificate", req, resp) + if err != nil { + return resp, err + } + + resp.CertificateUrl = rres.Header().Get("Location") + return resp, err +} + +func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { + if certificateId == "" { + return nil, fmt.Errorf("wangsu api error: invalid parameter: certificateId") + } + + resp := &UpdateCertificateResponse{} + _, err := c.client.SendRequestWithResult(http.MethodPut, fmt.Sprintf("/api/certificate/%s", url.PathEscape(certificateId)), req, resp) + if err != nil { + return resp, err + } + + return resp, err +} diff --git a/internal/pkg/sdk3rd/wangsu/certificate/client.go b/internal/pkg/sdk3rd/wangsu/certificate/client.go new file mode 100644 index 00000000..19f4cfaa --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/client.go @@ -0,0 +1,20 @@ +package certificate + +import ( + "time" + + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type Client struct { + client *openapi.Client +} + +func NewClient(accessKey, secretKey string) *Client { + return &Client{client: openapi.NewClient(accessKey, secretKey)} +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.WithTimeout(timeout) + return c +} diff --git a/internal/pkg/sdk3rd/wangsu/certificate/models.go b/internal/pkg/sdk3rd/wangsu/certificate/models.go new file mode 100644 index 00000000..4e882e7c --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/certificate/models.go @@ -0,0 +1,52 @@ +package certificate + +import ( + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type baseResponse struct { + RequestId *string `json:"requestId,omitempty"` + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +var _ openapi.Result = (*baseResponse)(nil) + +func (r *baseResponse) SetRequestId(requestId string) { + r.RequestId = &requestId +} + +type CreateCertificateRequest struct { + Name *string `json:"name,omitempty" required:"true"` + Certificate *string `json:"certificate,omitempty" required:"true"` + PrivateKey *string `json:"privateKey,omitempty"` + Comment *string `json:"comment,omitempty" ` +} + +type CreateCertificateResponse struct { + baseResponse + CertificateUrl string `json:"location,omitempty"` +} + +type UpdateCertificateRequest struct { + Name *string `json:"name,omitempty" required:"true"` + Certificate *string `json:"certificate,omitempty"` + PrivateKey *string `json:"privateKey,omitempty"` + Comment *string `json:"comment,omitempty" ` +} + +type UpdateCertificateResponse struct { + baseResponse +} + +type ListCertificatesResponse struct { + baseResponse + Certificates []*struct { + CertificateId string `json:"certificate-id"` + Name string `json:"name"` + Comment string `json:"comment"` + ValidityFrom string `json:"certificate-validity-from"` + ValidityTo string `json:"certificate-validity-to"` + Serial string `json:"certificate-serial"` + } `json:"ssl-certificates,omitempty"` +} diff --git a/internal/pkg/sdk3rd/wangsu/openapi/client.go b/internal/pkg/sdk3rd/wangsu/openapi/client.go index 95d17bb0..0bb141d8 100644 --- a/internal/pkg/sdk3rd/wangsu/openapi/client.go +++ b/internal/pkg/sdk3rd/wangsu/openapi/client.go @@ -154,8 +154,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}, con req = req.SetBody(params) } - for _, fn := range configureReq { - fn(req) + if configureReq != nil { + for _, fn := range configureReq { + fn(req) + } } resp, err := req.Send() diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 3bc1774d..a46ae1be 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -92,6 +92,7 @@ import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVo import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx"; import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx"; import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx"; +import DeployNodeConfigFormWangsuCertificateConfig from "./DeployNodeConfigFormWangsuCertificateConfig.tsx"; import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx"; type DeployNodeConfigFormFieldValues = Partial; @@ -335,6 +336,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO: return ; + case DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE: + return ; case DEPLOYMENT_PROVIDERS.WEBHOOK: return ; } diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx index f007bf87..2e539453 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx @@ -28,7 +28,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled, .string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") }) .nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder")) .trim(), - certificateArn: z.string({ message: t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder") }).nullish(), + certificateArn: z.string().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx index 2a54bb99..bd2347df 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAzureKeyVaultConfig.tsx @@ -35,7 +35,7 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({ .nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")) .trim(), certificateName: z - .string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") }) + .string() .nullish() .refine((v) => { if (!v) return true; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx index a86b34c0..e89e1e8d 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNProConfig.tsx @@ -5,37 +5,37 @@ import { z } from "zod"; import { validDomainName } from "@/utils/validators"; -type DeployNodeConfigFormBaishanCDNConfigFieldValues = Nullish<{ +type DeployNodeConfigFormWangsuCDNProConfigFieldValues = Nullish<{ environment: string; domain: string; certificateId?: string; webhookId?: string; }>; -export type DeployNodeConfigFormBaishanCDNConfigProps = { +export type DeployNodeConfigFormWangsuCDNProConfigProps = { form: FormInstance; formName: string; disabled?: boolean; - initialValues?: DeployNodeConfigFormBaishanCDNConfigFieldValues; - onValuesChange?: (values: DeployNodeConfigFormBaishanCDNConfigFieldValues) => void; + initialValues?: DeployNodeConfigFormWangsuCDNProConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCDNProConfigFieldValues) => void; }; const ENVIRONMENT_PRODUCTION = "production" as const; const ENVIRONMENT_STAGING = "stating" as const; -const initFormModel = (): DeployNodeConfigFormBaishanCDNConfigFieldValues => { +const initFormModel = (): DeployNodeConfigFormWangsuCDNProConfigFieldValues => { return { environment: ENVIRONMENT_PRODUCTION, }; }; -const DeployNodeConfigFormBaishanCDNConfig = ({ +const DeployNodeConfigFormWangsuCDNProConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange, -}: DeployNodeConfigFormBaishanCDNConfigProps) => { +}: DeployNodeConfigFormWangsuCDNProConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -104,4 +104,4 @@ const DeployNodeConfigFormBaishanCDNConfig = ({ ); }; -export default DeployNodeConfigFormBaishanCDNConfig; +export default DeployNodeConfigFormWangsuCDNProConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx new file mode 100644 index 00000000..739e4b28 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCertificateConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormWangsuCertificateConfigFieldValues = Nullish<{ + certificateId?: string; +}>; + +export type DeployNodeConfigFormWangsuCertificateConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormWangsuCertificateConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCertificateConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormWangsuCertificateConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormWangsuCertificateConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormWangsuCertificateConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + certificateId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormWangsuCertificateConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 4111f054..e679d13b 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -425,6 +425,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`, VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`, WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`, + WANGSU_CERTIFICATE: `${ACCESS_PROVIDERS.WANGSU}-certificate`, WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, } as const); @@ -520,6 +521,7 @@ export const deploymentProvidersMap: Maphttps://cdnpro.console.wangsu.com/v2/index/#/certificate", + "workflow_node.deploy.form.wangsu_certificate_id.label": "Wangsu Cloud certificate ID (Optional)", + "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "Please enter Wangsu Cloud certificate ID", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index#/certificate/list?code=cert_mylist&parentCode=cert_ssl&productCode=certificatemanagement", "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 9d997ca0..8c1be41a 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -146,6 +146,7 @@ "provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "provider.wangsu": "网宿云", "provider.wangsu.cdnpro": "网宿云 - CDN Pro", + "provider.wangsu.certificate_upload": "网宿云 - 上传到证书管理", "provider.webhook": "Webhook", "provider.wecombot": "企业微信群机器人", "provider.westcn": "西部数码", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 356c3d64..ada4ec22 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -767,6 +767,9 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "网宿云 CDN Pro 部署任务 Webhook ID(可选)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "请输入网宿云 CDN Pro 部署任务 Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", + "workflow_node.deploy.form.wangsu_certificate_id.label": "网宿云证书 ID(可选)", + "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "请输入网宿云证书 ID", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list?code=cert_mylist&parentCode=cert_ssl&productCode=certificatemanagement

不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", From 7a663d31cb328ecd447c7d47ed7b4e4372f93ce0 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 May 2025 11:32:57 +0800 Subject: [PATCH 10/13] feat: new deployment provider: wangsu cdn --- internal/deployer/providers.go | 11 +- internal/domain/provider.go | 2 +- .../providers/wangsu-cdn/wangsu_cdn.go | 104 +++++++++++++ .../providers/wangsu-cdn/wangsu_cdn_test.go | 75 +++++++++ .../providers/wangsu-cdnpro/wangsu_cdnpro.go | 20 +-- internal/pkg/sdk3rd/wangsu/cdn/api.go | 15 ++ internal/pkg/sdk3rd/wangsu/cdn/client.go | 20 +++ internal/pkg/sdk3rd/wangsu/cdn/models.go | 26 ++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormWangsuCDNConfig.tsx | 146 ++++++++++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.provider.json | 3 +- .../i18n/locales/en/nls.workflow.nodes.json | 7 +- ui/src/i18n/locales/zh/nls.provider.json | 3 +- .../i18n/locales/zh/nls.workflow.nodes.json | 7 +- 15 files changed, 428 insertions(+), 16 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go create mode 100644 internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go create mode 100644 internal/pkg/sdk3rd/wangsu/cdn/api.go create mode 100644 internal/pkg/sdk3rd/wangsu/cdn/client.go create mode 100644 internal/pkg/sdk3rd/wangsu/cdn/models.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 09cda5fe..3f0b2600 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -86,6 +86,7 @@ import ( pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex" pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" + pWangsuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdn" pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro" pWangsuCertificate "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-certificate" pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" @@ -1206,7 +1207,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeWangsuCDNPro, domain.DeploymentProviderTypeWangsuCertificate: + case domain.DeploymentProviderTypeWangsuCDN, domain.DeploymentProviderTypeWangsuCDNPro, domain.DeploymentProviderTypeWangsuCertificate: { access := domain.AccessConfigForWangsu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -1214,6 +1215,14 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } switch options.Provider { + case domain.DeploymentProviderTypeWangsuCDN: + deployer, err := pWangsuCDN.NewDeployer(&pWangsuCDN.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Domains: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderServiceConfig, "domains"), ";"), func(s string) bool { return s != "" }), + }) + return deployer, err + case domain.DeploymentProviderTypeWangsuCDNPro: deployer, err := pWangsuCDNPro.NewDeployer(&pWangsuCDNPro.DeployerConfig{ AccessKeyId: access.AccessKeyId, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c518c3b9..fc2d4124 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -243,7 +243,7 @@ const ( DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex") DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live") DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos") - DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") // 网宿 CDN(预留) + DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro") DeploymentProviderTypeWangsuCertificate = DeploymentProviderType(AccessProviderTypeWangsu + "-certificate") DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook) diff --git a/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go new file mode 100644 index 00000000..43c65de2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn.go @@ -0,0 +1,104 @@ +package wangsucdn + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strconv" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/wangsu-certificate" + wangsusdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/cdn" +) + +type DeployerConfig struct { + // 网宿云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 网宿云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名数组。 + Domains []string `json:"domains"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *wangsusdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到证书管理 + upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 批量修改域名证书配置 + // REF: https://www.wangsu.com/document/api-doc/37447 + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + batchUpdateCertificateConfigReq := &wangsusdk.BatchUpdateCertificateConfigRequest{ + CertificateId: certId, + DomainNames: d.config.Domains, + } + batchUpdateCertificateConfigResp, err := d.sdkClient.BatchUpdateCertificateConfig(batchUpdateCertificateConfigReq) + d.logger.Debug("sdk request 'cdn.BatchUpdateCertificateConfig'", slog.Any("request", batchUpdateCertificateConfigReq), slog.Any("response", batchUpdateCertificateConfigResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.BatchUpdateCertificateConfig': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) { + if accessKeyId == "" { + return nil, errors.New("invalid wangsu access key id") + } + + if accessKeySecret == "" { + return nil, errors.New("invalid wangsu access key secret") + } + + return wangsusdk.NewClient(accessKeyId, accessKeySecret), nil +} diff --git a/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go new file mode 100644 index 00000000..99859b85 --- /dev/null +++ b/internal/pkg/core/deployer/providers/wangsu-cdn/wangsu_cdn_test.go @@ -0,0 +1,75 @@ +package wangsucdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./wangsu_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_WANGSUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_WANGSUCDN_DOMAIN="example.com" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domains: []string{fDomain}, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index 436ea5a5..4d5f2e10 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -88,9 +88,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 查询已部署加速域名的详情 getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain) - d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp)) + d.logger.Debug("sdk request 'cdnpro.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetHostnameDetail': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetHostnameDetail': %w", err) } // 生成网宿云证书参数 @@ -126,9 +126,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE NewVersion: certificateNewVersionInfo, } createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateCertificate': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateCertificate': %w", err) } wangsuCertUrl = createCertificateResp.CertificateUrl @@ -149,9 +149,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE NewVersion: certificateNewVersionInfo, } updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateCertificate': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.UpdateCertificate': %w", err) } wangsuCertUrl = updateCertificateResp.CertificateUrl @@ -186,9 +186,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE createDeploymentTaskReq.Webhook = typeutil.ToPtr(d.config.WebhookId) } createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq) - d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp)) + d.logger.Debug("sdk request 'cdnpro.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateDeploymentTask': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.CreateDeploymentTask': %w", err) } // 循环获取部署任务详细信息,等待任务状态变更 @@ -206,9 +206,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId) - d.logger.Info("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp)) + d.logger.Info("sdk request 'cdnpro.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDeploymentTaskDetail': %w", err) + return nil, fmt.Errorf("failed to execute sdk request 'cdnpro.GetDeploymentTaskDetail': %w", err) } if getDeploymentTaskDetailResp.Status == "failed" { diff --git a/internal/pkg/sdk3rd/wangsu/cdn/api.go b/internal/pkg/sdk3rd/wangsu/cdn/api.go new file mode 100644 index 00000000..997c05bf --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdn/api.go @@ -0,0 +1,15 @@ +package cdn + +import ( + "net/http" +) + +func (c *Client) BatchUpdateCertificateConfig(req *BatchUpdateCertificateConfigRequest) (*BatchUpdateCertificateConfigResponse, error) { + resp := &BatchUpdateCertificateConfigResponse{} + _, err := c.client.SendRequestWithResult(http.MethodPut, "/api/config/certificate/batch", req, resp) + if err != nil { + return resp, err + } + + return resp, err +} diff --git a/internal/pkg/sdk3rd/wangsu/cdn/client.go b/internal/pkg/sdk3rd/wangsu/cdn/client.go new file mode 100644 index 00000000..ac53e171 --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdn/client.go @@ -0,0 +1,20 @@ +package cdn + +import ( + "time" + + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type Client struct { + client *openapi.Client +} + +func NewClient(accessKey, secretKey string) *Client { + return &Client{client: openapi.NewClient(accessKey, secretKey)} +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.WithTimeout(timeout) + return c +} diff --git a/internal/pkg/sdk3rd/wangsu/cdn/models.go b/internal/pkg/sdk3rd/wangsu/cdn/models.go new file mode 100644 index 00000000..5bf934af --- /dev/null +++ b/internal/pkg/sdk3rd/wangsu/cdn/models.go @@ -0,0 +1,26 @@ +package cdn + +import ( + "github.com/usual2970/certimate/internal/pkg/sdk3rd/wangsu/openapi" +) + +type baseResponse struct { + RequestId *string `json:"requestId,omitempty"` + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +var _ openapi.Result = (*baseResponse)(nil) + +func (r *baseResponse) SetRequestId(requestId string) { + r.RequestId = &requestId +} + +type BatchUpdateCertificateConfigRequest struct { + CertificateId int64 `json:"certificateId" required:"true"` + DomainNames []string `json:"domainNames" required:"true"` +} + +type BatchUpdateCertificateConfigResponse struct { + baseResponse +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index a46ae1be..67b43e77 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -91,6 +91,7 @@ import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolc import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx"; import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx"; import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx"; +import DeployNodeConfigFormWangsuCDNConfig from "./DeployNodeConfigFormWangsuCDNConfig.tsx"; import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx"; import DeployNodeConfigFormWangsuCertificateConfig from "./DeployNodeConfigFormWangsuCertificateConfig.tsx"; import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx"; @@ -334,6 +335,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS: return ; + case DEPLOYMENT_PROVIDERS.WANGSU_CDN: + return ; case DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO: return ; case DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx new file mode 100644 index 00000000..57d0d381 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx @@ -0,0 +1,146 @@ +import { memo } from "react"; +import { useTranslation } from "react-i18next"; +import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; +import { Button, Form, type FormInstance, Input, Space } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import ModalForm from "@/components/ModalForm"; +import MultipleInput from "@/components/MultipleInput"; +import { useAntdForm } from "@/hooks"; +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormWangsuCDNConfigFieldValues = Nullish<{ + domains: string; +}>; + +export type DeployNodeConfigFormWangsuCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormWangsuCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormWangsuCDNConfigFieldValues) => void; +}; + +const MULTIPLE_INPUT_DELIMITER = ";"; + +const initFormModel = (): DeployNodeConfigFormWangsuCDNConfigFieldValues => { + return { + domains: "", + }; +}; + +const DeployNodeConfigFormWangsuCDNConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormWangsuCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domains: z + .string() + .nullish() + .refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => validDomainName(e)); + }, t("workflow_node.deploy.form.wangsu_cdn_domains.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldDomains = Form.useWatch("domains", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + { + formInst.setFieldValue("domains", e.target.value); + }} + onClear={() => { + formInst.setFieldValue("domains", ""); + }} + /> + + + + + } + onChange={(value) => { + formInst.setFieldValue("domains", value); + }} + /> + + +
+ ); +}; + +const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domains: z.array(z.string()).refine((v) => { + return v.every((e) => validDomainName(e)); + }, t("workflow_node.deploy.form.wangsu_cdn_domains.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + name: "workflowNodeDeployConfigFormWangsuCDNNamesModalInput", + initialValues: { domains: value?.split(MULTIPLE_INPUT_DELIMITER) }, + onSubmit: (values) => { + onChange?.( + values.domains + .map((e) => e.trim()) + .filter((e) => !!e) + .join(MULTIPLE_INPUT_DELIMITER) + ); + }, + }); + + return ( + + + + + + ); +}); + +export default DeployNodeConfigFormWangsuCDNConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index e679d13b..1c80910e 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -424,6 +424,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`, VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`, VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`, + WANGSU_CDN: `${ACCESS_PROVIDERS.WANGSU}-cdn`, WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`, WANGSU_CERTIFICATE: `${ACCESS_PROVIDERS.WANGSU}-certificate`, WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, @@ -520,6 +521,7 @@ export const deploymentProvidersMap: Maphttps://console.volcengine.com/tos", + "workflow_node.deploy.form.wangsu_cdn_domains.label": "Wangsu Cloud CDN domains", + "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "Please enter Wangsu Cloud CDN domain names (separated by semicolons)", + "workflow_node.deploy.form.wangsu_cdn_domains.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index/#/property/list", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.title": "Change Wangsu Cloud CDN domains", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.placeholder": "Please enter Wangsu Cloud CDN domain", "workflow_node.deploy.form.wangsu_cdnpro_environment.label": "Wangsu Cloud environment", "workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "Please select Wangsu Cloud environment", "workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "Production environment", @@ -770,7 +775,7 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "For more information, see https://cdnpro.console.wangsu.com/v2/index/#/certificate", "workflow_node.deploy.form.wangsu_certificate_id.label": "Wangsu Cloud certificate ID (Optional)", "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "Please enter Wangsu Cloud certificate ID", - "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index#/certificate/list?code=cert_mylist&parentCode=cert_ssl&productCode=certificatemanagement", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "For more information, see https://cdn.console.wangsu.com/v2/index#/certificate/list", "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 8c1be41a..d69b1e38 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -145,7 +145,8 @@ "provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "provider.wangsu": "网宿云", - "provider.wangsu.cdnpro": "网宿云 - CDN Pro", + "provider.wangsu.cdn": "网宿云 - 内容分发网络 CDN", + "provider.wangsu.cdnpro": "网宿云 - CDN Pro (CDN 360)", "provider.wangsu.certificate_upload": "网宿云 - 上传到证书管理", "provider.webhook": "Webhook", "provider.wecombot": "企业微信群机器人", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index ada4ec22..13d21eed 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -754,6 +754,11 @@ "workflow_node.deploy.form.volcengine_tos_domain.label": "火山引擎 TOS 自定义域名", "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "请输入火山引擎 TOS 自定义域名", "workflow_node.deploy.form.volcengine_tos_domain.tooltip": "这是什么?请参阅 see https://console.volcengine.com/tos", + "workflow_node.deploy.form.wangsu_cdn_domains.label": "网宿云 CDN 加速域名", + "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "请输入网宿云 CDN 加速域名(多个值请用半角分号隔开)", + "workflow_node.deploy.form.wangsu_cdn_domains.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index/#/property/list", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.title": "修改网宿云 CDN 加速域名", + "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.placeholder": "请输入网宿云 CDN 加速域名", "workflow_node.deploy.form.wangsu_cdnpro_environment.label": "网宿云环境", "workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "请选择网宿云环境", "workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "生产环境", @@ -769,7 +774,7 @@ "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", "workflow_node.deploy.form.wangsu_certificate_id.label": "网宿云证书 ID(可选)", "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "请输入网宿云证书 ID", - "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list?code=cert_mylist&parentCode=cert_ssl&productCode=certificatemanagement

不填写时,将上传新证书;否则,将替换原证书。", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list

不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", From 4ad08d983a60ead2e291b85a991e0316bded37c9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 May 2025 14:15:17 +0800 Subject: [PATCH 11/13] refactor: clean code --- .../deployer/providers/webhook/webhook.go | 2 +- .../pkg/core/notifier/providers/bark/bark.go | 3 +- .../core/notifier/providers/gotify/gotify.go | 3 +- .../providers/mattermost/mattermost.go | 5 +-- .../notifier/providers/pushover/pushover.go | 3 +- .../notifier/providers/pushplus/pushplus.go | 3 +- .../providers/serverchan/serverchan.go | 3 +- .../providers/telegrambot/telegrambot.go | 3 +- .../notifier/providers/wecombot/wecombot.go | 3 +- internal/pkg/sdk3rd/1panel/client.go | 42 +++++++------------ internal/pkg/sdk3rd/baishan/client.go | 22 ++++------ internal/pkg/sdk3rd/btpanel/client.go | 14 +++---- internal/pkg/sdk3rd/bunny/client.go | 18 +++----- internal/pkg/sdk3rd/cachefly/client.go | 18 +++----- internal/pkg/sdk3rd/cdnfly/client.go | 25 ++++------- internal/pkg/sdk3rd/dnsla/client.go | 21 ++++------ internal/pkg/sdk3rd/dogecloud/client.go | 4 +- internal/pkg/sdk3rd/flexcdn/client.go | 33 +++++++-------- internal/pkg/sdk3rd/goedge/client.go | 33 +++++++-------- internal/pkg/sdk3rd/lecdn/v3/client/client.go | 31 +++++++------- internal/pkg/sdk3rd/lecdn/v3/master/client.go | 31 +++++++------- internal/pkg/sdk3rd/netlify/client.go | 19 ++++----- internal/pkg/sdk3rd/rainyun/client.go | 17 +++----- internal/pkg/sdk3rd/ratpanel/client.go | 8 +--- internal/pkg/sdk3rd/safeline/client.go | 15 +++---- internal/pkg/sdk3rd/upyun/console/client.go | 31 +++++++------- internal/pkg/sdk3rd/wangsu/openapi/client.go | 6 +-- 27 files changed, 169 insertions(+), 247 deletions(-) diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 3a21717b..49b07b47 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -176,7 +176,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } // 发送请求 - resp, err := req.SetDebug(true).Send() + resp, err := req.Send() if err != nil { return nil, fmt.Errorf("failed to send webhook request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/bark/bark.go b/internal/pkg/core/notifier/providers/bark/bark.go index 4d1902f2..97ece0be 100644 --- a/internal/pkg/core/notifier/providers/bark/bark.go +++ b/internal/pkg/core/notifier/providers/bark/bark.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -65,7 +64,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "body": message, "device_key": n.config.DeviceKey, }) - resp, err := req.Execute(http.MethodPost, serverUrl) + resp, err := req.Post(serverUrl) if err != nil { return nil, fmt.Errorf("bark api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/gotify/gotify.go b/internal/pkg/core/notifier/providers/gotify/gotify.go index 05b2f919..81dcb8ad 100644 --- a/internal/pkg/core/notifier/providers/gotify/gotify.go +++ b/internal/pkg/core/notifier/providers/gotify/gotify.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "strings" "github.com/go-resty/resty/v2" @@ -64,7 +63,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "message": message, "priority": n.config.Priority, }) - resp, err := req.Execute(http.MethodPost, fmt.Sprintf("%s/message", serverUrl)) + resp, err := req.Post(fmt.Sprintf("%s/message", serverUrl)) if err != nil { return nil, fmt.Errorf("gotify api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/mattermost/mattermost.go b/internal/pkg/core/notifier/providers/mattermost/mattermost.go index 4f1c3e9c..a9b2f4d6 100644 --- a/internal/pkg/core/notifier/providers/mattermost/mattermost.go +++ b/internal/pkg/core/notifier/providers/mattermost/mattermost.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "strings" "github.com/go-resty/resty/v2" @@ -64,7 +63,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "login_id": n.config.Username, "password": n.config.Password, }) - loginResp, err := loginReq.Execute(http.MethodPost, fmt.Sprintf("%s/api/v4/users/login", serverUrl)) + loginResp, err := loginReq.Post(fmt.Sprintf("%s/api/v4/users/login", serverUrl)) if err != nil { return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err) } else if loginResp.IsError() { @@ -88,7 +87,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s }, }, }) - postResp, err := postReq.Execute(http.MethodPost, fmt.Sprintf("%s/api/v4/posts", serverUrl)) + postResp, err := postReq.Post(fmt.Sprintf("%s/api/v4/posts", serverUrl)) if err != nil { return nil, fmt.Errorf("mattermost api error: failed to send request: %w", err) } else if postResp.IsError() { diff --git a/internal/pkg/core/notifier/providers/pushover/pushover.go b/internal/pkg/core/notifier/providers/pushover/pushover.go index 459a4950..b7f74bba 100644 --- a/internal/pkg/core/notifier/providers/pushover/pushover.go +++ b/internal/pkg/core/notifier/providers/pushover/pushover.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -59,7 +58,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "token": n.config.Token, "user": n.config.User, }) - resp, err := req.Execute(http.MethodPost, "https://api.pushover.net/1/messages.json") + resp, err := req.Post("https://api.pushover.net/1/messages.json") if err != nil { return nil, fmt.Errorf("pushover api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/pushplus/pushplus.go b/internal/pkg/core/notifier/providers/pushplus/pushplus.go index a8a95ac7..834f9683 100644 --- a/internal/pkg/core/notifier/providers/pushplus/pushplus.go +++ b/internal/pkg/core/notifier/providers/pushplus/pushplus.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -57,7 +56,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "content": message, "token": n.config.Token, }) - resp, err := req.Execute(http.MethodPost, "https://www.pushplus.plus/send") + resp, err := req.Post("https://www.pushplus.plus/send") if err != nil { return nil, fmt.Errorf("pushplus api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan.go b/internal/pkg/core/notifier/providers/serverchan/serverchan.go index cd77ce83..d74b2fcc 100644 --- a/internal/pkg/core/notifier/providers/serverchan/serverchan.go +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -55,7 +54,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "text": subject, "desp": message, }) - resp, err := req.Execute(http.MethodPost, n.config.ServerUrl) + resp, err := req.Post(n.config.ServerUrl) if err != nil { return nil, fmt.Errorf("serverchan api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go b/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go index a324ee00..99b86a38 100644 --- a/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go +++ b/internal/pkg/core/notifier/providers/telegrambot/telegrambot.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -57,7 +56,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "chat_id": n.config.ChatId, "text": subject + "\n" + message, }) - resp, err := req.Execute(http.MethodPost, fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", n.config.BotToken)) + resp, err := req.Post(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", n.config.BotToken)) if err != nil { return nil, fmt.Errorf("telegram api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/core/notifier/providers/wecombot/wecombot.go b/internal/pkg/core/notifier/providers/wecombot/wecombot.go index daa771e0..36c179d4 100644 --- a/internal/pkg/core/notifier/providers/wecombot/wecombot.go +++ b/internal/pkg/core/notifier/providers/wecombot/wecombot.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log/slog" - "net/http" "github.com/go-resty/resty/v2" @@ -57,7 +56,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s "content": subject + "\n\n" + message, }, }) - resp, err := req.Execute(http.MethodPost, n.config.WebhookUrl) + resp, err := req.Post(n.config.WebhookUrl) if err != nil { return nil, fmt.Errorf("wecom api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/1panel/client.go b/internal/pkg/sdk3rd/1panel/client.go index 4480ae49..003203d3 100644 --- a/internal/pkg/sdk3rd/1panel/client.go +++ b/internal/pkg/sdk3rd/1panel/client.go @@ -14,25 +14,30 @@ import ( ) type Client struct { - apiHost string - apiVersion string - apiKey string + apiKey string client *resty.Client } func NewClient(apiHost, apiVersion, apiKey string) *Client { - client := resty.New() - if apiVersion == "" { apiVersion = "v1" } + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/api/" + apiVersion). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + tokenMd5 := md5.Sum([]byte("1panel" + apiKey + timestamp)) + tokenMd5Hex := hex.EncodeToString(tokenMd5[:]) + req.Header.Set("1Panel-Timestamp", timestamp) + req.Header.Set("1Panel-Token", tokenMd5Hex) + + return nil + }) + return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiVersion: apiVersion, - apiKey: apiKey, - client: client, + client: client, } } @@ -46,16 +51,8 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { return c } -func (c *Client) generateToken(timestamp string) string { - tokenMd5 := md5.Sum([]byte("1panel" + c.apiKey + timestamp)) - tokenMd5Hex := hex.EncodeToString(tokenMd5[:]) - return tokenMd5Hex -} - func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = c.apiHost + "/api/" + c.apiVersion + path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -71,17 +68,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - timestamp := fmt.Sprintf("%d", time.Now().Unix()) - token := c.generateToken(timestamp) - req.SetHeader("1Panel-Timestamp", timestamp) - req.SetHeader("1Panel-Token", token) - - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("1panel api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/baishan/client.go b/internal/pkg/sdk3rd/baishan/client.go index 81790a21..b3e428ee 100644 --- a/internal/pkg/sdk3rd/baishan/client.go +++ b/internal/pkg/sdk3rd/baishan/client.go @@ -13,17 +13,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://cdn.api.baishan.com"). + SetHeader("token", apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -34,8 +33,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://cdn.api.baishan.com" + path if strings.EqualFold(method, http.MethodGet) { qs := url.Values{} if params != nil { @@ -61,17 +58,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetQueryParam("token", c.apiToken). - SetQueryParamsFromValues(qs) + req = req.SetQueryParamsFromValues(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetQueryParam("token", c.apiToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("baishan api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/btpanel/client.go b/internal/pkg/sdk3rd/btpanel/client.go index e09eed13..1da625da 100644 --- a/internal/pkg/sdk3rd/btpanel/client.go +++ b/internal/pkg/sdk3rd/btpanel/client.go @@ -14,19 +14,18 @@ import ( ) type Client struct { - apiHost string - apiKey string + apiKey string client *resty.Client } func NewClient(apiHost, apiKey string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - client: client, + apiKey: apiKey, + client: client, } } @@ -78,11 +77,10 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, data["request_time"] = fmt.Sprintf("%d", timestamp) data["request_token"] = c.generateSignature(fmt.Sprintf("%d", timestamp)) - url := c.apiHost + path req := c.client.R(). SetHeader("Content-Type", "application/x-www-form-urlencoded"). SetFormData(data) - resp, err := req.Post(url) + resp, err := req.Post(path) if err != nil { return resp, fmt.Errorf("baota api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/bunny/client.go b/internal/pkg/sdk3rd/bunny/client.go index 0f0dbea5..8d50e1fc 100644 --- a/internal/pkg/sdk3rd/bunny/client.go +++ b/internal/pkg/sdk3rd/bunny/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.bunny.net"). + SetHeader("AccessKey", apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -32,9 +31,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://api.bunny.net" + path - req = req.SetHeader("AccessKey", c.apiToken) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -50,12 +46,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("bunny api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/cachefly/client.go b/internal/pkg/sdk3rd/cachefly/client.go index 37a82ccc..342e329d 100644 --- a/internal/pkg/sdk3rd/cachefly/client.go +++ b/internal/pkg/sdk3rd/cachefly/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.cachefly.com/api/2.5"). + SetHeader("x-cf-authorization", "Bearer "+apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -32,9 +31,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = "https://api.cachefly.com/api/2.5" + path - req = req.SetHeader("x-cf-authorization", "Bearer "+c.apiToken) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -50,12 +46,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/cdnfly/client.go b/internal/pkg/sdk3rd/cdnfly/client.go index 5d80395c..c8753ed5 100644 --- a/internal/pkg/sdk3rd/cdnfly/client.go +++ b/internal/pkg/sdk3rd/cdnfly/client.go @@ -12,21 +12,17 @@ import ( ) type Client struct { - apiHost string - apiKey string - apiSecret string - client *resty.Client } func NewClient(apiHost, apiKey, apiSecret string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetHeader("api-key", apiKey). + SetHeader("api-secret", apiSecret) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiKey: apiKey, - apiSecret: apiSecret, - client: client, + client: client, } } @@ -42,11 +38,6 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = c.apiHost + path - req = req. - SetHeader("api-key", c.apiKey). - SetHeader("api-secret", c.apiSecret) if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -62,12 +53,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/dnsla/client.go b/internal/pkg/sdk3rd/dnsla/client.go index a0659011..d9a86fc5 100644 --- a/internal/pkg/sdk3rd/dnsla/client.go +++ b/internal/pkg/sdk3rd/dnsla/client.go @@ -11,19 +11,16 @@ import ( ) type Client struct { - apiId string - apiSecret string - client *resty.Client } func NewClient(apiId, apiSecret string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.dns.la/api"). + SetBasicAuth(apiId, apiSecret) return &Client{ - apiId: apiId, - apiSecret: apiSecret, - client: client, + client: client, } } @@ -33,9 +30,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.apiId, c.apiSecret) - req.Method = method - req.URL = "https://api.dns.la/api" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -51,12 +46,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/dogecloud/client.go b/internal/pkg/sdk3rd/dogecloud/client.go index 806b0ea9..75342907 100644 --- a/internal/pkg/sdk3rd/dogecloud/client.go +++ b/internal/pkg/sdk3rd/dogecloud/client.go @@ -164,8 +164,8 @@ func (c *Client) sendReq(method string, path string, data map[string]interface{} if err != nil { return nil, err } - req.Header.Add("Content-Type", mime) - req.Header.Add("Authorization", auth) + req.Header.Set("Content-Type", mime) + req.Header.Set("Authorization", auth) client := http.Client{} resp, err := client.Do(req) diff --git a/internal/pkg/sdk3rd/flexcdn/client.go b/internal/pkg/sdk3rd/flexcdn/client.go index 6b5626ee..beae469a 100644 --- a/internal/pkg/sdk3rd/flexcdn/client.go +++ b/internal/pkg/sdk3rd/flexcdn/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - apiHost string apiRole string accessKeyId string accessKey string @@ -26,15 +25,22 @@ type Client struct { } func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { - client := resty.New() - - return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), + client := &Client{ apiRole: apiRole, accessKeyId: accessKeyId, accessKey: accessKey, - client: client, } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("X-Cloud-Access-Token", client.accessToken) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -48,9 +54,7 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.accessKeyId, c.accessKey) - req.Method = method - req.URL = c.apiHost + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -64,17 +68,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetHeader("X-Cloud-Access-Token", c.accessToken). - SetQueryParams(qs) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("X-Cloud-Access-Token", c.accessToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("flexcdn api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/goedge/client.go b/internal/pkg/sdk3rd/goedge/client.go index 39ad8900..3dc961e3 100644 --- a/internal/pkg/sdk3rd/goedge/client.go +++ b/internal/pkg/sdk3rd/goedge/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - apiHost string apiRole string accessKeyId string accessKey string @@ -26,15 +25,22 @@ type Client struct { } func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { - client := resty.New() - - return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), + client := &Client{ apiRole: apiRole, accessKeyId: accessKeyId, accessKey: accessKey, - client: client, } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("X-Edge-Access-Token", client.accessToken) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -48,9 +54,7 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.accessKeyId, c.accessKey) - req.Method = method - req.URL = c.apiHost + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -64,17 +68,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetHeader("X-Edge-Access-Token", c.accessToken). - SetQueryParams(qs) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("X-Edge-Access-Token", c.accessToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("goedge api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/lecdn/v3/client/client.go b/internal/pkg/sdk3rd/lecdn/v3/client/client.go index 218499fa..ad3a752e 100644 --- a/internal/pkg/sdk3rd/lecdn/v3/client/client.go +++ b/internal/pkg/sdk3rd/lecdn/v3/client/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - apiHost string username string password string @@ -24,14 +23,21 @@ type Client struct { } func NewClient(apiHost, username, password string) *Client { - client := resty.New() - - return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), + client := &Client{ username: username, password: password, - client: client, } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/prod-api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("Authorization", "Bearer "+client.accessToken) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -45,7 +51,7 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.username, c.password) + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -59,17 +65,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetHeader("Authorization", "Bearer "+c.accessToken). - SetQueryParams(qs) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", "Bearer "+c.accessToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Execute(method, c.apiHost+"/prod-api"+path) + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/lecdn/v3/master/client.go b/internal/pkg/sdk3rd/lecdn/v3/master/client.go index 2f0cae4e..2da0c0c4 100644 --- a/internal/pkg/sdk3rd/lecdn/v3/master/client.go +++ b/internal/pkg/sdk3rd/lecdn/v3/master/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - apiHost string username string password string @@ -24,14 +23,21 @@ type Client struct { } func NewClient(apiHost, username, password string) *Client { - client := resty.New() - - return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), + client := &Client{ username: username, password: password, - client: client, } + client.client = resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/prod-api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.accessToken != "" { + req.Header.Set("Authorization", "Bearer "+client.accessToken) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -45,7 +51,7 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.username, c.password) + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -59,17 +65,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetHeader("Authorization", "Bearer "+c.accessToken). - SetQueryParams(qs) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", "Bearer "+c.accessToken). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Execute(method, c.apiHost+"/prod-api"+path) + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("lecdn api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/netlify/client.go b/internal/pkg/sdk3rd/netlify/client.go index e3110077..d270e35e 100644 --- a/internal/pkg/sdk3rd/netlify/client.go +++ b/internal/pkg/sdk3rd/netlify/client.go @@ -11,17 +11,16 @@ import ( ) type Client struct { - apiToken string - client *resty.Client } func NewClient(apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.netlify.com/api/v1"). + SetHeader("Authorization", "Bearer "+apiToken) return &Client{ - apiToken: apiToken, - client: client, + client: client, } } @@ -31,9 +30,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, queryParams interface{}, payloadParams interface{}) (*resty.Response, error) { - req := c.client.R().SetHeader("Authorization", "Bearer "+c.apiToken) - req.Method = method - req.URL = "https://api.netlify.com/api/v1" + path + req := c.client.R() if queryParams != nil { qs := make(map[string]string) @@ -63,12 +60,10 @@ func (c *Client) sendRequest(method string, path string, queryParams interface{} req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(payloadParams) + req = req.SetHeader("Content-Type", "application/json").SetBody(payloadParams) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("netlify api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/rainyun/client.go b/internal/pkg/sdk3rd/rainyun/client.go index eb34efd1..80113f0d 100644 --- a/internal/pkg/sdk3rd/rainyun/client.go +++ b/internal/pkg/sdk3rd/rainyun/client.go @@ -11,16 +11,15 @@ import ( ) type Client struct { - apiKey string - client *resty.Client } func NewClient(apiKey string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL("https://api.v2.rainyun.com"). + SetHeader("x-api-key", apiKey) return &Client{ - apiKey: apiKey, client: client, } } @@ -31,21 +30,17 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetHeader("x-api-key", c.apiKey) - req.Method = method - req.URL = "https://api.v2.rainyun.com" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { if params != nil { jsonb, _ := json.Marshal(params) req = req.SetQueryParam("options", string(jsonb)) } } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("rainyun api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/ratpanel/client.go b/internal/pkg/sdk3rd/ratpanel/client.go index ac2c4062..47202a04 100644 --- a/internal/pkg/sdk3rd/ratpanel/client.go +++ b/internal/pkg/sdk3rd/ratpanel/client.go @@ -81,8 +81,6 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -98,12 +96,10 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("ratpanel api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/safeline/client.go b/internal/pkg/sdk3rd/safeline/client.go index 0412514c..efcd3bd6 100644 --- a/internal/pkg/sdk3rd/safeline/client.go +++ b/internal/pkg/sdk3rd/safeline/client.go @@ -11,19 +11,16 @@ import ( ) type Client struct { - apiHost string - apiToken string - client *resty.Client } func NewClient(apiHost, apiToken string) *Client { - client := resty.New() + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/")). + SetHeader("X-SLCE-API-TOKEN", apiToken) return &Client{ - apiHost: strings.TrimRight(apiHost, "/"), - apiToken: apiToken, - client: client, + client: client, } } @@ -38,12 +35,10 @@ func (c *Client) WithTLSConfig(config *tls.Config) *Client { } func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) { - url := c.apiHost + path req := c.client.R(). SetHeader("Content-Type", "application/json"). - SetHeader("X-SLCE-API-TOKEN", c.apiToken). SetBody(params) - resp, err := req.Post(url) + resp, err := req.Post(path) if err != nil { return resp, fmt.Errorf("safeline api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/upyun/console/client.go b/internal/pkg/sdk3rd/upyun/console/client.go index 6b560adc..b207549e 100644 --- a/internal/pkg/sdk3rd/upyun/console/client.go +++ b/internal/pkg/sdk3rd/upyun/console/client.go @@ -20,13 +20,21 @@ type Client struct { } func NewClient(username, password string) *Client { - client := resty.New() - - return &Client{ + client := &Client{ username: username, password: password, - client: client, } + client.client = resty.New(). + SetBaseURL("https://console.upyun.com"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.loginCookie != "" { + req.Header.Set("Cookie", client.loginCookie) + } + + return nil + }) + + return client } func (c *Client) WithTimeout(timeout time.Duration) *Client { @@ -35,9 +43,7 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { } func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { - req := c.client.R().SetBasicAuth(c.username, c.password) - req.Method = method - req.URL = "https://console.upyun.com" + path + req := c.client.R() if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -51,17 +57,12 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } } - req = req. - SetHeader("Cookie", c.loginCookie). - SetQueryParams(qs) + req = req.SetQueryParams(qs) } else { - req = req. - SetHeader("Content-Type", "application/json"). - SetHeader("Cookie", c.loginCookie). - SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("upyun api error: failed to send request: %w", err) } else if resp.IsError() { diff --git a/internal/pkg/sdk3rd/wangsu/openapi/client.go b/internal/pkg/sdk3rd/wangsu/openapi/client.go index 0bb141d8..a8f4f2af 100644 --- a/internal/pkg/sdk3rd/wangsu/openapi/client.go +++ b/internal/pkg/sdk3rd/wangsu/openapi/client.go @@ -134,8 +134,6 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { func (c *Client) sendRequest(method string, path string, params interface{}, configureReq ...func(req *resty.Request)) (*resty.Response, error) { req := c.client.R() - req.Method = method - req.URL = path if strings.EqualFold(method, http.MethodGet) { qs := make(map[string]string) if params != nil { @@ -151,7 +149,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}, con req = req.SetQueryParams(qs) } else { - req = req.SetBody(params) + req = req.SetHeader("Content-Type", "application/json").SetBody(params) } if configureReq != nil { @@ -160,7 +158,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}, con } } - resp, err := req.Send() + resp, err := req.Execute(method, path) if err != nil { return resp, fmt.Errorf("wangsu api error: failed to send request: %w", err) } else if resp.IsError() { From 591df58992c737ef6a41f919d2403e67a0f54b38 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 May 2025 21:16:28 +0800 Subject: [PATCH 12/13] feat: new deployment provider: baotawaf site --- internal/deployer/providers.go | 24 +++ internal/domain/access.go | 6 + internal/domain/provider.go | 2 + .../providers/baotawaf-site/baotawaf_site.go | 151 ++++++++++++++++++ .../baotawaf-site/baotawaf_site_test.go | 81 ++++++++++ internal/pkg/sdk3rd/btwaf/api.go | 19 +++ internal/pkg/sdk3rd/btwaf/client.go | 77 +++++++++ internal/pkg/sdk3rd/btwaf/models.go | 67 ++++++++ ui/public/imgs/providers/baotawaf.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormBaotaPanelConfig.tsx | 6 +- .../access/AccessFormBaotaWAFConfig.tsx | 71 ++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...DeployNodeConfigFormBaotaWAFSiteConfig.tsx | 78 +++++++++ ui/src/domain/access.ts | 7 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 8 + ui/src/i18n/locales/en/nls.provider.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 7 +- ui/src/i18n/locales/zh/nls.access.json | 8 + ui/src/i18n/locales/zh/nls.provider.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 +- 22 files changed, 628 insertions(+), 8 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go create mode 100644 internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go create mode 100644 internal/pkg/sdk3rd/btwaf/api.go create mode 100644 internal/pkg/sdk3rd/btwaf/client.go create mode 100644 internal/pkg/sdk3rd/btwaf/models.go create mode 100644 ui/public/imgs/providers/baotawaf.svg create mode 100644 ui/src/components/access/AccessFormBaotaWAFConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 3f0b2600..0cf989a9 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -35,6 +35,7 @@ import ( pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn" pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console" pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" + pBaotaWAFSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site" pBunnyCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn" pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly" @@ -475,6 +476,29 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeBaotaWAFSite: + { + access := domain.AccessConfigForBaotaWAF{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeploymentProviderTypeBaotaWAFSite: + deployer, err := pBaotaWAFSite.NewDeployer(&pBaotaWAFSite.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"), + SitePort: maputil.GetOrDefaultInt32(options.ProviderServiceConfig, "sitePort", 443), + }) + return deployer, err + + default: + break + } + } + case domain.DeploymentProviderTypeBunnyCDN: { access := domain.AccessConfigForBunny{} diff --git a/internal/domain/access.go b/internal/domain/access.go index a2fd8a4a..cb5b7708 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -67,6 +67,12 @@ type AccessConfigForBaotaPanel struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForBaotaWAF struct { + ApiUrl string `json:"apiUrl"` + ApiKey string `json:"apiKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForBytePlus struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index fc2d4124..b825b823 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -19,6 +19,7 @@ const ( AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") AccessProviderTypeBaishan = AccessProviderType("baishan") AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") + AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf") AccessProviderTypeBytePlus = AccessProviderType("byteplus") AccessProviderTypeBunny = AccessProviderType("bunny") AccessProviderTypeBuypass = AccessProviderType("buypass") @@ -190,6 +191,7 @@ const ( DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn") DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console") DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site") + DeploymentProviderTypeBaotaWAFSite = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-site") DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn") DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn") DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly) diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go new file mode 100644 index 00000000..ed05937a --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go @@ -0,0 +1,151 @@ +package baotapanelwaf + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // 堡塔云 WAF 地址。 + ApiUrl string `json:"apiUrl"` + // 堡塔云 WAF 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 网站名称。 + SiteName string `json:"siteName"` + // 网站 SSL 端口。 + // 零值时默认为 443。 + SitePort int32 `json:"sitePort,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *btsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + if d.config.SiteName == "" { + return nil, errors.New("config `siteName` is required") + } + if d.config.SitePort == 0 { + d.config.SitePort = 443 + } + + // 遍历获取网站列表,获取网站 ID + // REF: https://support.huaweicloud.com/api-waf/ListHost.html + siteId := "" + getSitListPage := int32(1) + getSitListPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getSiteListReq := &btsdk.GetSiteListRequest{ + SiteName: typeutil.ToPtr(d.config.SiteName), + Page: typeutil.ToPtr(getSitListPage), + PageSize: typeutil.ToPtr(getSitListPageSize), + } + getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) + d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) + } + + if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil { + for _, siteItem := range getSiteListResp.Result.List { + if siteItem.SiteName == d.config.SiteName { + siteId = siteItem.SiteId + break + } + } + } + + if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) { + break + } else { + getSitListPage++ + } + } + if siteId == "" { + return nil, errors.New("site not found") + } + + // 修改站点配置 + modifySiteReq := &btsdk.ModifySiteRequest{ + SiteId: siteId, + Type: typeutil.ToPtr("openCert"), + Server: &btsdk.SiteServerInfo{ + ListenSSLPort: typeutil.ToPtr(d.config.SitePort), + SSL: &btsdk.SiteServerSSLInfo{ + IsSSL: typeutil.ToPtr(int32(1)), + FullChain: typeutil.ToPtr(certPEM), + PrivateKey: typeutil.ToPtr(privkeyPEM), + }, + }, + } + modifySiteResp, err := d.sdkClient.ModifySite(modifySiteReq) + d.logger.Debug("sdk request 'bt.ModifySite'", slog.Any("request", modifySiteReq), slog.Any("response", modifySiteResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.ModifySite': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid baota api url") + } + + if apiKey == "" { + return nil, errors.New("invalid baota api key") + } + + client := btsdk.NewClient(apiUrl, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go new file mode 100644 index 00000000..4e1ffe34 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go @@ -0,0 +1,81 @@ +package baotapanelwaf_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fSiteName string + fSitePort int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFSITE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") + flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "") + flag.Int64Var(&fSitePort, argsPrefix+"SITEPORT", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./baotawaf_site_test.go -args \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIKEY="your-api-key" \ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITENAME="your-site-name"\ + --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITEPORT=443 +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + fmt.Sprintf("SITENAME: %v", fSiteName), + fmt.Sprintf("SITEPORT: %v", fSitePort), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + SiteName: fSiteName, + SitePort: int32(fSitePort), + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/sdk3rd/btwaf/api.go b/internal/pkg/sdk3rd/btwaf/api.go new file mode 100644 index 00000000..bc35dee5 --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/api.go @@ -0,0 +1,19 @@ +package btwaf + +func (c *Client) GetSiteList(req *GetSiteListRequest) (*GetSiteListResponse, error) { + resp := &GetSiteListResponse{} + err := c.sendRequestWithResult("/wafmastersite/get_site_list", req, resp) + return resp, err +} + +func (c *Client) ModifySite(req *ModifySiteRequest) (*ModifySiteResponse, error) { + resp := &ModifySiteResponse{} + err := c.sendRequestWithResult("/wafmastersite/modify_site", req, resp) + return resp, err +} + +func (c *Client) ConfigSetSSL(req *ConfigSetSSLRequest) (*ConfigSetSSLResponse, error) { + resp := &ConfigSetSSLResponse{} + err := c.sendRequestWithResult("/config/set_cert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/btwaf/client.go b/internal/pkg/sdk3rd/btwaf/client.go new file mode 100644 index 00000000..5ae545cc --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/client.go @@ -0,0 +1,77 @@ +package btwaf + +import ( + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + client *resty.Client +} + +func NewClient(apiHost, apiKey string) *Client { + client := resty.New(). + SetBaseURL(strings.TrimRight(apiHost, "/") + "/api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + keyMd5 := md5.Sum([]byte(apiKey)) + keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:])) + signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex)) + signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:])) + req.Header.Set("waf_request_time", timestamp) + req.Header.Set("waf_request_token", signMd5Hex) + + return nil + }) + + return &Client{ + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) { + req := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(params) + resp, err := req.Post(path) + if err != nil { + return resp, fmt.Errorf("baota api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(path, params) + if err != nil { + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("baota api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 0 { + return fmt.Errorf("baota api error: code='%d'", errcode) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/btwaf/models.go b/internal/pkg/sdk3rd/btwaf/models.go new file mode 100644 index 00000000..16290e88 --- /dev/null +++ b/internal/pkg/sdk3rd/btwaf/models.go @@ -0,0 +1,67 @@ +package btwaf + +type BaseResponse interface { + GetCode() int32 +} + +type baseResponse struct { + Code *int32 `json:"code,omitempty"` +} + +func (r *baseResponse) GetCode() int32 { + if r.Code != nil { + return *r.Code + } + return 0 +} + +type GetSiteListRequest struct { + Page *int32 `json:"p,omitempty"` + PageSize *int32 `json:"p_size,omitempty"` + SiteName *string `json:"site_name,omitempty"` +} + +type GetSiteListResponse struct { + baseResponse + Result *struct { + List []*struct { + SiteId string `json:"site_id"` + SiteName string `json:"site_name"` + Type string `json:"types"` + Status int32 `json:"status"` + CreateTime int64 `json:"create_time"` + UpdateTime int64 `json:"update_time"` + } `json:"list"` + Total int32 `json:"total"` + } `json:"res,omitempty"` +} + +type SiteServerInfo struct { + ListenSSLPort *int32 `json:"listen_ssl_port,omitempty"` + SSL *SiteServerSSLInfo `json:"ssl,omitempty"` +} + +type SiteServerSSLInfo struct { + IsSSL *int32 `json:"is_ssl,omitempty"` + FullChain *string `json:"full_chain,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` +} + +type ModifySiteRequest struct { + SiteId string `json:"site_id"` + Type *string `json:"types,omitempty"` + Server *SiteServerInfo `json:"server,omitempty"` +} + +type ModifySiteResponse struct { + baseResponse +} + +type ConfigSetSSLRequest struct { + CertContent string `json:"certContent"` + KeyContent string `json:"keyContent"` +} + +type ConfigSetSSLResponse struct { + baseResponse +} diff --git a/ui/public/imgs/providers/baotawaf.svg b/ui/public/imgs/providers/baotawaf.svg new file mode 100644 index 00000000..34ab8ec8 --- /dev/null +++ b/ui/public/imgs/providers/baotawaf.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 5bc6cab0..fdf4f93f 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -20,6 +20,7 @@ import AccessFormAzureConfig from "./AccessFormAzureConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; import AccessFormBaishanConfig from "./AccessFormBaishanConfig"; import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig"; +import AccessFormBaotaWAFConfig from "./AccessFormBaotaWAFConfig"; import AccessFormBunnyConfig from "./AccessFormBunnyConfig"; import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig"; import AccessFormCacheFlyConfig from "./AccessFormCacheFlyConfig"; @@ -196,6 +197,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.BAOTAPANEL: return ; + case ACCESS_PROVIDERS.BAOTAWAF: + return ; case ACCESS_PROVIDERS.BUNNY: return ; case ACCESS_PROVIDERS.BYTEPLUS: diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx index d03c0f1b..dd355b5e 100644 --- a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx +++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx @@ -27,11 +27,7 @@ const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initia const formSchema = z.object({ apiUrl: z.string().url(t("common.errmsg.url_invalid")), - apiKey: z - .string() - .min(1, t("access.form.baotapanel_api_key.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + apiKey: z.string().nonempty(t("access.form.baotapanel_api_key.placeholder")).trim(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/access/AccessFormBaotaWAFConfig.tsx b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx new file mode 100644 index 00000000..e87ed596 --- /dev/null +++ b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForBaotaWAF } from "@/domain/access"; + +type AccessFormBaotaWAFConfigFieldValues = Nullish; + +export type AccessFormBaotaWAFConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormBaotaWAFConfigFieldValues; + onValuesChange?: (values: AccessFormBaotaWAFConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormBaotaWAFConfigFieldValues => { + return { + apiUrl: "http://:8379/", + apiKey: "", + }; +}; + +const AccessFormBaotaWAFConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormBaotaWAFConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + apiKey: z.string().nonempty(t("access.form.baotawaf_api_key.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormBaotaWAFConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 67b43e77..49ed12ec 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -42,6 +42,7 @@ import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaidu import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig"; import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig"; import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig"; +import DeployNodeConfigFormBaotaWAFSiteConfig from "./DeployNodeConfigFormBaotaWAFSiteConfig"; import DeployNodeConfigFormBunnyCDNConfig from "./DeployNodeConfigFormBunnyCDNConfig.tsx"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; @@ -237,6 +238,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE: return ; + case DEPLOYMENT_PROVIDERS.BAOTAWAF_SITE: + return ; case DEPLOYMENT_PROVIDERS.BUNNY_CDN: return ; case DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx new file mode 100644 index 00000000..6f992fb8 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx @@ -0,0 +1,78 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, InputNumber } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validPortNumber } from "@/utils/validators"; + +type DeployNodeConfigFormBaotaWAFSiteConfigFieldValues = Nullish<{ + siteName: string; + sitePort: number; +}>; + +export type DeployNodeConfigFormBaotaWAFSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormBaotaWAFSiteConfigFieldValues => { + return { + siteName: "", + sitePort: 443, + }; +}; + +const DeployNodeConfigFormBaotaWAFSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormBaotaWAFSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.baotawaf_site_name.placeholder")).trim(), + sitePort: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("workflow_node.deploy.form.baotawaf_site_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + + +
+ ); +}; + +export default DeployNodeConfigFormBaotaWAFSiteConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 4326aa75..f60f39c8 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -15,6 +15,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForBaiduCloud | AccessConfigForBaishan | AccessConfigForBaotaPanel + | AccessConfigForBaotaWAF | AccessConfigForBunny | AccessConfigForBytePlus | AccessConfigForCacheFly @@ -123,6 +124,12 @@ export type AccessConfigForBaotaPanel = { allowInsecureConnections?: boolean; }; +export type AccessConfigForBaotaWAF = { + apiUrl: string; + apiKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForBunny = { apiKey: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 1c80910e..3cb52e9f 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -13,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ BAIDUCLOUD: "baiducloud", BAISHAN: "baishan", BAOTAPANEL: "baotapanel", + BAOTAWAF: "baotawaf", BUNNY: "bunny", BYTEPLUS: "byteplus", BUYPASS: "buypass", @@ -124,6 +125,7 @@ export const accessProvidersMap: Map [ diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 4bf67617..589b2572 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -112,6 +112,14 @@ "access.form.baotapanel_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.baotapanel_allow_insecure_conns.switch.on": "Allow", "access.form.baotapanel_allow_insecure_conns.switch.off": "Disallow", + "access.form.baotawaf_api_url.label": "aaWAF URL", + "access.form.baotawaf_api_url.placeholder": "Please enter aaWAF URL", + "access.form.baotawaf_api_key.label": "aaWAF API key", + "access.form.baotawaf_api_key.placeholder": "Please enter aaWAF API key", + "access.form.baotawaf_api_key.tooltip": "For more information, see https://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.baotawaf_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.baotawaf_allow_insecure_conns.switch.on": "Allow", + "access.form.baotawaf_allow_insecure_conns.switch.off": "Disallow", "access.form.byteplus_access_key.label": "BytePlus AccessKey", "access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey", "access.form.byteplus_access_key.tooltip": "For more information, see https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index f87a5c2f..2493af42 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -42,6 +42,8 @@ "provider.baotapanel": "aaPanel (aka BaoTaPanel)", "provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console", "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website", + "provider.baotawaf": "aaWAF (aka BaotaWAF)", + "provider.baotawaf.site": "aaWAF (aka BaotaWAF) - Website", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - CDN (Content Delivery Network)", "provider.byteplus": "BytePlus", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index ba5ebe49..80237287 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -342,9 +342,14 @@ "workflow_node.deploy.form.baotapanel_site_names.label": "aaPanel site names", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter aaPanel site names (separated by semicolons)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid aaPanel site name", - "workflow_node.deploy.form.baotapanel_site_names.tooltip": "Usually equal to the websites domain name.", + "workflow_node.deploy.form.baotapanel_site_names.tooltip": "You can find it on aaPanel WebUI.", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "Change aaPanel site names", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "Please enter aaPanel site name", + "workflow_node.deploy.form.baotawaf_site_name.label": "aaWAF site name", + "workflow_node.deploy.form.baotawaf_site_name.placeholder": "Please enter aaWAF site name", + "workflow_node.deploy.form.baotawaf_site_name.tooltip": "You can find it on aaWAF WebUI.", + "workflow_node.deploy.form.baotawaf_site_port.label": "aaWAF site SSL port", + "workflow_node.deploy.form.baotawaf_site_port.placeholder": "Please enter aaWAF SSL port", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN pull zone ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "Please enter Bunny CDN pull zone ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "What is this? See https://dash.bunny.net/cdn", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index a3d059e1..f4ee2c2a 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -103,6 +103,14 @@ "access.form.baotapanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.baotapanel_allow_insecure_conns.switch.on": "允许", "access.form.baotapanel_allow_insecure_conns.switch.off": "不允许", + "access.form.baotawaf_api_url.label": "堡塔云 WAF URL", + "access.form.baotawaf_api_url.placeholder": "请输入堡塔云 WAF URL", + "access.form.baotawaf_api_key.label": "堡塔云 WAF 接口密钥", + "access.form.baotawaf_api_key.placeholder": "请输入 堡塔云 WAF 接口密钥", + "access.form.baotawaf_api_key.tooltip": "这是什么?请参阅 https://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.baotawaf_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.baotawaf_allow_insecure_conns.switch.on": "允许", + "access.form.baotawaf_allow_insecure_conns.switch.off": "不允许", "access.form.bunny_api_key.label": "Bunny API Key", "access.form.bunny_api_key.placeholder": "请输入 Bunny API Key", "access.form.bunny_api_key.tooltip": "这是什么?请参阅 https://docs.bunny.net/reference/bunnynet-api-overview", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index d69b1e38..7f57b7e1 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -42,6 +42,8 @@ "provider.baotapanel": "宝塔面板", "provider.baotapanel.console": "宝塔面板 - 面板", "provider.baotapanel.site": "宝塔面板 - 网站", + "provider.baotawaf": "堡塔云 WAF", + "provider.baotawaf.site": "堡塔云 WAF - 网站", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - 内容分发网络 CDN", "provider.byteplus": "BytePlus", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 13d21eed..faf40816 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -337,13 +337,18 @@ "workflow_node.deploy.form.baotapanel_site_type.option.other.label": "其他", "workflow_node.deploy.form.baotapanel_site_name.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_name.placeholder": "请输入宝塔面板网站名称", - "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看。", "workflow_node.deploy.form.baotapanel_site_names.label": "宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_names.placeholder": "请输入宝塔面板网站名称(多个值请用半角分号隔开)", "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "请输入正确的宝塔面板网站名称", - "workflow_node.deploy.form.baotapanel_site_names.tooltip": "通常为网站域名。", + "workflow_node.deploy.form.baotapanel_site_names.tooltip": "请登录宝塔面板查看。", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "修改宝塔面板网站名称", "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "请输入宝塔面板网站名称", + "workflow_node.deploy.form.baotawaf_site_name.label": "堡塔云 WAF 网站名称", + "workflow_node.deploy.form.baotawaf_site_name.placeholder": "请输入堡塔云 WAF 网站名称", + "workflow_node.deploy.form.baotawaf_site_name.tooltip": "请登录堡塔云 WAF 面板查看。", + "workflow_node.deploy.form.baotawaf_site_port.label": "堡塔云 WAF 网站 SSL 端口", + "workflow_node.deploy.form.baotawaf_site_port.placeholder": "请输入堡塔云 WAF 网站 SSL 端口", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN 拉取区域 ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "请输入 Bunny CDN 拉取区域 ID", "workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "这是什么?请参阅 https://dash.bunny.net/cdn", From 524c4fd1e849a315a6665fecb9b22b95f99158de Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 May 2025 21:22:01 +0800 Subject: [PATCH 13/13] feat: new deployment provider: baotawaf console --- internal/deployer/providers.go | 11 ++- internal/domain/provider.go | 1 + .../baotawaf-console/baotawaf_console.go | 88 +++++++++++++++++++ .../baotawaf-console/baotawaf_console_test.go | 73 +++++++++++++++ .../access/AccessFormACMECAConfig.tsx | 2 +- ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.provider.json | 7 +- 8 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go create mode 100644 internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 0cf989a9..15a8f370 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -35,6 +35,7 @@ import ( pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn" pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console" pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" + pBaotaWAFConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-console" pBaotaWAFSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site" pBunnyCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn" pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" @@ -476,7 +477,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeBaotaWAFSite: + case domain.DeploymentProviderTypeBaotaWAFConsole, domain.DeploymentProviderTypeBaotaWAFSite: { access := domain.AccessConfigForBaotaWAF{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -484,6 +485,14 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } switch options.Provider { + case domain.DeploymentProviderTypeBaotaWAFConsole: + deployer, err := pBaotaWAFConsole.NewDeployer(&pBaotaWAFConsole.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + }) + return deployer, err + case domain.DeploymentProviderTypeBaotaWAFSite: deployer, err := pBaotaWAFSite.NewDeployer(&pBaotaWAFSite.DeployerConfig{ ApiUrl: access.ApiUrl, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index b825b823..db0de59d 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -191,6 +191,7 @@ const ( DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn") DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console") DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site") + DeploymentProviderTypeBaotaWAFConsole = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-console") DeploymentProviderTypeBaotaWAFSite = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-site") DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn") DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn") diff --git a/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go new file mode 100644 index 00000000..811b350b --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console.go @@ -0,0 +1,88 @@ +package baotapanelconsole + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf" +) + +type DeployerConfig struct { + // 堡塔云 WAF 地址。 + ApiUrl string `json:"apiUrl"` + // 堡塔云 WAF 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *btsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 设置面板 SSL + configSetSSLReq := &btsdk.ConfigSetSSLRequest{ + CertContent: certPEM, + KeyContent: privkeyPEM, + } + configSetSSLResp, err := d.sdkClient.ConfigSetSSL(configSetSSLReq) + d.logger.Debug("sdk request 'bt.ConfigSetSSL'", slog.Any("request", configSetSSLReq), slog.Any("response", configSetSSLResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'bt.ConfigSetSSL': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid baota api url") + } + + if apiKey == "" { + return nil, errors.New("invalid baota api key") + } + + client := btsdk.NewClient(apiUrl, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go new file mode 100644 index 00000000..ba6ddd26 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotawaf-console/baotawaf_console_test.go @@ -0,0 +1,73 @@ +package baotapanelconsole_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-console" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fSiteName string + fSitePort int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./baotawaf_console_test.go -args \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_APIURL="http://127.0.0.1:8888" \ + --CERTIMATE_DEPLOYER_BAOTAWAFCONSOLE_APIKEY="your-api-key" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/ui/src/components/access/AccessFormACMECAConfig.tsx b/ui/src/components/access/AccessFormACMECAConfig.tsx index ac4a32c2..d3075f22 100644 --- a/ui/src/components/access/AccessFormACMECAConfig.tsx +++ b/ui/src/components/access/AccessFormACMECAConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Select } from "antd"; +import { Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 3cb52e9f..15542455 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -373,6 +373,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ BAISHAN_CDN: `${ACCESS_PROVIDERS.BAISHAN}-cdn`, BAOTAPANEL_CONSOLE: `${ACCESS_PROVIDERS.BAOTAPANEL}-console`, BAOTAPANEL_SITE: `${ACCESS_PROVIDERS.BAOTAPANEL}-site`, + BAOTAWAF_CONSOLE: `${ACCESS_PROVIDERS.BAOTAWAF}-console`, BAOTAWAF_SITE: `${ACCESS_PROVIDERS.BAOTAWAF}-site`, BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`, BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`, @@ -551,6 +552,7 @@ export const deploymentProvidersMap: Map [ diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 2493af42..a3bbfecc 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -43,6 +43,7 @@ "provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console", "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website", "provider.baotawaf": "aaWAF (aka BaotaWAF)", + "provider.baotawaf.console": "aaWAF (aka BaotaWAF) - Console", "provider.baotawaf.site": "aaWAF (aka BaotaWAF) - Website", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - CDN (Content Delivery Network)", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 7f57b7e1..b9fb8777 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -1,6 +1,6 @@ { "provider.1panel": "1Panel", - "provider.1panel.console": "1Panel - 面板", + "provider.1panel.console": "1Panel - 控制台", "provider.1panel.site": "1Panel - 网站", "provider.acmeca": "ACME 自定义 CA 端点", "provider.acmehttpreq": "ACME 自定义 HTTP 端点", @@ -40,9 +40,10 @@ "provider.baishan": "白山云", "provider.baishan.cdn": "白山云 - 内容分发网络 CDN", "provider.baotapanel": "宝塔面板", - "provider.baotapanel.console": "宝塔面板 - 面板", + "provider.baotapanel.console": "宝塔面板 - 控制台", "provider.baotapanel.site": "宝塔面板 - 网站", "provider.baotawaf": "堡塔云 WAF", + "provider.baotawaf.console": "堡塔云 WAF - 控制台", "provider.baotawaf.site": "堡塔云 WAF - 网站", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - 内容分发网络 CDN", @@ -110,7 +111,7 @@ "provider.rainyun": "雨云", "provider.rainyun.rcdn": "雨云 - 雨盾 CDN", "provider.ratpanel": "耗子面板", - "provider.ratpanel.console": "耗子面板 - 面板", + "provider.ratpanel.console": "耗子面板 - 控制台", "provider.ratpanel.site": "耗子面板 - 网站", "provider.safeline": "雷池", "provider.ssh": "SSH 部署",