From e43010922821e06c7cac71efeeb1bfeb3a7ee1ef Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 14 Jan 2025 21:02:08 +0800 Subject: [PATCH 1/5] feat: add ucloud ussl uploader --- go.mod | 2 + go.sum | 5 + .../providers/ucloud-ussl/ucloud_ussl.go | 220 ++++++++++++++++++ .../providers/ucloud-ussl/ucloud_ussl_test.go | 72 ++++++ .../volcengine_certcenter.go | 12 +- internal/pkg/vendors/ucloud-sdk/ussl/apis.go | 161 +++++++++++++ .../pkg/vendors/ucloud-sdk/ussl/client.go | 18 ++ .../pkg/vendors/ucloud-sdk/ussl/models.go | 61 +++++ 8 files changed, 545 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go create mode 100644 internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl_test.go create mode 100644 internal/pkg/vendors/ucloud-sdk/ussl/apis.go create mode 100644 internal/pkg/vendors/ucloud-sdk/ussl/client.go create mode 100644 internal/pkg/vendors/ucloud-sdk/ussl/models.go diff --git a/go.mod b/go.mod index 6d8a5d7d..6a4eed0e 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,9 @@ require ( github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/ucloud/ucloud-sdk-go v0.22.31 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index 16cf7f56..8e6555c8 100644 --- a/go.sum +++ b/go.sum @@ -783,10 +783,13 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -847,6 +850,8 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ucloud/ucloud-sdk-go v0.22.31 h1:izZK+Re9ZkJAd1fHSVpFzgh8uKda4f5G6++iUw4n/mE= +github.com/ucloud/ucloud-sdk-go v0.22.31/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go new file mode 100644 index 00000000..a8bc7173 --- /dev/null +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go @@ -0,0 +1,220 @@ +package ucloudussl + +import ( + "context" + "crypto/md5" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + xerrors "github.com/pkg/errors" + usdk "github.com/ucloud/ucloud-sdk-go/ucloud" + uAuth "github.com/ucloud/ucloud-sdk-go/ucloud/auth" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + x509util "github.com/usual2970/certimate/internal/pkg/utils/x509" + usdkSsl "github.com/usual2970/certimate/internal/pkg/vendors/ucloud-sdk/ussl" +) + +type UCloudUSSLUploaderConfig struct { + // 优刻得 API 私钥。 + PrivateKey string `json:"privateKey"` + // 优刻得 API 公钥。 + PublicKey string `json:"publicKey"` + // 优刻得项目 ID。 + ProjectId string `json:"projectId,omitempty"` +} + +type UCloudUSSLUploader struct { + config *UCloudUSSLUploaderConfig + sdkClient *usdkSsl.USSLClient +} + +var _ uploader.Uploader = (*UCloudUSSLUploader)(nil) + +func New(config *UCloudUSSLUploaderConfig) (*UCloudUSSLUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + client, err := createSdkClient(config.PrivateKey, config.PublicKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &UCloudUSSLUploader{ + config: config, + sdkClient: client, + }, nil +} + +func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 生成新证书名(需符合优刻得命名规则) + var certId, certName string + certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + + // 生成优刻得所需的证书参数 + certPemBase64 := base64.StdEncoding.EncodeToString([]byte(certPem)) + privkeyPemBase64 := base64.StdEncoding.EncodeToString([]byte(privkeyPem)) + certMd5 := md5.Sum([]byte(certPemBase64 + privkeyPemBase64)) + certMd5Hex := hex.EncodeToString(certMd5[:]) + + // 上传托管证书 + // REF: https://docs.ucloud.cn/api/usslcertificate-api/upload_normal_certificate + uploadNormalCertificateReq := u.sdkClient.NewUploadNormalCertificateRequest() + uploadNormalCertificateReq.CertificateName = usdk.String(certName) + uploadNormalCertificateReq.SslPublicKey = usdk.String(certPemBase64) + uploadNormalCertificateReq.SslPrivateKey = usdk.String(privkeyPemBase64) + uploadNormalCertificateReq.SslMD5 = usdk.String(certMd5Hex) + if u.config.ProjectId != "" { + uploadNormalCertificateReq.ProjectId = usdk.String(u.config.ProjectId) + } + uploadNormalCertificateResp, err := u.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq) + if err != nil { + if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { + return u.getExistCert(ctx, certPem, privkeyPem) + } + + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.UploadNormalCertificate'") + } + + certId = fmt.Sprintf("%d", uploadNormalCertificateResp.CertificateID) + return &uploader.UploadResult{ + CertId: certId, + CertName: certName, + ExtendedData: map[string]interface{}{ + "resourceId": uploadNormalCertificateResp.LongResourceID, + }, + }, nil +} + +func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 解析证书内容 + certX509, err := x509util.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + // 遍历获取用户证书列表,避免重复上传 + // REF: https://docs.ucloud.cn/api/usslcertificate-api/get_certificate_list + // REF: https://docs.ucloud.cn/api/usslcertificate-api/download_certificate + getCertificateListPage := int(1) + getCertificateListLimit := int(1000) + for { + getCertificateListReq := u.sdkClient.NewGetCertificateListRequest() + getCertificateListReq.Mode = usdk.String("trust") + getCertificateListReq.Domain = usdk.String(certX509.Subject.CommonName) + getCertificateListReq.Sort = usdk.String("2") + getCertificateListReq.Page = usdk.Int(getCertificateListPage) + getCertificateListReq.PageSize = usdk.Int(getCertificateListLimit) + if u.config.ProjectId != "" { + getCertificateListReq.ProjectId = usdk.String(u.config.ProjectId) + } + getCertificateListResp, err := u.sdkClient.GetCertificateList(getCertificateListReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.GetCertificateList'") + } + + if getCertificateListResp.CertificateList != nil { + for _, certInfo := range getCertificateListResp.CertificateList { + // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试匹配来判断是否为同一证书 + // 先分别匹配证书的域名、品牌、有效期,再匹配签名算法 + + if len(certX509.DNSNames) == 0 || certInfo.Domains != strings.Join(certX509.DNSNames, ",") { + continue + } + + if len(certX509.Issuer.Organization) == 0 || certInfo.Brand != certX509.Issuer.Organization[0] { + continue + } + + if int64(certInfo.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certInfo.NotAfter) != certX509.NotAfter.UnixMilli() { + continue + } + + getCertificateDetailInfoReq := u.sdkClient.NewGetCertificateDetailInfoRequest() + getCertificateDetailInfoReq.CertificateID = usdk.Int(certInfo.CertificateID) + if u.config.ProjectId != "" { + getCertificateDetailInfoReq.ProjectId = usdk.String(u.config.ProjectId) + } + getCertificateDetailInfoResp, err := u.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.GetCertificateDetailInfo'") + } + + switch certX509.SignatureAlgorithm { + case x509.SHA256WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") { + continue + } + case x509.SHA384WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") { + continue + } + case x509.SHA512WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") { + continue + } + case x509.SHA256WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") { + continue + } + case x509.SHA384WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") { + continue + } + case x509.SHA512WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") { + continue + } + case x509.ECDSAWithSHA256: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") { + continue + } + case x509.ECDSAWithSHA384: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") { + continue + } + case x509.ECDSAWithSHA512: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") { + continue + } + default: + // 未知签名算法,跳过 + continue + } + + return &uploader.UploadResult{ + CertId: fmt.Sprintf("%d", certInfo.CertificateID), + CertName: certInfo.Name, + ExtendedData: map[string]interface{}{ + "resourceId": certInfo.CertificateSN, + }, + }, nil + } + } + + if getCertificateListResp.CertificateList == nil || len(getCertificateListResp.CertificateList) < int(getCertificateListLimit) { + break + } else { + getCertificateListPage++ + } + } + + return nil, errors.New("no certificate found") +} + +func createSdkClient(privateKey, publicKey string) (*usdkSsl.USSLClient, error) { + cfg := usdk.NewConfig() + + credential := uAuth.NewCredential() + credential.PrivateKey = privateKey + credential.PublicKey = publicKey + + client := usdkSsl.NewClient(&cfg, &credential) + return client, nil +} diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl_test.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl_test.go new file mode 100644 index 00000000..c0a0f719 --- /dev/null +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl_test.go @@ -0,0 +1,72 @@ +package ucloudussl_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" +) + +var ( + fInputCertPath string + fInputKeyPath string + fPrivateKey string + fPublicKey string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_UCLOUDUSSL_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fPrivateKey, argsPrefix+"PRIVATEKEY", "", "") + flag.StringVar(&fPublicKey, argsPrefix+"PUBLICKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ucloud_ussl_test.go -args \ + --CERTIMATE_UPLOADER_UCLOUDUSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_UCLOUDUSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_UPLOADER_UCLOUDUSSL_PRIVATEKEY="your-private-key" \ + --CERTIMATE_UPLOADER_UCLOUDUSSL_PUBLICKEY="your-public-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("PRIVATEKEY: %v", fPrivateKey), + fmt.Sprintf("PUBLICKEY: %v", fPublicKey), + }, "\n")) + + uploader, err := provider.New(&provider.UCloudUSSLUploaderConfig{ + PrivateKey: fPrivateKey, + PublicKey: fPublicKey, + }) + 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/core/uploader/providers/volcengine-certcenter/volcengine_certcenter.go b/internal/pkg/core/uploader/providers/volcengine-certcenter/volcengine_certcenter.go index e1fa00f2..5e301ebd 100644 --- a/internal/pkg/core/uploader/providers/volcengine-certcenter/volcengine_certcenter.go +++ b/internal/pkg/core/uploader/providers/volcengine-certcenter/volcengine_certcenter.go @@ -9,7 +9,7 @@ import ( veSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" "github.com/usual2970/certimate/internal/pkg/core/uploader" - veCertCenter "github.com/usual2970/certimate/internal/pkg/vendors/volcengine-sdk/certcenter" + vesdkCc "github.com/usual2970/certimate/internal/pkg/vendors/volcengine-sdk/certcenter" ) type VolcEngineCertCenterUploaderConfig struct { @@ -23,7 +23,7 @@ type VolcEngineCertCenterUploaderConfig struct { type VolcEngineCertCenterUploader struct { config *VolcEngineCertCenterUploaderConfig - sdkClient *veCertCenter.CertCenter + sdkClient *vesdkCc.CertCenter } var _ uploader.Uploader = (*VolcEngineCertCenterUploader)(nil) @@ -47,8 +47,8 @@ func New(config *VolcEngineCertCenterUploaderConfig) (*VolcEngineCertCenterUploa func (u *VolcEngineCertCenterUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { // 上传证书 // REF: https://www.volcengine.com/docs/6638/1365580 - importCertificateReq := &veCertCenter.ImportCertificateInput{ - CertificateInfo: &veCertCenter.ImportCertificateInputCertificateInfo{ + importCertificateReq := &vesdkCc.ImportCertificateInput{ + CertificateInfo: &vesdkCc.ImportCertificateInputCertificateInfo{ CertificateChain: ve.String(certPem), PrivateKey: ve.String(privkeyPem), }, @@ -71,7 +71,7 @@ func (u *VolcEngineCertCenterUploader) Upload(ctx context.Context, certPem strin }, nil } -func createSdkClient(accessKeyId, accessKeySecret, region string) (*veCertCenter.CertCenter, error) { +func createSdkClient(accessKeyId, accessKeySecret, region string) (*vesdkCc.CertCenter, error) { if region == "" { region = "cn-beijing" // 证书中心默认区域:北京 } @@ -83,6 +83,6 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*veCertCenter return nil, err } - client := veCertCenter.New(session) + client := vesdkCc.New(session) return client, nil } diff --git a/internal/pkg/vendors/ucloud-sdk/ussl/apis.go b/internal/pkg/vendors/ucloud-sdk/ussl/apis.go new file mode 100644 index 00000000..5d0c6e32 --- /dev/null +++ b/internal/pkg/vendors/ucloud-sdk/ussl/apis.go @@ -0,0 +1,161 @@ +package ussl + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + "github.com/ucloud/ucloud-sdk-go/ucloud/response" +) + +type UploadNormalCertificateRequest struct { + request.CommonBase + + CertificateName *string `required:"true"` + SslPublicKey *string `required:"true"` + SslPrivateKey *string `required:"true"` + SslMD5 *string `required:"true"` + SslCaKey *string `required:"false"` +} + +type UploadNormalCertificateResponse struct { + response.CommonBase + + CertificateID int + LongResourceID string +} + +func (c *USSLClient) NewUploadNormalCertificateRequest() *UploadNormalCertificateRequest { + req := &UploadNormalCertificateRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *USSLClient) UploadNormalCertificate(req *UploadNormalCertificateRequest) (*UploadNormalCertificateResponse, error) { + var err error + var res UploadNormalCertificateResponse + + reqCopier := *req + + err = c.Client.InvokeAction("UploadNormalCertificate", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} + +type GetCertificateListRequest struct { + request.CommonBase + + Mode *string `required:"true"` + StateCode *string `required:"false"` + Brand *string `required:"false"` + CaOrganization *string `required:"false"` + Domain *string `required:"false"` + Sort *string `required:"false"` + Page *int `required:"false"` + PageSize *int `required:"false"` +} + +type GetCertificateListResponse struct { + response.CommonBase + + CertificateList []*CertificateListItem + TotalCount int +} + +func (c *USSLClient) NewGetCertificateListRequest() *GetCertificateListRequest { + req := &GetCertificateListRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *USSLClient) GetCertificateList(req *GetCertificateListRequest) (*GetCertificateListResponse, error) { + var err error + var res GetCertificateListResponse + + reqCopier := *req + + err = c.Client.InvokeAction("GetCertificateList", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} + +type GetCertificateDetailInfoRequest struct { + request.CommonBase + + CertificateID *int `required:"true"` +} + +type GetCertificateDetailInfoResponse struct { + response.CommonBase + + CertificateInfo *CertificateInfo +} + +func (c *USSLClient) NewGetCertificateDetailInfoRequest() *GetCertificateDetailInfoRequest { + req := &GetCertificateDetailInfoRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *USSLClient) GetCertificateDetailInfo(req *GetCertificateDetailInfoRequest) (*GetCertificateDetailInfoResponse, error) { + var err error + var res GetCertificateDetailInfoResponse + + reqCopier := *req + + err = c.Client.InvokeAction("GetCertificateDetailInfo", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} + +type DownloadCertificateRequest struct { + request.CommonBase + + CertificateID *int `required:"true"` +} + +type DownloadCertificateResponse struct { + response.CommonBase + + CertificateUrl string + CertCA *CertificateDownloadInfo + Certificate *CertificateDownloadInfo +} + +func (c *USSLClient) NewDownloadCertificateRequest() *DownloadCertificateRequest { + req := &DownloadCertificateRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *USSLClient) DownloadCertificate(req *DownloadCertificateRequest) (*DownloadCertificateResponse, error) { + var err error + var res DownloadCertificateResponse + + reqCopier := *req + + err = c.Client.InvokeAction("DownloadCertificate", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} diff --git a/internal/pkg/vendors/ucloud-sdk/ussl/client.go b/internal/pkg/vendors/ucloud-sdk/ussl/client.go new file mode 100644 index 00000000..6d92a204 --- /dev/null +++ b/internal/pkg/vendors/ucloud-sdk/ussl/client.go @@ -0,0 +1,18 @@ +package ussl + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/auth" +) + +type USSLClient struct { + *ucloud.Client +} + +func NewClient(config *ucloud.Config, credential *auth.Credential) *USSLClient { + meta := ucloud.ClientMeta{Product: "USSL"} + client := ucloud.NewClientWithMeta(config, credential, meta) + return &USSLClient{ + client, + } +} diff --git a/internal/pkg/vendors/ucloud-sdk/ussl/models.go b/internal/pkg/vendors/ucloud-sdk/ussl/models.go new file mode 100644 index 00000000..50343d9f --- /dev/null +++ b/internal/pkg/vendors/ucloud-sdk/ussl/models.go @@ -0,0 +1,61 @@ +package ussl + +type CertificateListItem struct { + CertificateID int + CertificateSN string + CertificateCat string + Mode string + Domains string + Brand string + ValidityPeriod int + Type string + NotBefore int + NotAfter int + AlarmState int + State string + StateCode string + Name string + MaxDomainsCount int + DomainsCount int + CaChannel string + CSRAlgorithms []CSRAlgorithmInfo + TopOrganizationID int + OrganizationID int + IsFree int + YearOfValidity int + Channel int + CreateTime int + CertificateUrl string +} + +type CSRAlgorithmInfo struct { + Algorithm string + AlgorithmOption []string +} + +type CertificateInfo struct { + Type string + CertificateID int + CertificateType string + CaOrganization string + Algorithm string + ValidityPeriod int + State string + StateCode string + Name string + Brand string + Domains string + DomainsCount int + Mode string + CSROnline int + CSR string + CSRKeyParameter string + CSREncryptAlgo string + IssuedDate int + ExpiredDate int +} + +type CertificateDownloadInfo struct { + FileData string + FileName string +} From e87ac72281efeda5b337163e107320ccae212726 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 14 Jan 2025 21:31:10 +0800 Subject: [PATCH 2/5] feat: add ucloud ucdn deployer --- internal/deployer/providers.go | 23 +++ internal/domain/access.go | 5 + internal/domain/provider.go | 2 + .../providers/ucloud-ucdn/ucloud_ucdn.go | 132 ++++++++++++++++++ .../providers/ucloud-ucdn/ucloud_ucdn_test.go | 75 ++++++++++ migrations/1736861196_updated_access.go | 113 +++++++++++++++ ui/public/imgs/providers/ucloud.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormUCloudConfig.tsx | 90 ++++++++++++ .../provider/ApplyDNSProviderPicker.tsx | 2 +- .../provider/DeployProviderPicker.tsx | 2 +- .../components/workflow/node/DeployNode.tsx | 8 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormUCloudUCDNConfig.tsx | 63 +++++++++ .../components/workflow/node/NotifyNode.tsx | 8 +- 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.common.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 3 + ui/src/i18n/locales/zh/nls.access.json | 9 ++ ui/src/i18n/locales/zh/nls.common.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 3 + 23 files changed, 559 insertions(+), 10 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go create mode 100644 internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go create mode 100644 migrations/1736861196_updated_access.go create mode 100644 ui/public/imgs/providers/ucloud.svg create mode 100644 ui/src/components/access/AccessFormUCloudConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormUCloudUCDNConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 35a818f4..31d84baf 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -27,6 +27,7 @@ import ( providerTencentCloudCSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-css" providerTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" providerTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo" + providerUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" providerVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" providerVolcEngineCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-clb" providerVolcEngineDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-dcdn" @@ -352,6 +353,28 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, } } + case domain.DeployProviderTypeUCloudUCDN: + { + access := domain.AccessConfigForUCloud{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderTypeUCloudUCDN: + deployer, err := providerUCloudUCDN.NewWithLogger(&providerUCloudUCDN.UCloudUCDNDeployerConfig{ + PrivateKey: access.PrivateKey, + PublicKey: access.PublicKey, + ProjectId: maps.GetValueAsString(options.ProviderDeployConfig, "projectId"), + DomainId: maps.GetValueAsString(options.ProviderDeployConfig, "domainId"), + }, logger) + return deployer, logger, err + + default: + break + } + } + case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS: { access := domain.AccessConfigForVolcEngine{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 6ee3b052..e1979ea3 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -115,6 +115,11 @@ type AccessConfigForTencentCloud struct { SecretKey string `json:"secretKey"` } +type AccessConfigForUCloud struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` +} + type AccessConfigForVolcEngine struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 7d3f2464..d5b844a9 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -27,6 +27,7 @@ const ( AccessProviderTypeQiniu = AccessProviderType("qiniu") AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") + AccessProviderTypeUCloud = AccessProviderType("ucloud") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeWebhook = AccessProviderType("webhook") ) @@ -92,6 +93,7 @@ const ( DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") + DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") diff --git a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go new file mode 100644 index 00000000..ec127f41 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go @@ -0,0 +1,132 @@ +package uclouducdn + +import ( + "context" + "errors" + "strconv" + + xerrors "github.com/pkg/errors" + uCdn "github.com/ucloud/ucloud-sdk-go/services/ucdn" + usdk "github.com/ucloud/ucloud-sdk-go/ucloud" + uAuth "github.com/ucloud/ucloud-sdk-go/ucloud/auth" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" +) + +type UCloudUCDNDeployerConfig struct { + // 优刻得 API 私钥。 + PrivateKey string `json:"privateKey"` + // 优刻得 API 公钥。 + PublicKey string `json:"publicKey"` + // 优刻得项目 ID。 + ProjectId string `json:"projectId,omitempty"` + // 加速域名 ID。 + DomainId string `json:"domainId"` +} + +type UCloudUCDNDeployer struct { + config *UCloudUCDNDeployerConfig + logger logger.Logger + sdkClient *uCdn.UCDNClient + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*UCloudUCDNDeployer)(nil) + +func New(config *UCloudUCDNDeployerConfig) (*UCloudUCDNDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *UCloudUCDNDeployerConfig, logger logger.Logger) (*UCloudUCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.PrivateKey, config.PublicKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{ + PrivateKey: config.PrivateKey, + PublicKey: config.PublicKey, + ProjectId: config.ProjectId, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &UCloudUCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *UCloudUCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 USSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Logt("certificate file uploaded", upres) + + // 获取加速域名配置 + // REF: https://docs.ucloud.cn/api/ucdn-api/get_ucdn_domain_config + getUcdnDomainConfigReq := d.sdkClient.NewGetUcdnDomainConfigRequest() + getUcdnDomainConfigReq.DomainId = []string{d.config.DomainId} + if d.config.ProjectId != "" { + getUcdnDomainConfigReq.ProjectId = usdk.String(d.config.ProjectId) + } + getUcdnDomainConfigResp, err := d.sdkClient.GetUcdnDomainConfig(getUcdnDomainConfigReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ucdn.GetUcdnDomainConfig'") + } else if len(getUcdnDomainConfigResp.DomainList) == 0 { + return nil, errors.New("no domain found") + } + + d.logger.Logt("已查询到加速域名配置", getUcdnDomainConfigResp) + + // 更新 HTTPS 加速配置 + // REF: https://docs.ucloud.cn/api/ucdn-api/update_ucdn_domain_https_config_v2 + certId, _ := strconv.Atoi(upres.CertId) + updateUcdnDomainHttpsConfigV2Req := d.sdkClient.NewUpdateUcdnDomainHttpsConfigV2Request() + updateUcdnDomainHttpsConfigV2Req.DomainId = usdk.String(d.config.DomainId) + updateUcdnDomainHttpsConfigV2Req.HttpsStatusCn = usdk.String(getUcdnDomainConfigResp.DomainList[0].HttpsStatusCn) + updateUcdnDomainHttpsConfigV2Req.HttpsStatusAbroad = usdk.String(getUcdnDomainConfigResp.DomainList[0].HttpsStatusAbroad) + updateUcdnDomainHttpsConfigV2Req.HttpsStatusAbroad = usdk.String(getUcdnDomainConfigResp.DomainList[0].HttpsStatusAbroad) + updateUcdnDomainHttpsConfigV2Req.CertId = usdk.Int(certId) + updateUcdnDomainHttpsConfigV2Req.CertName = usdk.String(upres.CertName) + updateUcdnDomainHttpsConfigV2Req.CertType = usdk.String("ussl") + if d.config.ProjectId != "" { + updateUcdnDomainHttpsConfigV2Req.ProjectId = usdk.String(d.config.ProjectId) + } + updateUcdnDomainHttpsConfigV2Resp, err := d.sdkClient.UpdateUcdnDomainHttpsConfigV2(updateUcdnDomainHttpsConfigV2Req) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ucdn.UpdateUcdnDomainHttpsConfigV2'") + } + + d.logger.Logt("已更新 HTTPS 加速配置", updateUcdnDomainHttpsConfigV2Resp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(privateKey, publicKey string) (*uCdn.UCDNClient, error) { + cfg := usdk.NewConfig() + + credential := uAuth.NewCredential() + credential.PrivateKey = privateKey + credential.PublicKey = publicKey + + client := uCdn.NewClient(&cfg, &credential) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go new file mode 100644 index 00000000..ba285d71 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go @@ -0,0 +1,75 @@ +package uclouducdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fPrivateKey string + fPublicKey string + fDomainId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_UCLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fPrivateKey, argsPrefix+"PRIVATEKEY", "", "") + flag.StringVar(&fPublicKey, argsPrefix+"PUBLICKEY", "", "") + flag.StringVar(&fDomainId, argsPrefix+"DOMAINID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ucloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_UCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDCDN_PRIVATEKEY="your-private-key" \ + --CERTIMATE_DEPLOYER_UCLOUDCDN_PUBLICKEY="your-public-key" \ + --CERTIMATE_DEPLOYER_UCLOUDCDN_DOMAINID="your-domain-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("PRIVATEKEY: %v", fPrivateKey), + fmt.Sprintf("PUBLICKEY: %v", fPublicKey), + fmt.Sprintf("DOMAIN: %v", fDomainId), + }, "\n")) + + deployer, err := provider.New(&provider.UCloudUCDNDeployerConfig{ + AccessKeyId: fPrivateKey, + AccessKeySecret: fPublicKey, + DomainId: fDomainId, + }) + 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/migrations/1736861196_updated_access.go b/migrations/1736861196_updated_access.go new file mode 100644 index 00000000..8d6023bc --- /dev/null +++ b/migrations/1736861196_updated_access.go @@ -0,0 +1,113 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update + edit_provider := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hwy7m03o", + "name": "provider", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "acmehttpreq", + "aliyun", + "aws", + "azure", + "baiducloud", + "byteplus", + "cloudflare", + "dogecloud", + "godaddy", + "huaweicloud", + "k8s", + "local", + "namedotcom", + "namesilo", + "powerdns", + "qiniu", + "ssh", + "tencentcloud", + "ucloud", + "volcengine", + "webhook" + ] + } + }`), edit_provider); err != nil { + return err + } + collection.Schema.AddField(edit_provider) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update + edit_provider := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hwy7m03o", + "name": "provider", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "acmehttpreq", + "aliyun", + "aws", + "azure", + "baiducloud", + "byteplus", + "cloudflare", + "dogecloud", + "godaddy", + "huaweicloud", + "k8s", + "local", + "namedotcom", + "namesilo", + "powerdns", + "qiniu", + "ssh", + "tencentcloud", + "volcengine", + "webhook" + ] + } + }`), edit_provider); err != nil { + return err + } + collection.Schema.AddField(edit_provider) + + return dao.SaveCollection(collection) + }) +} diff --git a/ui/public/imgs/providers/ucloud.svg b/ui/public/imgs/providers/ucloud.svg new file mode 100644 index 00000000..9e246c65 --- /dev/null +++ b/ui/public/imgs/providers/ucloud.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 96f804ed..b398013a 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -27,6 +27,7 @@ import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; import AccessFormQiniuConfig from "./AccessFormQiniuConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; +import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; @@ -118,6 +119,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.TENCENTCLOUD: return ; + case ACCESS_PROVIDERS.UCLOUD: + return ; case ACCESS_PROVIDERS.VOLCENGINE: return ; case ACCESS_PROVIDERS.WEBHOOK: diff --git a/ui/src/components/access/AccessFormUCloudConfig.tsx b/ui/src/components/access/AccessFormUCloudConfig.tsx new file mode 100644 index 00000000..f230e7c2 --- /dev/null +++ b/ui/src/components/access/AccessFormUCloudConfig.tsx @@ -0,0 +1,90 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForUCloud } from "@/domain/access"; + +type AccessFormUCloudConfigFieldValues = Nullish; + +export type AccessFormUCloudConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormUCloudConfigFieldValues; + onValuesChange?: (values: AccessFormUCloudConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormUCloudConfigFieldValues => { + return { + privateKey: "", + publicKey: "", + }; +}; + +const AccessFormUCloudConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormUCloudConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + privateKey: z + .string() + .trim() + .min(1, t("access.form.ucloud_private_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + publicKey: z + .string() + .min(1, t("access.form.ucloud_public_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + projectId: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim() + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormUCloudConfig; diff --git a/ui/src/components/provider/ApplyDNSProviderPicker.tsx b/ui/src/components/provider/ApplyDNSProviderPicker.tsx index e72935cc..88d9ebc6 100644 --- a/ui/src/components/provider/ApplyDNSProviderPicker.tsx +++ b/ui/src/components/provider/ApplyDNSProviderPicker.tsx @@ -51,7 +51,7 @@ const ApplyDNSProviderPicker = ({ className, style, placeholder, onSelect }: App > - {t(provider.name)} + {t(provider.name)} diff --git a/ui/src/components/provider/DeployProviderPicker.tsx b/ui/src/components/provider/DeployProviderPicker.tsx index 29ad5f46..159bd53a 100644 --- a/ui/src/components/provider/DeployProviderPicker.tsx +++ b/ui/src/components/provider/DeployProviderPicker.tsx @@ -51,7 +51,7 @@ const DeployProviderPicker = ({ className, style, placeholder, onSelect }: Deplo > - {t(provider.name)} + {t(provider.name)} diff --git a/ui/src/components/workflow/node/DeployNode.tsx b/ui/src/components/workflow/node/DeployNode.tsx index 8a211a67..62a81470 100644 --- a/ui/src/components/workflow/node/DeployNode.tsx +++ b/ui/src/components/workflow/node/DeployNode.tsx @@ -1,6 +1,6 @@ import { memo, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Avatar, Space, Typography } from "antd"; +import { Avatar, Flex, Typography } from "antd"; import { produce } from "immer"; import { deployProvidersMap } from "@/domain/provider"; @@ -45,10 +45,10 @@ const DeployNode = ({ node, disabled }: DeployNodeProps) => { const config = (node.config as WorkflowNodeConfigForDeploy) ?? {}; const provider = deployProvidersMap.get(config.provider); return ( - + - {t(provider?.name ?? "")} - + {t(provider?.name ?? "")} + ); }, [node]); diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 2643b2c7..99c427f5 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -37,6 +37,7 @@ import DeployNodeConfigFormTencentCloudCOSConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudCSSConfig from "./DeployNodeConfigFormTencentCloudCSSConfig.tsx"; import DeployNodeConfigFormTencentCloudECDNConfig from "./DeployNodeConfigFormTencentCloudECDNConfig.tsx"; import DeployNodeConfigFormTencentCloudEOConfig from "./DeployNodeConfigFormTencentCloudEOConfig.tsx"; +import DeployNodeConfigFormUCloudUCDNConfig from "./DeployNodeConfigFormUCloudUCDNConfig.tsx"; import DeployNodeConfigFormVolcEngineCDNConfig from "./DeployNodeConfigFormVolcEngineCDNConfig.tsx"; import DeployNodeConfigFormVolcEngineCLBConfig from "./DeployNodeConfigFormVolcEngineCLBConfig.tsx"; import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolcEngineDCDNConfig.tsx"; @@ -156,6 +157,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.TENCENTCLOUD_EO: return ; + case DEPLOY_PROVIDERS.UCLOUD_UCDN: + return ; case DEPLOY_PROVIDERS.VOLCENGINE_CDN: return ; case DEPLOY_PROVIDERS.VOLCENGINE_CLB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUCDNConfig.tsx new file mode 100644 index 00000000..41c059fc --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUCDNConfig.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 DeployNodeConfigFormUCloudUCDNConfigFieldValues = Nullish<{ + domainId: string; +}>; + +export type DeployNodeConfigFormUCloudUCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUCloudUCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUCloudUCDNConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUCloudUCDNConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormUCloudUCDNConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormUCloudUCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domainId: z + .string({ message: t("workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder") }) + .nonempty(t("workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormUCloudUCDNConfig; diff --git a/ui/src/components/workflow/node/NotifyNode.tsx b/ui/src/components/workflow/node/NotifyNode.tsx index 70233c50..1b739e8f 100644 --- a/ui/src/components/workflow/node/NotifyNode.tsx +++ b/ui/src/components/workflow/node/NotifyNode.tsx @@ -1,6 +1,6 @@ import { memo, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Typography } from "antd"; +import { Flex, Typography } from "antd"; import { produce } from "immer"; import { notifyChannelsMap } from "@/domain/settings"; @@ -40,12 +40,12 @@ const NotifyNode = ({ node, disabled }: NotifyNodeProps) => { const config = (node.config as WorkflowNodeConfigForNotify) ?? {}; const channel = notifyChannelsMap.get(config.channel as string); return ( -
- {t(channel?.name ?? " ")} + + {t(channel?.name ?? " ")} {config.subject ?? ""} -
+ ); }, [node]); diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 33002d83..01d97d04 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -26,6 +26,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForQiniu | AccessConfigForSSH | AccessConfigForTencentCloud + | AccessConfigForUCloud | AccessConfigForVolcEngine | AccessConfigForWebhook ); @@ -125,6 +126,12 @@ export type AccessConfigForTencentCloud = { secretKey: string; }; +export type AccessConfigForUCloud = { + privateKey: string; + publicKey: string; + projectId?: string; +}; + export type AccessConfigForVolcEngine = { accessKeyId: string; secretAccessKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 0874fbcc..a9876aad 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ QINIU: "qiniu", SSH: "ssh", TENCENTCLOUD: "tencentcloud", + UCLOUD: "ucloud", VOLCENGINE: "volcengine", WEBHOOK: "webhook", } as const); @@ -61,6 +62,7 @@ export const accessProvidersMap: Map [ type, { diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 41f738f8..7f9ead1d 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -121,6 +121,15 @@ "access.form.tencentcloud_secret_key.label": "Tencent Cloud SecretKey", "access.form.tencentcloud_secret_key.placeholder": "Please enter Tencent Cloud SecretKey", "access.form.tencentcloud_secret_key.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", + "access.form.ucloud_private_key.label": "UCloud API private key", + "access.form.ucloud_private_key.placeholder": "Please enter UCloud API private key", + "access.form.ucloud_private_key.tooltip": "For more information, see https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_public_key.label": "UCloud API public key", + "access.form.ucloud_public_key.placeholder": "Please enter UCloud API public key", + "access.form.ucloud_public_key.tooltip": "For more information, see https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_project_id.label": "UCloud project ID (Optional)", + "access.form.ucloud_project_id.placeholder": "Please enter UCloud project ID", + "access.form.ucloud_project_id.tooltip": "For more information, see https://console.ucloud.cn/uaccount/iam/project_manage", "access.form.volcengine_access_key_id.label": "VolcEngine AccessKeyId", "access.form.volcengine_access_key_id.placeholder": "Please enter VolcEngine AccessKeyId", "access.form.volcengine_access_key_id.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index c8c37a7e..0b673e32 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -78,6 +78,8 @@ "common.provider.tencentcloud.dns": "Tencent Cloud - Domain Name Service (DNS)", "common.provider.tencentcloud.ecdn": "Tencent Cloud - Enterprise Content Delivery Network (ECDN)", "common.provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", + "common.provider.ucloud": "UCloud", + "common.provider.ucloud.ucdn": "UCloud - UCloud Content Delivery Network (UCDN)", "common.provider.volcengine": "Volcengine", "common.provider.volcengine.cdn": "Volcengine - Content Delivery Network (CDN)", "common.provider.volcengine.clb": "Volcengine - Cloud Load Balancer (CLB)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 0ec16538..bbebd5a7 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -301,6 +301,9 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.label": "Tencent Cloud EdgeOne domain", "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "Please enter Tencent Cloud EdgeOne domain name", "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "UCloud UCDN domain ID", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "Please enter UCloud UCDN domain ID", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "For more information, see https://console.ucloud.cn/ucdn", "workflow_node.deploy.form.volcengine_cdn_domain.label": "VolcEngine CDN domain", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "Please enter VolcEngine CDN domain name", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "For more information, see https://console.volcengine.com/cdn/homepage", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 342610af..e24c125b 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -121,6 +121,15 @@ "access.form.tencentcloud_secret_key.label": "腾讯云 SecretKey", "access.form.tencentcloud_secret_key.placeholder": "请输入腾讯云 SecretKey", "access.form.tencentcloud_secret_key.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488", + "access.form.ucloud_private_key.label": "优刻得 API 私钥", + "access.form.ucloud_private_key.placeholder": "请输入优刻得 API 私钥", + "access.form.ucloud_private_key.tooltip": "这是什么?请参阅 https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_public_key.label": "优刻得 API 公钥", + "access.form.ucloud_public_key.placeholder": "请输入优刻得 API 公钥", + "access.form.ucloud_public_key.tooltip": "这是什么?请参阅 https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_project_id.label": "优刻得项目 ID(可选)", + "access.form.ucloud_project_id.placeholder": "请输入优刻得项目 ID", + "access.form.ucloud_project_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/uaccount/iam/project_manage", "access.form.volcengine_access_key_id.label": "火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.placeholder": "请输入火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 0a7d93cf..c35f19a6 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -78,6 +78,8 @@ "common.provider.tencentcloud.dns": "腾讯云 - 云解析 DNS", "common.provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", "common.provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", + "common.provider.ucloud": "优刻得", + "common.provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", "common.provider.volcengine": "火山引擎", "common.provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "common.provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index c06ff102..41450494 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -301,6 +301,9 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.label": "腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "请输入腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "优刻得 UCDN 域名 ID", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "请输入优刻得 UCDN 域名 ID", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ucdn", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage

泛域名表示形式为:*.example.com", From 3dd79d447b2dc60a4b6b42650874a4b72a011834 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 14 Jan 2025 21:34:22 +0800 Subject: [PATCH 3/5] update README --- README.md | 1 + README_EN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index f6b33ead..d630efbb 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ make local.run | [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN | | [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | | [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | +| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 UCDN 等服务 | diff --git a/README_EN.md b/README_EN.md index 153db7f0..65bcfac3 100644 --- a/README_EN.md +++ b/README_EN.md @@ -124,6 +124,7 @@ The following hosting providers are supported: | [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN | | [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | | [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | +| [UCloud](https://www.ucloud.cn/) | Supports deployment to UCloud UCDN | From 6a9cf2ed2882307f933b21dc7cde7d776b776d48 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 14 Jan 2025 21:46:09 +0800 Subject: [PATCH 4/5] feat(ui): improve responsive ui --- ui/src/components/provider/ApplyDNSProviderPicker.tsx | 2 +- ui/src/components/provider/DeployProviderPicker.tsx | 2 +- ui/src/pages/workflows/WorkflowDetail.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/components/provider/ApplyDNSProviderPicker.tsx b/ui/src/components/provider/ApplyDNSProviderPicker.tsx index 88d9ebc6..f297716f 100644 --- a/ui/src/components/provider/ApplyDNSProviderPicker.tsx +++ b/ui/src/components/provider/ApplyDNSProviderPicker.tsx @@ -40,7 +40,7 @@ const ApplyDNSProviderPicker = ({ className, style, placeholder, onSelect }: App {filteredProviders.map((provider, index) => { return ( - + {filteredProviders.map((provider, index) => { return ( - + { -
+
From db687218342cd9993c56f905a4ce292a522870c2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 14 Jan 2025 22:19:40 +0800 Subject: [PATCH 5/5] feat: add ucloud us3 deployer --- README.md | 2 +- README_EN.md | 2 +- go.mod | 2 +- internal/deployer/providers.go | 16 ++- internal/domain/access.go | 15 +-- internal/domain/provider.go | 1 + .../providers/ucloud-ucdn/ucloud_ucdn_test.go | 20 +-- .../providers/ucloud-us3/ucloud_us3.go | 116 ++++++++++++++++++ .../providers/ucloud-us3/ucloud_us3_test.go | 85 +++++++++++++ internal/pkg/vendors/ucloud-sdk/ufile/apis.go | 42 +++++++ .../pkg/vendors/ucloud-sdk/ufile/client.go | 18 +++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormUCloudUS3Config.tsx | 93 ++++++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.access.json | 6 +- ui/src/i18n/locales/en/nls.common.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 11 +- ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 ++ 19 files changed, 419 insertions(+), 26 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go create mode 100644 internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3_test.go create mode 100644 internal/pkg/vendors/ucloud-sdk/ufile/apis.go create mode 100644 internal/pkg/vendors/ucloud-sdk/ufile/client.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormUCloudUS3Config.tsx diff --git a/README.md b/README.md index d630efbb..3ab77300 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ make local.run | [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN | | [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | | [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | -| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 UCDN 等服务 | +| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | diff --git a/README_EN.md b/README_EN.md index 65bcfac3..f0f8338e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -124,7 +124,7 @@ The following hosting providers are supported: | [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN | | [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | | [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | -| [UCloud](https://www.ucloud.cn/) | Supports deployment to UCloud UCDN | +| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | diff --git a/go.mod b/go.mod index 6a4eed0e..1ff91571 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1080 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1065 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1065 + github.com/ucloud/ucloud-sdk-go v0.22.31 github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.8 github.com/volcengine/volc-sdk-golang v1.0.189 github.com/volcengine/volcengine-go-sdk v1.0.177 @@ -82,7 +83,6 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/ucloud/ucloud-sdk-go v0.22.31 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 31d84baf..6cb192f9 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -28,6 +28,7 @@ import ( providerTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" providerTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo" providerUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" + providerUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" providerVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" providerVolcEngineCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-clb" providerVolcEngineDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-dcdn" @@ -353,7 +354,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, } } - case domain.DeployProviderTypeUCloudUCDN: + case domain.DeployProviderTypeUCloudUCDN, domain.DeployProviderTypeUCloudUS3: { access := domain.AccessConfigForUCloud{} if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { @@ -365,11 +366,22 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, deployer, err := providerUCloudUCDN.NewWithLogger(&providerUCloudUCDN.UCloudUCDNDeployerConfig{ PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, - ProjectId: maps.GetValueAsString(options.ProviderDeployConfig, "projectId"), + ProjectId: access.ProjectId, DomainId: maps.GetValueAsString(options.ProviderDeployConfig, "domainId"), }, logger) return deployer, logger, err + case domain.DeployProviderTypeUCloudUS3: + deployer, err := providerUCloudUS3.NewWithLogger(&providerUCloudUS3.UCloudUS3DeployerConfig{ + PrivateKey: access.PrivateKey, + PublicKey: access.PublicKey, + ProjectId: access.ProjectId, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }, logger) + return deployer, logger, err + default: break } diff --git a/internal/domain/access.go b/internal/domain/access.go index e1979ea3..1893b4ab 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -25,9 +25,9 @@ func (a *Access) UnmarshalConfigToMap() (map[string]any, error) { type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` - Mode string `json:"mode"` - Username string `json:"username"` - Password string `json:"password"` + Mode string `json:"mode,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` } type AccessConfigForAliyun struct { @@ -79,7 +79,7 @@ type AccessConfigForHuaweiCloud struct { type AccessConfigForLocal struct{} type AccessConfigForKubernetes struct { - KubeConfig string `json:"kubeConfig"` + KubeConfig string `json:"kubeConfig,omitempty"` } type AccessConfigForNameDotCom struct { @@ -105,9 +105,9 @@ type AccessConfigForSSH struct { Host string `json:"host"` Port int32 `json:"port"` Username string `json:"username"` - Password string `json:"password"` - Key string `json:"key"` - KeyPassphrase string `json:"keyPassphrase"` + Password string `json:"password,omitempty"` + Key string `json:"key,omitempty"` + KeyPassphrase string `json:"keyPassphrase,omitempty"` } type AccessConfigForTencentCloud struct { @@ -118,6 +118,7 @@ type AccessConfigForTencentCloud struct { type AccessConfigForUCloud struct { PrivateKey string `json:"privateKey"` PublicKey string `json:"publicKey"` + ProjectId string `json:"projectId,omitempty"` } type AccessConfigForVolcEngine struct { diff --git a/internal/domain/provider.go b/internal/domain/provider.go index d5b844a9..b90d9b38 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -94,6 +94,7 @@ const ( DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") + DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") diff --git a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go index ba285d71..d8b703b5 100644 --- a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go +++ b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn_test.go @@ -20,7 +20,7 @@ var ( ) func init() { - argsPrefix := "CERTIMATE_DEPLOYER_UCLOUDCDN_" + argsPrefix := "CERTIMATE_DEPLOYER_UCLOUDUCDN_" flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") @@ -32,12 +32,12 @@ func init() { /* Shell command to run this test: - go test -v ./ucloud_cdn_test.go -args \ - --CERTIMATE_DEPLOYER_UCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - --CERTIMATE_DEPLOYER_UCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ - --CERTIMATE_DEPLOYER_UCLOUDCDN_PRIVATEKEY="your-private-key" \ - --CERTIMATE_DEPLOYER_UCLOUDCDN_PUBLICKEY="your-public-key" \ - --CERTIMATE_DEPLOYER_UCLOUDCDN_DOMAINID="your-domain-id" + go test -v ./ucloud_ucdn_test.go -args \ + --CERTIMATE_DEPLOYER_UCLOUDUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDUCDN_PRIVATEKEY="your-private-key" \ + --CERTIMATE_DEPLOYER_UCLOUDUCDN_PUBLICKEY="your-public-key" \ + --CERTIMATE_DEPLOYER_UCLOUDUCDN_DOMAINID="your-domain-id" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -53,9 +53,9 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.New(&provider.UCloudUCDNDeployerConfig{ - AccessKeyId: fPrivateKey, - AccessKeySecret: fPublicKey, - DomainId: fDomainId, + PrivateKey: fPrivateKey, + PublicKey: fPublicKey, + DomainId: fDomainId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go new file mode 100644 index 00000000..b55d1acc --- /dev/null +++ b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go @@ -0,0 +1,116 @@ +package ucloudus3 + +import ( + "context" + "errors" + + xerrors "github.com/pkg/errors" + usdk "github.com/ucloud/ucloud-sdk-go/ucloud" + uAuth "github.com/ucloud/ucloud-sdk-go/ucloud/auth" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" + usdkFile "github.com/usual2970/certimate/internal/pkg/vendors/ucloud-sdk/ufile" +) + +type UCloudUS3DeployerConfig struct { + // 优刻得 API 私钥。 + PrivateKey string `json:"privateKey"` + // 优刻得 API 公钥。 + PublicKey string `json:"publicKey"` + // 优刻得项目 ID。 + ProjectId string `json:"projectId,omitempty"` + // 优刻得地域。 + Region string `json:"region"` + // 存储桶名。 + Bucket string `json:"bucket"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type UCloudUS3Deployer struct { + config *UCloudUS3DeployerConfig + logger logger.Logger + sdkClient *usdkFile.UFileClient + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*UCloudUS3Deployer)(nil) + +func New(config *UCloudUS3DeployerConfig) (*UCloudUS3Deployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *UCloudUS3DeployerConfig, logger logger.Logger) (*UCloudUS3Deployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.PrivateKey, config.PublicKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{ + PrivateKey: config.PrivateKey, + PublicKey: config.PublicKey, + ProjectId: config.ProjectId, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &UCloudUS3Deployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *UCloudUS3Deployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 USSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Logt("certificate file uploaded", upres) + + // 添加 SSL 证书 + // REF: https://docs.ucloud.cn/api/ufile-api/add_ufile_ssl_cert + addUFileSSLCertReq := d.sdkClient.NewAddUFileSSLCertRequest() + addUFileSSLCertReq.BucketName = usdk.String(d.config.Bucket) + addUFileSSLCertReq.Domain = usdk.String(d.config.Domain) + addUFileSSLCertReq.USSLId = usdk.String(upres.CertId) + addUFileSSLCertReq.CertificateName = usdk.String(upres.CertName) + if d.config.ProjectId != "" { + addUFileSSLCertReq.ProjectId = usdk.String(d.config.ProjectId) + } + addUFileSSLCertResp, err := d.sdkClient.AddUFileSSLCert(addUFileSSLCertReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ucdn.AddUFileSSLCert'") + } + + d.logger.Logt("添加 SSL 证书", addUFileSSLCertResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(privateKey, publicKey, region string) (*usdkFile.UFileClient, error) { + cfg := usdk.NewConfig() + cfg.Region = region + + credential := uAuth.NewCredential() + credential.PrivateKey = privateKey + credential.PublicKey = publicKey + + client := usdkFile.NewClient(&cfg, &credential) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3_test.go b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3_test.go new file mode 100644 index 00000000..e4175bfc --- /dev/null +++ b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3_test.go @@ -0,0 +1,85 @@ +package ucloudus3_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" +) + +var ( + fInputCertPath string + fInputKeyPath string + fPrivateKey string + fPublicKey string + fRegion string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_UCLOUDUS3_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fPrivateKey, argsPrefix+"PRIVATEKEY", "", "") + flag.StringVar(&fPublicKey, argsPrefix+"PUBLICKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ucloud_us3_test.go -args \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_PRIVATEKEY="your-private-key" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_PUBLICKEY="your-public-key" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_REGION="cn-bj2" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_BUCKET="your-us3-bucket" \ + --CERTIMATE_DEPLOYER_UCLOUDUS3_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("PRIVATEKEY: %v", fPrivateKey), + fmt.Sprintf("PUBLICKEY: %v", fPublicKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.UCloudUS3DeployerConfig{ + PrivateKey: fPrivateKey, + PublicKey: fPublicKey, + Region: fRegion, + Bucket: fBucket, + 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/vendors/ucloud-sdk/ufile/apis.go b/internal/pkg/vendors/ucloud-sdk/ufile/apis.go new file mode 100644 index 00000000..009405fa --- /dev/null +++ b/internal/pkg/vendors/ucloud-sdk/ufile/apis.go @@ -0,0 +1,42 @@ +package ufile + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + "github.com/ucloud/ucloud-sdk-go/ucloud/response" +) + +type AddUFileSSLCertRequest struct { + request.CommonBase + + BucketName *string `required:"true"` + Domain *string `required:"true"` + CertificateName *string `required:"true"` + USSLId *string `required:"false"` +} + +type AddUFileSSLCertResponse struct { + response.CommonBase +} + +func (c *UFileClient) NewAddUFileSSLCertRequest() *AddUFileSSLCertRequest { + req := &AddUFileSSLCertRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *UFileClient) AddUFileSSLCert(req *AddUFileSSLCertRequest) (*AddUFileSSLCertResponse, error) { + var err error + var res AddUFileSSLCertResponse + + reqCopier := *req + + err = c.Client.InvokeAction("AddUFileSSLCert", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} diff --git a/internal/pkg/vendors/ucloud-sdk/ufile/client.go b/internal/pkg/vendors/ucloud-sdk/ufile/client.go new file mode 100644 index 00000000..ab1f4d2e --- /dev/null +++ b/internal/pkg/vendors/ucloud-sdk/ufile/client.go @@ -0,0 +1,18 @@ +package ufile + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/auth" +) + +type UFileClient struct { + *ucloud.Client +} + +func NewClient(config *ucloud.Config, credential *auth.Credential) *UFileClient { + meta := ucloud.ClientMeta{Product: "UFile"} + client := ucloud.NewClientWithMeta(config, credential, meta) + return &UFileClient{ + client, + } +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 99c427f5..cbeb6c7a 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -38,6 +38,7 @@ import DeployNodeConfigFormTencentCloudCSSConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudECDNConfig from "./DeployNodeConfigFormTencentCloudECDNConfig.tsx"; import DeployNodeConfigFormTencentCloudEOConfig from "./DeployNodeConfigFormTencentCloudEOConfig.tsx"; import DeployNodeConfigFormUCloudUCDNConfig from "./DeployNodeConfigFormUCloudUCDNConfig.tsx"; +import DeployNodeConfigFormUCloudUS3Config from "./DeployNodeConfigFormUCloudUS3Config.tsx"; import DeployNodeConfigFormVolcEngineCDNConfig from "./DeployNodeConfigFormVolcEngineCDNConfig.tsx"; import DeployNodeConfigFormVolcEngineCLBConfig from "./DeployNodeConfigFormVolcEngineCLBConfig.tsx"; import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolcEngineDCDNConfig.tsx"; @@ -159,6 +160,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.UCLOUD_UCDN: return ; + case DEPLOY_PROVIDERS.UCLOUD_US3: + return ; case DEPLOY_PROVIDERS.VOLCENGINE_CDN: return ; case DEPLOY_PROVIDERS.VOLCENGINE_CLB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUS3Config.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUS3Config.tsx new file mode 100644 index 00000000..0ac3a5d6 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUCloudUS3Config.tsx @@ -0,0 +1,93 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormUCloudUS3ConfigFieldValues = Nullish<{ + region: string; + bucket: string; + domain: string; +}>; + +export type DeployNodeConfigFormUCloudUS3ConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUCloudUS3ConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUCloudUS3ConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUCloudUS3ConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormUCloudUS3Config = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormUCloudUS3ConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.ucloud_us3_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.ucloud_us3_region.placeholder")) + .trim(), + bucket: z + .string({ message: t("workflow_node.deploy.form.ucloud_us3_bucket.placeholder") }) + .nonempty(t("workflow_node.deploy.form.ucloud_us3_bucket.placeholder")) + .trim(), + domain: z + .string({ message: t("workflow_node.deploy.form.ucloud_us3_domain.placeholder") }) + .refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormUCloudUS3Config; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index a9876aad..42250e9d 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -176,6 +176,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ TENCENTCLOUD_ECDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ecdn`, TENCENTCLOUD_EO: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eo`, UCLOUD_UCDN: `${ACCESS_PROVIDERS.UCLOUD}-ucdn`, + UCLOUD_US3: `${ACCESS_PROVIDERS.UCLOUD}-us3`, VOLCENGINE_CDN: `${ACCESS_PROVIDERS.VOLCENGINE}-cdn`, VOLCENGINE_CLB: `${ACCESS_PROVIDERS.VOLCENGINE}-clb`, VOLCENGINE_DCDN: `${ACCESS_PROVIDERS.VOLCENGINE}-dcdn`, @@ -227,6 +228,7 @@ export const deployProvidersMap: Map [ type, diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 7f9ead1d..66228013 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -123,13 +123,13 @@ "access.form.tencentcloud_secret_key.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", "access.form.ucloud_private_key.label": "UCloud API private key", "access.form.ucloud_private_key.placeholder": "Please enter UCloud API private key", - "access.form.ucloud_private_key.tooltip": "For more information, see https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_private_key.tooltip": "For more information, see https://console.ucloud-global.com/uaccount/api_manage", "access.form.ucloud_public_key.label": "UCloud API public key", "access.form.ucloud_public_key.placeholder": "Please enter UCloud API public key", - "access.form.ucloud_public_key.tooltip": "For more information, see https://console.ucloud.cn/uaccount/api_manage", + "access.form.ucloud_public_key.tooltip": "For more information, see https://console.ucloud-global.com/uaccount/api_manage", "access.form.ucloud_project_id.label": "UCloud project ID (Optional)", "access.form.ucloud_project_id.placeholder": "Please enter UCloud project ID", - "access.form.ucloud_project_id.tooltip": "For more information, see https://console.ucloud.cn/uaccount/iam/project_manage", + "access.form.ucloud_project_id.tooltip": "For more information, see https://console.ucloud-global.com/uaccount/iam/project_manage", "access.form.volcengine_access_key_id.label": "VolcEngine AccessKeyId", "access.form.volcengine_access_key_id.placeholder": "Please enter VolcEngine AccessKeyId", "access.form.volcengine_access_key_id.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 0b673e32..dc8ea864 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -80,6 +80,7 @@ "common.provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", "common.provider.ucloud": "UCloud", "common.provider.ucloud.ucdn": "UCloud - UCloud Content Delivery Network (UCDN)", + "common.provider.ucloud.us3": "UCloud - UCloud Object-based Storage (US3)", "common.provider.volcengine": "Volcengine", "common.provider.volcengine.cdn": "Volcengine - Content Delivery Network (CDN)", "common.provider.volcengine.clb": "Volcengine - Cloud Load Balancer (CLB)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index bbebd5a7..7275d08b 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -303,7 +303,16 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "UCloud UCDN domain ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "Please enter UCloud UCDN domain ID", - "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "For more information, see https://console.ucloud.cn/ucdn", + "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "For more information, see https://console.ucloud-global.com/ucdn", + "workflow_node.deploy.form.ucloud_us3_region.label": "UCloud region", + "workflow_node.deploy.form.ucloud_us3_region.placeholder": "Please enter VolcEngine region (e.g. cn-bj2)", + "workflow_node.deploy.form.ucloud_us3_region.tooltip": "For more information, see https://www.ucloud-global.com/en/docs/api/summary/regionlist", + "workflow_node.deploy.form.ucloud_us3_bucket.label": "UCloud US3 bucket", + "workflow_node.deploy.form.ucloud_us3_bucket.placeholder": "Please enter UCloud US3 bucket name", + "workflow_node.deploy.form.ucloud_us3_bucket.tooltip": "For more information, see https://console.ucloud-global.com/ufile", + "workflow_node.deploy.form.ucloud_us3_domain.label": "UCloud US3 domain", + "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 domain name", + "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see https://console.ucloud-global.com/ufile", "workflow_node.deploy.form.volcengine_cdn_domain.label": "VolcEngine CDN domain", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "Please enter VolcEngine CDN domain name", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "For more information, see https://console.volcengine.com/cdn/homepage", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index c35f19a6..80d6ccc9 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -80,6 +80,7 @@ "common.provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", "common.provider.ucloud": "优刻得", "common.provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", + "common.provider.ucloud.us3": "优刻得 - 对象存储 US3", "common.provider.volcengine": "火山引擎", "common.provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "common.provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 41450494..00b24d75 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -304,6 +304,15 @@ "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "请输入优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ucdn", + "workflow_node.deploy.form.ucloud_us3_region.label": "优刻得地域", + "workflow_node.deploy.form.ucloud_us3_region.placeholder": "优刻得地域(例如:cn-bj2)", + "workflow_node.deploy.form.ucloud_us3_region.tooltip": "这是什么?请参阅 https://docs.ucloud.cn/api/summary/regionlist", + "workflow_node.deploy.form.ucloud_us3_bucket.label": "优刻得 US3 存储桶名", + "workflow_node.deploy.form.ucloud_us3_bucket.placeholder": "请输入优刻得 US3 存储桶名", + "workflow_node.deploy.form.ucloud_us3_bucket.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", + "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", + "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", + "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage

泛域名表示形式为:*.example.com",