From 46c32f15e36ac4295f4d4c70e589c40e1d6b6a49 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 21:07:58 +0800 Subject: [PATCH 1/7] feat: add tencent cos deploy support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增腾讯云COS配置支持 --- internal/deployer/tencent-cos.go | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 internal/deployer/tencent-cos.go diff --git a/internal/deployer/tencent-cos.go b/internal/deployer/tencent-cos.go new file mode 100644 index 00000000..cac7143d --- /dev/null +++ b/internal/deployer/tencent-cos.go @@ -0,0 +1,113 @@ +package deployer + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "net/http" + "net/url" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/rand" +) + +type TencentCOSDeployer struct { + option *DeployerOption + credential *common.Credential + infos []string +} + +func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) { + access := &domain.TencentAccess{} + if err := json.Unmarshal([]byte(option.Access), access); err != nil { + return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err) + } + + credential := common.NewCredential( + access.SecretId, + access.SecretKey, + ) + + return &TencentCOSDeployer{ + option: option, + credential: credential, + infos: make([]string, 0), + }, nil +} + +func (d *TencentCOSDeployer) GetID() string { + return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id) +} + +func (d *TencentCOSDeployer) GetInfo() []string { + return d.infos +} + +func (d *TencentCOSDeployer) Deploy(ctx context.Context) error { + // 上传证书 + certId, err := d.uploadCert() + if err != nil { + return fmt.Errorf("failed to upload certificate: %w", err) + } + d.infos = append(d.infos, toStr("上传证书", certId)) + + if err := d.deploy(certId); err != nil { + return fmt.Errorf("failed to deploy: %w", err) + } + + return nil +} + +// 上传证书,与CDN部署的上传方法一致。 +func (d *TencentCOSDeployer) uploadCert() (string, error) { + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com" + + client, _ := ssl.NewClient(d.credential, "", cpf) + + request := ssl.NewUploadCertificateRequest() + + request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate) + request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey) + request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6)) + request.Repeatable = common.BoolPtr(false) + + response, err := client.UploadCertificate(request) + if err != nil { + return "", fmt.Errorf("failed to upload certificate: %w", err) + } + + return *response.Response.CertificateId, nil +} + +func (d *TencentCOSDeployer) deploy(certId string) error { + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com" + // 实例化要请求产品的client对象,clientProfile是可选的 + client, _ := ssl.NewClient(d.credential, "", cpf) + + // 实例化一个请求对象,每个接口都会对应一个request对象 + request := ssl.NewDeployCertificateInstanceRequest() + + request.Region = common.StringPtr(getDeployString(d.option.DeployConfig, "region")) + request.CertificateId = common.StringPtr(certId) + request.ResourceType = common.StringPtr("c") + request.Status = common.Int64Ptr(1) + + domain := getDeployString(d.option.DeployConfig, "domain") + request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", getDeployString(d.option.DeployConfig, "region"), getDeployString(d.option.DeployConfig, "bucket"), domain)}) + + // 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应 + resp, err := client.DeployCertificateInstance(request) + if err != nil { + return fmt.Errorf("failed to deploy certificate: %w", err) + } + d.infos = append(d.infos, toStr("部署证书", resp.Response)) + return nil +} \ No newline at end of file From 63865b5fbd4873a25c884ab487e6ec8c3dd2be27 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 21:11:49 +0800 Subject: [PATCH 2/7] fix ResourceType --- internal/deployer/tencent-cos.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/deployer/tencent-cos.go b/internal/deployer/tencent-cos.go index cac7143d..1d943b59 100644 --- a/internal/deployer/tencent-cos.go +++ b/internal/deployer/tencent-cos.go @@ -97,7 +97,7 @@ func (d *TencentCOSDeployer) deploy(certId string) error { request.Region = common.StringPtr(getDeployString(d.option.DeployConfig, "region")) request.CertificateId = common.StringPtr(certId) - request.ResourceType = common.StringPtr("c") + request.ResourceType = common.StringPtr("cos") request.Status = common.Int64Ptr(1) domain := getDeployString(d.option.DeployConfig, "domain") From f7972d5b68096b7fe2794830f97144f78ca225f8 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 21:19:20 +0800 Subject: [PATCH 3/7] remove unused imports --- internal/deployer/tencent-cos.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/deployer/tencent-cos.go b/internal/deployer/tencent-cos.go index 1d943b59..68c1f672 100644 --- a/internal/deployer/tencent-cos.go +++ b/internal/deployer/tencent-cos.go @@ -6,8 +6,6 @@ import ( "encoding/json" "fmt" "strings" - "net/http" - "net/url" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" From cd76d170b2a8006aa3d46d278c3670c2c228c762 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 21:55:49 +0800 Subject: [PATCH 4/7] fix region of cos --- internal/deployer/tencent-cos.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/deployer/tencent-cos.go b/internal/deployer/tencent-cos.go index 68c1f672..2ea8c7d6 100644 --- a/internal/deployer/tencent-cos.go +++ b/internal/deployer/tencent-cos.go @@ -2,10 +2,8 @@ package deployer import ( "context" - "encoding/base64" "encoding/json" "fmt" - "strings" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -88,12 +86,11 @@ func (d *TencentCOSDeployer) deploy(certId string) error { cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com" // 实例化要请求产品的client对象,clientProfile是可选的 - client, _ := ssl.NewClient(d.credential, "", cpf) + client, _ := ssl.NewClient(d.credential, getDeployString(d.option.DeployConfig, "region"), cpf) // 实例化一个请求对象,每个接口都会对应一个request对象 request := ssl.NewDeployCertificateInstanceRequest() - request.Region = common.StringPtr(getDeployString(d.option.DeployConfig, "region")) request.CertificateId = common.StringPtr(certId) request.ResourceType = common.StringPtr("cos") request.Status = common.Int64Ptr(1) From a8f718afa09b3e7fbf5475055763d2b88277cc02 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 21:56:26 +0800 Subject: [PATCH 5/7] add tencent-cos ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 表单主要从OSS表单修改 --- .../components/certimate/DeployEditDialog.tsx | 3 + .../certimate/DeployToTencentCOS.tsx | 164 ++++++++++++++++++ ui/src/domain/domain.ts | 1 + ui/src/i18n/locales/en/nls.domain.json | 4 + ui/src/i18n/locales/zh/nls.domain.json | 4 + 5 files changed, 176 insertions(+) create mode 100644 ui/src/components/certimate/DeployToTencentCOS.tsx diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx index 1e5b900e..774f1d4a 100644 --- a/ui/src/components/certimate/DeployEditDialog.tsx +++ b/ui/src/components/certimate/DeployEditDialog.tsx @@ -12,6 +12,7 @@ import { Context as DeployEditContext } from "./DeployEdit"; import DeployToAliyunOSS from "./DeployToAliyunOSS"; import DeployToAliyunCDN from "./DeployToAliyunCDN"; import DeployToTencentCDN from "./DeployToTencentCDN"; +import DeployToTencentCOS from "./DeployToTencentCOS"; import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN"; import DeployToQiniuCDN from "./DeployToQiniuCDN"; import DeployToSSH from "./DeployToSSH"; @@ -118,6 +119,8 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro case "tencent-cdn": childComponent = ; break; + case "tencent-cos": + childComponent = ; case "huaweicloud-cdn": childComponent = ; break; diff --git a/ui/src/components/certimate/DeployToTencentCOS.tsx b/ui/src/components/certimate/DeployToTencentCOS.tsx new file mode 100644 index 00000000..e006fc08 --- /dev/null +++ b/ui/src/components/certimate/DeployToTencentCOS.tsx @@ -0,0 +1,164 @@ +import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { z } from "zod"; +import { produce } from "immer"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { useDeployEditContext } from "./DeployEdit"; + +const DeployToTencentCOS = () => { + const { deploy: data, setDeploy, error, setError } = useDeployEditContext(); + + const { t } = useTranslation(); + + useEffect(() => { + setError({}); + }, []); + + useEffect(() => { + const resp = domainSchema.safeParse(data.config?.domain); + if (!resp.success) { + setError({ + ...error, + domain: JSON.parse(resp.error.message)[0].message, + }); + } else { + setError({ + ...error, + domain: "", + }); + } + }, [data]); + + useEffect(() => { + const bucketResp = bucketSchema.safeParse(data.config?.domain); + if (!bucketResp.success) { + setError({ + ...error, + bucket: JSON.parse(bucketResp.error.message)[0].message, + }); + } else { + setError({ + ...error, + bucket: "", + }); + } + }, []); + + useEffect(() => { + if (!data.id) { + setDeploy({ + ...data, + config: { + region: "", + bucket: "", + domain: "", + }, + }); + } + }, []); + + const domainSchema = z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { + message: t("common.errmsg.domain_invalid"), + }); + + const bucketSchema = z.string().min(1, { + message: t("domain.deployment.form.cos_region.placeholder"), + }); + + return ( +
+
+ + { + const temp = e.target.value; + + const newData = produce(data, (draft) => { + if (!draft.config) { + draft.config = {}; + } + draft.config.region = temp; + }); + setDeploy(newData); + }} + /> +
{error?.endpoint}
+
+ +
+ + { + const temp = e.target.value; + + const resp = bucketSchema.safeParse(temp); + if (!resp.success) { + setError({ + ...error, + bucket: JSON.parse(resp.error.message)[0].message, + }); + } else { + setError({ + ...error, + bucket: "", + }); + } + + const newData = produce(data, (draft) => { + if (!draft.config) { + draft.config = {}; + } + draft.config.bucket = temp; + }); + setDeploy(newData); + }} + /> +
{error?.bucket}
+
+ +
+ + { + const temp = e.target.value; + + const resp = domainSchema.safeParse(temp); + if (!resp.success) { + setError({ + ...error, + domain: JSON.parse(resp.error.message)[0].message, + }); + } else { + setError({ + ...error, + domain: "", + }); + } + + const newData = produce(data, (draft) => { + if (!draft.config) { + draft.config = {}; + } + draft.config.domain = temp; + }); + setDeploy(newData); + }} + /> +
{error?.domain}
+
+
+ ); +}; + +export default DeployToTencentCOS; diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index 234abe2c..350f89a4 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -75,6 +75,7 @@ export const deployTargetsMap: Map = new Map ["aliyun-cdn", "common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"], ["aliyun-dcdn", "common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"], ["tencent-cdn", "common.provider.tencent.cdn", "/imgs/providers/tencent.svg"], + ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"], ["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"], ["qiniu-cdn", "common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"], ["local", "common.provider.local", "/imgs/providers/local.svg"], diff --git a/ui/src/i18n/locales/en/nls.domain.json b/ui/src/i18n/locales/en/nls.domain.json index 36552760..98d7dcce 100644 --- a/ui/src/i18n/locales/en/nls.domain.json +++ b/ui/src/i18n/locales/en/nls.domain.json @@ -54,6 +54,10 @@ "domain.deployment.form.access.label": "Access Configuration", "domain.deployment.form.access.placeholder": "Please select provider authorization configuration", "domain.deployment.form.access.list": "Provider Authorization Configurations", + "domain.deployment.form.cos_region.label": "Region", + "domain.deployment.form.cos_region.placeholder": "Please enter region, e.g. ap-guangzhou", + "domain.deployment.form.cos_bucket.label": "Bucket", + "domain.deployment.form.cos_bucket.placeholder": "Please enter bucket, e.g. example-1250000000", "domain.deployment.form.domain.label": "Deploy to domain (Single domain only, not wildcard domain)", "domain.deployment.form.domain.placeholder": "Please enter domain to be deployed", "domain.deployment.form.ssh_key_path.label": "Private Key Save Path", diff --git a/ui/src/i18n/locales/zh/nls.domain.json b/ui/src/i18n/locales/zh/nls.domain.json index e2d87fc6..861b9259 100644 --- a/ui/src/i18n/locales/zh/nls.domain.json +++ b/ui/src/i18n/locales/zh/nls.domain.json @@ -54,6 +54,10 @@ "domain.deployment.form.access.label": "授权配置", "domain.deployment.form.access.placeholder": "请选择授权配置", "domain.deployment.form.access.list": "服务商授权配置列表", + "domain.deployment.form.cos_region.label": "region", + "domain.deployment.form.cos_region.placeholder": "请输入 region, 如 ap-guangzhou", + "domain.deployment.form.cos_bucket.label": "存储桶", + "domain.deployment.form.cos_bucket.placeholder": "请输入存储桶名, 如 example-1250000000", "domain.deployment.form.domain.label": "部署到域名(仅支持单个域名;不支持泛域名)", "domain.deployment.form.domain.placeholder": "请输入部署到的域名", "domain.deployment.form.ssh_key_path.label": "私钥保存路径", From c9eb4879534bf662574ea523daf0a2cea3c11c35 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 22:18:52 +0800 Subject: [PATCH 6/7] fix ui-deployer route --- internal/deployer/deployer.go | 3 +++ ui/src/components/certimate/DeployEditDialog.tsx | 1 + ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.common.json | 1 + 4 files changed, 6 insertions(+) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 18794028..9d296065 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -19,6 +19,7 @@ const ( targetAliyunCDN = "aliyun-cdn" targetAliyunESA = "aliyun-dcdn" targetTencentCDN = "tencent-cdn" + targetTencentCOS = "tencent-cos" targetHuaweiCloudCDN = "huaweicloud-cdn" targetQiniuCdn = "qiniu-cdn" targetLocal = "local" @@ -105,6 +106,8 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep return NewAliyunESADeployer(option) case targetTencentCDN: return NewTencentCDNDeployer(option) + case targetTencentCOS: + return NewTencentCOSDeployer(option) case targetHuaweiCloudCDN: return NewHuaweiCloudCDNDeployer(option) case targetQiniuCdn: diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx index 774f1d4a..54710e49 100644 --- a/ui/src/components/certimate/DeployEditDialog.tsx +++ b/ui/src/components/certimate/DeployEditDialog.tsx @@ -121,6 +121,7 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro break; case "tencent-cos": childComponent = ; + break; case "huaweicloud-cdn": childComponent = ; break; diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 358639e0..d3a17019 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -58,6 +58,7 @@ "common.provider.aliyun.dcdn": "Alibaba Cloud - DCDN", "common.provider.tencent": "Tencent", "common.provider.tencent.cdn": "Tencent - CDN", + "common.provider.tencent.cos": "Tencent - COS", "common.provider.huaweicloud": "Huawei Cloud", "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN", "common.provider.qiniu": "Qiniu", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index f6179636..212d7b2e 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -54,6 +54,7 @@ "common.provider.tencent": "腾讯云", "common.provider.tencent.cdn": "腾讯云 - CDN", + "common.provider.tencent.cos": "腾讯云 - COS", "common.provider.aliyun": "阿里云", "common.provider.aliyun.oss": "阿里云 - OSS", "common.provider.aliyun.cdn": "阿里云 - CDN", From b01849eb0cec64faad673413a05db89ff10104f0 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 22 Oct 2024 22:32:35 +0800 Subject: [PATCH 7/7] update readme for new feat. --- README.md | 2 +- README_EN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af94b0b1..2e36d3a2 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ make local.run | 服务商 | 支持申请证书 | 支持部署证书 | 备注 | | :--------: | :----------: | :----------: | ------------------------------------------------------------ | | 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN | -| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN | +| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN、COS | | 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN | | 七牛云 | | √ | 可部署到七牛云 CDN | | AWS | √ | | 可签发在 AWS Route53 托管的域名 | diff --git a/README_EN.md b/README_EN.md index 9cc42453..f6356c86 100644 --- a/README_EN.md +++ b/README_EN.md @@ -73,7 +73,7 @@ password:1234567890 | Provider | Registration | Deployment | Remarks | | :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- | | Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN | -| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN | +| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN, COS | | Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN | | Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN | | AWS | √ | | Supports domains managed on AWS Route53 |