From 1499c637ee4c528081c1010b8b385b506024557f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 7 May 2025 22:06:01 +0800 Subject: [PATCH] feat: new deployment provider: goedge --- internal/deployer/providers.go | 18 +++ internal/domain/access.go | 6 + internal/domain/provider.go | 3 +- .../providers/aliyun-ddos/aliyun_ddos_test.go | 2 +- .../core/deployer/providers/goedge/consts.go | 8 ++ .../core/deployer/providers/goedge/goedge.go | 131 ++++++++++++++++++ .../deployer/providers/goedge/goedge_test.go | 81 +++++++++++ .../providers/safeline/safeline_test.go | 2 +- internal/pkg/sdk3rd/goedge/api.go | 46 ++++++ internal/pkg/sdk3rd/goedge/client.go | 97 +++++++++++++ internal/pkg/sdk3rd/goedge/models.go | 52 +++++++ internal/pkg/sdk3rd/upyun/console/client.go | 5 +- ui/public/imgs/providers/goedge.png | Bin 0 -> 2902 bytes ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormGoEdgeConfig.tsx | 87 ++++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormGoEdgeConfig.tsx | 79 +++++++++++ ui/src/domain/access.ts | 7 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 9 ++ ui/src/i18n/locales/en/nls.provider.json | 1 - .../i18n/locales/en/nls.workflow.nodes.json | 5 + ui/src/i18n/locales/zh/nls.access.json | 9 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 - .../i18n/locales/zh/nls.workflow.nodes.json | 5 + 25 files changed, 657 insertions(+), 7 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/goedge/consts.go create mode 100644 internal/pkg/core/deployer/providers/goedge/goedge.go create mode 100644 internal/pkg/core/deployer/providers/goedge/goedge_test.go create mode 100644 internal/pkg/sdk3rd/goedge/api.go create mode 100644 internal/pkg/sdk3rd/goedge/client.go create mode 100644 internal/pkg/sdk3rd/goedge/models.go create mode 100644 ui/public/imgs/providers/goedge.png create mode 100644 ui/src/components/access/AccessFormGoEdgeConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 9dd36e32..ab92fa6f 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -41,6 +41,7 @@ import ( pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" pEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications" 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" pHuaweiCloudELB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb" pHuaweiCloudSCM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-scm" @@ -568,6 +569,23 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeGoEdge: + { + access := domain.AccessConfigForGoEdge{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pGoEdge.NewDeployer(&pGoEdge.DeployerConfig{ + ApiUrl: access.ApiUrl, + AccessKeyId: access.AccessKeyId, + AccessKey: access.AccessKey, + ResourceType: pGoEdge.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeHuaweiCloudCDN, domain.DeploymentProviderTypeHuaweiCloudELB, domain.DeploymentProviderTypeHuaweiCloudSCM, domain.DeploymentProviderTypeHuaweiCloudWAF: { access := domain.AccessConfigForHuaweiCloud{} diff --git a/internal/domain/access.go b/internal/domain/access.go index fd36417c..39d945a1 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -146,6 +146,12 @@ type AccessConfigForGoDaddy struct { ApiSecret string `json:"apiSecret"` } +type AccessConfigForGoEdge struct { + ApiUrl string `json:"apiUrl"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` +} + type AccessConfigForGoogleTrustServices struct { EabKid string `json:"eabKid"` EabHmacKey string `json:"eabHmacKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 61af784f..2767d1ec 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -39,7 +39,7 @@ const ( AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGcore = AccessProviderType("gcore") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") - AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge(预留) + AccessProviderTypeGoEdge = AccessProviderType("goedge") AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices") AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") AccessProviderTypeJDCloud = AccessProviderType("jdcloud") @@ -186,6 +186,7 @@ const ( DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications") DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn") + DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge) DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn") DeploymentProviderTypeHuaweiCloudELB = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-elb") DeploymentProviderTypeHuaweiCloudSCM = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-scm") diff --git a/internal/pkg/core/deployer/providers/aliyun-ddos/aliyun_ddos_test.go b/internal/pkg/core/deployer/providers/aliyun-ddos/aliyun_ddos_test.go index b66f924f..b7f5ad34 100644 --- a/internal/pkg/core/deployer/providers/aliyun-ddos/aliyun_ddos_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-ddos/aliyun_ddos_test.go @@ -21,7 +21,7 @@ var ( ) func init() { - argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNDCDN_" + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNDDOS_" flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") diff --git a/internal/pkg/core/deployer/providers/goedge/consts.go b/internal/pkg/core/deployer/providers/goedge/consts.go new file mode 100644 index 00000000..91eaa9a3 --- /dev/null +++ b/internal/pkg/core/deployer/providers/goedge/consts.go @@ -0,0 +1,8 @@ +package goedge + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/goedge/goedge.go b/internal/pkg/core/deployer/providers/goedge/goedge.go new file mode 100644 index 00000000..6aed4d56 --- /dev/null +++ b/internal/pkg/core/deployer/providers/goedge/goedge.go @@ -0,0 +1,131 @@ +package goedge + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + goedgesdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/goedge" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" +) + +type DeployerConfig struct { + // GoEdge URL。 + ApiUrl string `json:"apiUrl"` + // GoEdge 用户 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // GoEdge 用户 AccessKey。 + AccessKey string `json:"accessKey"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *goedgesdk.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.AccessKeyId, config.AccessKey) + 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://goedge.cloud/dev/api/service/SSLCertService?role=user#updateSSLCert + updateSSLCertReq := &goedgesdk.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 'goedge.UpdateSSLCert'", slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'goedge.UpdateSSLCert': %w", err) + } + + return nil +} + +func createSdkClient(apiUrl, accessKeyId, accessKey string) (*goedgesdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid goedge api url") + } + + if accessKeyId == "" { + return nil, errors.New("invalid goedge access key id") + } + + if accessKey == "" { + return nil, errors.New("invalid goedge access key") + } + + client := goedgesdk.NewClient(apiUrl, "user", accessKeyId, accessKey) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/goedge/goedge_test.go b/internal/pkg/core/deployer/providers/goedge/goedge_test.go new file mode 100644 index 00000000..1f326b9e --- /dev/null +++ b/internal/pkg/core/deployer/providers/goedge/goedge_test.go @@ -0,0 +1,81 @@ +package goedge_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/goedge" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessKeyId string + fAccessKey string + fCertificateId int +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_GOEDGE_" + + 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 ./goedge_test.go -args \ + --CERTIMATE_DEPLOYER_GOEDGE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_GOEDGE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_GOEDGE_APIURL="http://127.0.0.1:7788" \ + --CERTIMATE_DEPLOYER_GOEDGE_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_GOEDGE_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_GOEDGE_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, + AccessKeyId: fAccessKeyId, + AccessKey: fAccessKey, + 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/safeline/safeline_test.go b/internal/pkg/core/deployer/providers/safeline/safeline_test.go index b3bac993..294086c8 100644 --- a/internal/pkg/core/deployer/providers/safeline/safeline_test.go +++ b/internal/pkg/core/deployer/providers/safeline/safeline_test.go @@ -56,7 +56,7 @@ func TestDeploy(t *testing.T) { ApiUrl: fApiUrl, ApiToken: fApiToken, AllowInsecureConnections: true, - ResourceType: provider.ResourceType("certificate"), + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, CertificateId: int32(fCertificateId), }) if err != nil { diff --git a/internal/pkg/sdk3rd/goedge/api.go b/internal/pkg/sdk3rd/goedge/api.go new file mode 100644 index 00000000..67ee9194 --- /dev/null +++ b/internal/pkg/sdk3rd/goedge/api.go @@ -0,0 +1,46 @@ +package goedge + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +func (c *Client) getAccessToken() error { + req := &getAPIAccessTokenRequest{ + Type: c.apiUserType, + 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("goedge api error: failed to parse response: %w", err) + } else if resp.GetCode() != 200 { + 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 + } + } + + resp := &UpdateSSLCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/SSLCertService/updateSSLCert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/goedge/client.go b/internal/pkg/sdk3rd/goedge/client.go new file mode 100644 index 00000000..c2e3b4f8 --- /dev/null +++ b/internal/pkg/sdk3rd/goedge/client.go @@ -0,0 +1,97 @@ +package goedge + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + apiUserType string + accessKeyId string + accessKey string + + accessToken string + accessTokenExp time.Time + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, apiUserType, accessKeyId, accessKey string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + apiUserType: apiUserType, + accessKeyId: accessKeyId, + accessKey: accessKey, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + 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. + SetQueryParams(qs). + SetHeader("X-Edge-Access-Token", c.accessToken) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("X-Edge-Access-Token", c.accessToken). + SetBody(params) + } + + resp, err := req.Send() + if err != nil { + return resp, fmt.Errorf("goedge api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("goedge api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body()) + } + + 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("goedge api error: failed to parse response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("goedge api error: %d - %s", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/goedge/models.go b/internal/pkg/sdk3rd/goedge/models.go new file mode 100644 index 00000000..d19bb558 --- /dev/null +++ b/internal/pkg/sdk3rd/goedge/models.go @@ -0,0 +1,52 @@ +package goedge + +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/upyun/console/client.go b/internal/pkg/sdk3rd/upyun/console/client.go index cf431c2c..56e7a86e 100644 --- a/internal/pkg/sdk3rd/upyun/console/client.go +++ b/internal/pkg/sdk3rd/upyun/console/client.go @@ -11,8 +11,9 @@ import ( ) type Client struct { - username string - password string + username string + password string + loginCookie string client *resty.Client diff --git a/ui/public/imgs/providers/goedge.png b/ui/public/imgs/providers/goedge.png new file mode 100644 index 0000000000000000000000000000000000000000..760ab333d3b797b20910e0841616f95757ab4082 GIT binary patch literal 2902 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 CL)@7F literal 0 HcmV?d00001 diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 85ad8a78..3727f09c 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -34,6 +34,7 @@ import AccessFormEmailConfig from "./AccessFormEmailConfig"; import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; import AccessFormGnameConfig from "./AccessFormGnameConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; +import AccessFormGoEdgeConfig from "./AccessFormGoEdgeConfig"; import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServicesConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; @@ -200,6 +201,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.GODADDY: return ; + case ACCESS_PROVIDERS.GOEDGE: + return ; case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES: return ; case ACCESS_PROVIDERS.EDGIO: diff --git a/ui/src/components/access/AccessFormGoEdgeConfig.tsx b/ui/src/components/access/AccessFormGoEdgeConfig.tsx new file mode 100644 index 00000000..a673aa56 --- /dev/null +++ b/ui/src/components/access/AccessFormGoEdgeConfig.tsx @@ -0,0 +1,87 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForGoEdge } from "@/domain/access"; + +type AccessFormGoEdgeConfigFieldValues = Nullish; + +export type AccessFormGoEdgeConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormGoEdgeConfigFieldValues; + onValuesChange?: (values: AccessFormGoEdgeConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormGoEdgeConfigFieldValues => { + return { + apiUrl: "http://:7788/", + accessKeyId: "", + accessKey: "", + }; +}; + +const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormGoEdgeConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + 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(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormGoEdgeConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index a9c83a65..e2e4f811 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -47,6 +47,7 @@ import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig"; import DeployNodeConfigFormGcoreCDNConfig from "./DeployNodeConfigFormGcoreCDNConfig"; +import DeployNodeConfigFormGoEdgeConfig from "./DeployNodeConfigFormGoEdgeConfig"; import DeployNodeConfigFormHuaweiCloudCDNConfig from "./DeployNodeConfigFormHuaweiCloudCDNConfig"; import DeployNodeConfigFormHuaweiCloudELBConfig from "./DeployNodeConfigFormHuaweiCloudELBConfig"; import DeployNodeConfigFormHuaweiCloudWAFConfig from "./DeployNodeConfigFormHuaweiCloudWAFConfig"; @@ -238,6 +239,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.GCORE_CDN: return ; + case DEPLOYMENT_PROVIDERS.GOEDGE: + return ; case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_CDN: return ; case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx new file mode 100644 index 00000000..89dffb5f --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx @@ -0,0 +1,79 @@ +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 DeployNodeConfigFormGoEdgeConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; +}>; + +export type DeployNodeConfigFormGoEdgeConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormGoEdgeConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormGoEdgeConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormGoEdgeConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormGoEdgeConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.goedge_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.goedge_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + + + + +
+ ); +}; + +export default DeployNodeConfigFormGoEdgeConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index b7c54306..f28de47b 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -31,6 +31,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForGcore | AccessConfigForGname | AccessConfigForGoDaddy + | AccessConfigForGoEdge | AccessConfigForGoogleTrustServices | AccessConfigForHuaweiCloud | AccessConfigForJDCloud @@ -194,6 +195,12 @@ export type AccessConfigForGoDaddy = { apiSecret: string; }; +export type AccessConfigForGoEdge = { + apiUrl: string; + accessKeyId: string; + accessKey: string; +}; + export type AccessConfigForGoogleTrustServices = { eabKid: string; eabHmacKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 219a2713..ea8744f4 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -30,6 +30,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", + GOEDGE: "goedge", GOOGLETRUSTSERVICES: "googletrustservices", HUAWEICLOUD: "huaweicloud", JDCLOUD: "jdcloud", @@ -118,6 +119,7 @@ export const accessProvidersMap: Maphttps://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.tooltip": "For more information, see https://goedge.cloud/docs/API/Summary.md", + "access.form.goedge_access_key_id.label": "GoEdge user AccessKeyId", + "access.form.goedge_access_key_id.placeholder": "Please enter GoEdge user AccessKeyId", + "access.form.goedge_access_key_id.tooltip": "For more information, see https://goedge.cloud/docs/API/Auth.md", + "access.form.goedge_access_key.label": "GoEdge user AccessKey", + "access.form.goedge_access_key.placeholder": "Please enter GoEdge user AccessKey", + "access.form.goedge_access_key.tooltip": "For more information, see https://goedge.cloud/docs/API/Auth.md", "access.form.googletrustservices_eab_kid.label": "ACME EAB KID", "access.form.googletrustservices_eab_kid.placeholder": "Please enter ACME EAB KID", "access.form.googletrustservices_eab_kid.tooltip": "For more information, see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 6beb2a0a..8a191665 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -67,7 +67,6 @@ "provider.gname": "GNAME", "provider.godaddy": "GoDaddy", "provider.goedge": "GoEdge", - "provider.goedge.cdn": "GoEdge - CDN (Content Delivery Network)", "provider.googletrustservices": "Google Trust Services", "provider.huaweicloud": "Huawei Cloud", "provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 08a59a43..6ebbba63 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -351,6 +351,11 @@ "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", + "workflow_node.deploy.form.goedge_resource_type.label": "Resource type", + "workflow_node.deploy.form.goedge_resource_type.placeholder": "Please select resource type", + "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.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 04f74b58..5cb1a038 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -194,6 +194,15 @@ "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.tooltip": "这是什么?请参阅 https://goedge.cloud/docs/API/Summary.md", + "access.form.goedge_access_key_id.label": "GoEdge 用户 AccessKeyId", + "access.form.goedge_access_key_id.placeholder": "请输入 GoEdge 用户 AccessKeyId", + "access.form.goedge_access_key_id.tooltip": "这是什么?请参阅 https://goedge.cloud/docs/API/Auth.md", + "access.form.goedge_access_key.label": "GoEdge 用户 AccessKey", + "access.form.goedge_access_key.placeholder": "请输入 GoEdge 用户 AccessKey", + "access.form.goedge_access_key.tooltip": "这是什么?请参阅 https://goedge.cloud/docs/API/Auth.md", "access.form.googletrustservices_eab_kid.label": "ACME EAB KID", "access.form.googletrustservices_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.googletrustservices_eab_kid.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 00e45abf..207dfc37 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -67,7 +67,6 @@ "provider.gname": "GNAME", "provider.godaddy": "GoDaddy", "provider.goedge": "GoEdge", - "provider.goedge.cdn": "GoEdge - 内容分发网络 CDN", "provider.googletrustservices": "Google Trust Services", "provider.huaweicloud": "华为云", "provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 2233e93e..28e4b54d 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -350,6 +350,11 @@ "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.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.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",