From 295b7779eeac629e2f6dc491db7657309cfddf4f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 18 Nov 2024 09:10:28 +0800 Subject: [PATCH 01/17] refactor: clean code --- internal/deployer/huaweicloud_elb.go | 12 ++---------- .../providers/huaweicloud-elb/huaweicloud_elb.go | 9 +++------ .../providers/huaweicloud-scm/huaweicloud_scm.go | 4 ++-- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/internal/deployer/huaweicloud_elb.go b/internal/deployer/huaweicloud_elb.go index b7658660..ccc6f9f5 100644 --- a/internal/deployer/huaweicloud_elb.go +++ b/internal/deployer/huaweicloud_elb.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "sort" - "strings" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" @@ -17,6 +15,7 @@ import ( hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model" hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region" xerrors "github.com/pkg/errors" + "golang.org/x/exp/slices" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/uploader" @@ -163,9 +162,6 @@ func (u *HuaweiCloudELBDeployer) getSdkProjectId(accessKeyId, secretAccessKey, r } client := hcIam.NewIamClient(hcClient) - if err != nil { - return "", err - } request := &hcIamModel.KeystoneListProjectsRequest{ Name: ®ion, @@ -352,11 +348,7 @@ func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, newCertificate := showNewCertificateResp.Certificate if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil { - oldCertificateSans := oldCertificate.SubjectAlternativeNames - newCertificateSans := newCertificate.SubjectAlternativeNames - sort.Strings(*oldCertificateSans) - sort.Strings(*newCertificateSans) - if strings.Join(*oldCertificateSans, ";") == strings.Join(*newCertificateSans, ";") { + if slices.Equal(*oldCertificate.SubjectAlternativeNames, *newCertificate.SubjectAlternativeNames) { continue } } else { diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index 5b6ab376..526d35ef 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -56,7 +56,7 @@ func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { // 解析证书内容 - newCert, err := x509.ParseCertificateFromPEM(certPem) + certX509, err := x509.ParseCertificateFromPEM(certPem) if err != nil { return nil, err } @@ -83,12 +83,12 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri if certDetail.Certificate == certPem { isSameCert = true } else { - cert, err := x509.ParseCertificateFromPEM(certDetail.Certificate) + oldCertX509, err := x509.ParseCertificateFromPEM(certDetail.Certificate) if err != nil { continue } - isSameCert = x509.EqualCertificate(cert, newCert) + isSameCert = x509.EqualCertificate(certX509, oldCertX509) } // 如果已存在相同证书,直接返回已有的证书信息 @@ -205,9 +205,6 @@ func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error } client := hcIam.NewIamClient(hcClient) - if err != nil { - return "", err - } request := &hcIamModel.KeystoneListProjectsRequest{ Name: ®ion, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index 45450d9e..6d85fbff 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -92,12 +92,12 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri if *exportCertificateResp.Certificate == certPem { isSameCert = true } else { - cert, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate) + oldCertX509, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate) if err != nil { continue } - isSameCert = x509.EqualCertificate(certX509, cert) + isSameCert = x509.EqualCertificate(certX509, oldCertX509) } // 如果已存在相同证书,直接返回已有的证书信息 From 43b2ff795738ece87c0a41b5e70b1ff9644c4c77 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 18 Nov 2024 09:12:15 +0800 Subject: [PATCH 02/17] refactor: extract x509 transformer utils --- internal/deployer/deployer.go | 60 ------------------ internal/deployer/local.go | 5 +- internal/deployer/ssh.go | 5 +- internal/pkg/utils/x509/transformer.go | 87 ++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 64 deletions(-) create mode 100644 internal/pkg/utils/x509/transformer.go diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index fbb582f5..b05147f0 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -1,21 +1,15 @@ package deployer import ( - "bytes" "context" "encoding/json" - "encoding/pem" "errors" "fmt" - "time" - "github.com/pavlo-v-chernykh/keystore-go/v4" "github.com/pocketbase/pocketbase/models" - "software.sslmate.com/src/go-pkcs12" "github.com/usual2970/certimate/internal/applicant" "github.com/usual2970/certimate/internal/domain" - "github.com/usual2970/certimate/internal/pkg/utils/x509" "github.com/usual2970/certimate/internal/utils/app" ) @@ -167,57 +161,3 @@ func toStr(tag string, data any) string { byts, _ := json.Marshal(data) return tag + ":" + string(byts) } - -func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) { - cert, err := x509.ParseCertificateFromPEM(certificate) - if err != nil { - return nil, err - } - - privkey, err := x509.ParsePKCS1PrivateKeyFromPEM(privateKey) - if err != nil { - return nil, err - } - - pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password) - if err != nil { - return nil, err - } - - return pfxData, nil -} - -func convertPEMToJKS(certificate string, privateKey string, alias string, keypass string, storepass string) ([]byte, error) { - certBlock, _ := pem.Decode([]byte(certificate)) - if certBlock == nil { - return nil, errors.New("failed to decode certificate PEM") - } - - privkeyBlock, _ := pem.Decode([]byte(privateKey)) - if privkeyBlock == nil { - return nil, errors.New("failed to decode private key PEM") - } - - ks := keystore.New() - entry := keystore.PrivateKeyEntry{ - CreationTime: time.Now(), - PrivateKey: privkeyBlock.Bytes, - CertificateChain: []keystore.Certificate{ - { - Type: "X509", - Content: certBlock.Bytes, - }, - }, - } - - if err := ks.SetPrivateKeyEntry(alias, entry, []byte(keypass)); err != nil { - return nil, err - } - - var buf bytes.Buffer - if err := ks.Store(&buf, []byte(storepass)); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/internal/deployer/local.go b/internal/deployer/local.go index 7562e2f6..3c693d30 100644 --- a/internal/deployer/local.go +++ b/internal/deployer/local.go @@ -11,6 +11,7 @@ import ( xerrors "github.com/pkg/errors" "github.com/usual2970/certimate/internal/pkg/utils/fs" + "github.com/usual2970/certimate/internal/pkg/utils/x509" ) type LocalDeployer struct { @@ -73,7 +74,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error { d.infos = append(d.infos, toStr("保存私钥成功", nil)) case certFormatPFX: - pfxData, err := convertPEMToPFX( + pfxData, err := x509.TransformCertificateFromPEMToPFX( d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("pfxPassword"), @@ -89,7 +90,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error { d.infos = append(d.infos, toStr("保存证书成功", nil)) case certFormatJKS: - jksData, err := convertPEMToJKS( + jksData, err := x509.TransformCertificateFromPEMToJKS( d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("jksAlias"), diff --git a/internal/deployer/ssh.go b/internal/deployer/ssh.go index 8a5bfa1d..96f8bdd2 100644 --- a/internal/deployer/ssh.go +++ b/internal/deployer/ssh.go @@ -14,6 +14,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/utils/x509" ) type SSHDeployer struct { @@ -78,7 +79,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error { d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil)) case certFormatPFX: - pfxData, err := convertPEMToPFX( + pfxData, err := x509.TransformCertificateFromPEMToPFX( d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("pfxPassword"), @@ -94,7 +95,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error { d.infos = append(d.infos, toStr("SSH 上传证书成功", nil)) case certFormatJKS: - jksData, err := convertPEMToJKS( + jksData, err := x509.TransformCertificateFromPEMToJKS( d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("jksAlias"), diff --git a/internal/pkg/utils/x509/transformer.go b/internal/pkg/utils/x509/transformer.go new file mode 100644 index 00000000..6170d88a --- /dev/null +++ b/internal/pkg/utils/x509/transformer.go @@ -0,0 +1,87 @@ +package x509 + +import ( + "bytes" + "encoding/pem" + "errors" + "time" + + "github.com/pavlo-v-chernykh/keystore-go/v4" + "software.sslmate.com/src/go-pkcs12" +) + +// 将 PEM 编码的证书字符串转换为 PFX 格式。 +// +// 入参: +// - certPem: 证书 PEM 内容。 +// - privkeyPem: 私钥 PEM 内容。 +// - pfxPassword: PFX 导出密码。 +// +// 出参: +// - data: PFX 格式的证书数据。 +// - err: 错误。 +func TransformCertificateFromPEMToPFX(certPem string, privkeyPem string, pfxPassword string) ([]byte, error) { + cert, err := ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + privkey, err := ParsePKCS1PrivateKeyFromPEM(privkeyPem) + if err != nil { + return nil, err + } + + pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword) + if err != nil { + return nil, err + } + + return pfxData, nil +} + +// 将 PEM 编码的证书字符串转换为 JKS 格式。 +// +// 入参: +// - certPem: 证书 PEM 内容。 +// - privkeyPem: 私钥 PEM 内容。 +// - jksAlias: JKS 别名。 +// - jksKeypass: JKS 密钥密码。 +// - jksStorepass: JKS 存储密码。 +// +// 出参: +// - data: JKS 格式的证书数据。 +// - err: 错误。 +func TransformCertificateFromPEMToJKS(certPem string, privkeyPem string, jksAlias string, jksKeypass string, jksStorepass string) ([]byte, error) { + certBlock, _ := pem.Decode([]byte(certPem)) + if certBlock == nil { + return nil, errors.New("failed to decode certificate PEM") + } + + privkeyBlock, _ := pem.Decode([]byte(privkeyPem)) + if privkeyBlock == nil { + return nil, errors.New("failed to decode private key PEM") + } + + ks := keystore.New() + entry := keystore.PrivateKeyEntry{ + CreationTime: time.Now(), + PrivateKey: privkeyBlock.Bytes, + CertificateChain: []keystore.Certificate{ + { + Type: "X509", + Content: certBlock.Bytes, + }, + }, + } + + if err := ks.SetPrivateKeyEntry(jksAlias, entry, []byte(jksKeypass)); err != nil { + return nil, err + } + + var buf bytes.Buffer + if err := ks.Store(&buf, []byte(jksStorepass)); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} From 26d11de249555cff51cbce4f979d8f05e779f101 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 09:08:49 +0800 Subject: [PATCH 03/17] feat: add deployer interface --- internal/pkg/core/deployer/deployer.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 internal/pkg/core/deployer/deployer.go diff --git a/internal/pkg/core/deployer/deployer.go b/internal/pkg/core/deployer/deployer.go new file mode 100644 index 00000000..b7b839fe --- /dev/null +++ b/internal/pkg/core/deployer/deployer.go @@ -0,0 +1,24 @@ +package deployer + +import "context" + +// 表示定义证书部署器的抽象类型接口。 +// 注意与 `Uploader` 区分,“部署”通常为“上传”的后置操作。 +type Deployer interface { + // 部署证书。 + // + // 入参: + // - ctx:上下文。 + // - certPem:证书 PEM 内容。 + // - privkeyPem:私钥 PEM 内容。 + // + // 出参: + // - res:部署结果。 + // - err: 错误。 + Deploy(ctx context.Context, certPem string, privkeyPem string) (res *DeployResult, err error) +} + +// 表示证书部署结果的数据结构。 +type DeployResult struct { + DeploymentData map[string]any `json:"deploymentData,omitempty"` +} From 6367785b1bc203ee15f82d13e6af167a0c2ca63c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 19:09:48 +0800 Subject: [PATCH 04/17] feat: implement local, ssh, webhook `Deployer` --- internal/domain/access.go | 10 +- internal/pkg/core/deployer/logger.go | 107 ++++++++ .../core/deployer/providers/local/define.go | 17 ++ .../core/deployer/providers/local/local.go | 177 ++++++++++++ .../pkg/core/deployer/providers/ssh/define.go | 9 + .../pkg/core/deployer/providers/ssh/ssh.go | 251 ++++++++++++++++++ .../deployer/providers/webhook/webhook.go | 81 ++++++ 7 files changed, 647 insertions(+), 5 deletions(-) create mode 100644 internal/pkg/core/deployer/logger.go create mode 100644 internal/pkg/core/deployer/providers/local/define.go create mode 100644 internal/pkg/core/deployer/providers/local/local.go create mode 100644 internal/pkg/core/deployer/providers/ssh/define.go create mode 100644 internal/pkg/core/deployer/providers/ssh/ssh.go create mode 100644 internal/pkg/core/deployer/providers/webhook/webhook.go diff --git a/internal/domain/access.go b/internal/domain/access.go index 98716ff4..8229ca5f 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -6,8 +6,8 @@ type AliyunAccess struct { } type ByteplusAccess struct { - AccessKey string - SecretKey string + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` } type TencentAccess struct { @@ -27,9 +27,9 @@ type BaiduCloudAccess struct { } type AwsAccess struct { - Region string `json:"region"` AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` + Region string `json:"region"` HostedZoneId string `json:"hostedZoneId"` } @@ -62,8 +62,8 @@ type PdnsAccess struct { } type VolcengineAccess struct { - AccessKeyID string - SecretAccessKey string + AccessKeyID string `json:"accessKeyId"` + SecretAccessKey string `json:"secretAccessKey"` } type HttpreqAccess struct { diff --git a/internal/pkg/core/deployer/logger.go b/internal/pkg/core/deployer/logger.go new file mode 100644 index 00000000..a97800b1 --- /dev/null +++ b/internal/pkg/core/deployer/logger.go @@ -0,0 +1,107 @@ +package deployer + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// 表示定义证书部署器的日志记录器的抽象类型接口。 +type Logger interface { + // 追加一条日志记录。 + // 该方法会将 `data` 以 JSON 序列化后拼接到 `tag` 结尾。 + // + // 入参: + // - tag:标签。 + // - data:数据。 + Appendt(tag string, data ...any) + + // 追加一条日志记录。 + // 该方法会将 `args` 以 `format` 格式化。 + // + // 入参: + // - format:格式化字符串。 + // - args:格式化参数。 + Appendf(format string, args ...any) + + // 获取所有日志记录。 + GetRecords() []string +} + +// 表示默认的日志记录器类型。 +type DefaultLogger struct { + records []string +} + +var _ Logger = (*DefaultLogger)(nil) + +func (l *DefaultLogger) Appendt(tag string, data ...any) { + l.ensureInitialized() + + temp := make([]string, len(data)+1) + temp[0] = tag + for i, v := range data { + s := "" + if v != nil { + switch reflect.ValueOf(v).Kind() { + case reflect.String: + s = v.(string) + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + s = fmt.Sprintf("%v", v) + default: + jsonData, _ := json.Marshal(v) + s = string(jsonData) + } + } + + temp[i+1] = s + } + + l.records = append(l.records, strings.Join(temp, ": ")) +} + +func (l *DefaultLogger) Appendf(format string, args ...any) { + l.ensureInitialized() + + l.records = append(l.records, fmt.Sprintf(format, args...)) +} + +func (l *DefaultLogger) GetRecords() []string { + l.ensureInitialized() + + temp := make([]string, len(l.records)) + copy(temp, l.records) + return temp +} + +func (l *DefaultLogger) ensureInitialized() { + if l.records == nil { + l.records = make([]string, 0) + } +} + +func NewDefaultLogger() *DefaultLogger { + return &DefaultLogger{ + records: make([]string, 0), + } +} + +// 表示空的日志记录器类型。 +// 该日志记录器不会执行任何操作。 +type NilLogger struct{} + +var _ Logger = (*NilLogger)(nil) + +func (l *NilLogger) Appendt(string, ...any) {} +func (l *NilLogger) Appendf(string, ...any) {} +func (l *NilLogger) GetRecords() []string { + return make([]string, 0) +} + +func NewNilLogger() *NilLogger { + return &NilLogger{} +} diff --git a/internal/pkg/core/deployer/providers/local/define.go b/internal/pkg/core/deployer/providers/local/define.go new file mode 100644 index 00000000..5b3118d8 --- /dev/null +++ b/internal/pkg/core/deployer/providers/local/define.go @@ -0,0 +1,17 @@ +package local + +type OutputFormatType string + +const ( + OUTPUT_FORMAT_PEM = OutputFormatType("PEM") + OUTPUT_FORMAT_PFX = OutputFormatType("PFX") + OUTPUT_FORMAT_JKS = OutputFormatType("JKS") +) + +type ShellEnvType string + +const ( + SHELL_ENV_SH = ShellEnvType("sh") + SHELL_ENV_CMD = ShellEnvType("cmd") + SHELL_ENV_POWERSHELL = ShellEnvType("powershell") +) diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go new file mode 100644 index 00000000..073014db --- /dev/null +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -0,0 +1,177 @@ +package local + +import ( + "bytes" + "context" + "errors" + "fmt" + "os/exec" + "runtime" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/utils/fs" + "github.com/usual2970/certimate/internal/pkg/utils/x509" +) + +type LocalDeployerConfig struct { + // Shell 执行环境。 + // 空值时默认根据操作系统决定。 + ShellEnv ShellEnvType `json:"shellEnv,omitempty"` + // 前置命令。 + PreCommand string `json:"preCommand,omitempty"` + // 后置命令。 + PostCommand string `json:"postCommand,omitempty"` + // 输出证书格式。 + // 空值时默认为 [OUTPUT_FORMAT_PEM]。 + OutputFormat OutputFormatType `json:"outputFormat,omitempty"` + // 输出证书文件路径。 + OutputCertPath string `json:"outputCertPath,omitempty"` + // 输出私钥文件路径。 + OutputKeyPath string `json:"outputKeyPath,omitempty"` + // PFX 导出密码。 + // 证书格式为 PFX 时必填。 + PfxPassword string `json:"pfxPassword,omitempty"` + // JKS 别名。 + // 证书格式为 JKS 时必填。 + JksAlias string `json:"jksAlias,omitempty"` + // JKS 密钥密码。 + // 证书格式为 JKS 时必填。 + JksKeypass string `json:"jksKeypass,omitempty"` + // JKS 存储密码。 + // 证书格式为 JKS 时必填。 + JksStorepass string `json:"jksStorepass,omitempty"` +} + +type LocalDeployer struct { + config *LocalDeployerConfig + logger deployer.Logger +} + +func New(config *LocalDeployerConfig) (*LocalDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *LocalDeployerConfig, logger deployer.Logger) (*LocalDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + return &LocalDeployer{ + logger: logger, + config: config, + }, nil +} + +func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 执行前置命令 + if d.config.PreCommand != "" { + stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand) + if err != nil { + return nil, xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr) + } + + d.logger.Appendt("pre-command executed", stdout) + } + + // 写入证书和私钥文件 + switch d.config.OutputFormat { + case "", OUTPUT_FORMAT_PEM: + if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file saved") + + if err := fs.WriteFileString(d.config.OutputKeyPath, privkeyPem); err != nil { + return nil, err + } + + d.logger.Appendt("private key file saved") + + case OUTPUT_FORMAT_PFX: + pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) + if err != nil { + return nil, err + } + + d.logger.Appendt("certificate transformed to PFX") + + if err := fs.WriteFile(d.config.OutputCertPath, pfxData); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file saved") + + case OUTPUT_FORMAT_JKS: + jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) + if err != nil { + return nil, err + } + + d.logger.Appendt("certificate transformed to JKS") + + if err := fs.WriteFile(d.config.OutputCertPath, jksData); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file uploaded") + + default: + return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat) + } + + // 执行后置命令 + if d.config.PostCommand != "" { + stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PostCommand) + if err != nil { + return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr) + } + + d.logger.Appendt("post-command executed", stdout) + } + + return &deployer.DeployResult{}, nil +} + +func execCommand(shellEnv ShellEnvType, command string) (string, string, error) { + var cmd *exec.Cmd + + switch shellEnv { + case SHELL_ENV_SH: + cmd = exec.Command("sh", "-c", command) + + case SHELL_ENV_CMD: + cmd = exec.Command("cmd", "/C", command) + + case SHELL_ENV_POWERSHELL: + cmd = exec.Command("powershell", "-Command", command) + + case "": + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/C", command) + } else { + cmd = exec.Command("sh", "-c", command) + } + + default: + return "", "", fmt.Errorf("unsupported shell env: %s", shellEnv) + } + + var stdoutBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + var stderrBuf bytes.Buffer + cmd.Stderr = &stderrBuf + + err := cmd.Run() + if err != nil { + return "", "", xerrors.Wrap(err, "failed to execute shell command") + } + + return stdoutBuf.String(), stderrBuf.String(), nil +} diff --git a/internal/pkg/core/deployer/providers/ssh/define.go b/internal/pkg/core/deployer/providers/ssh/define.go new file mode 100644 index 00000000..6f30871b --- /dev/null +++ b/internal/pkg/core/deployer/providers/ssh/define.go @@ -0,0 +1,9 @@ +package ssh + +type OutputFormatType string + +const ( + OUTPUT_FORMAT_PEM = OutputFormatType("PEM") + OUTPUT_FORMAT_PFX = OutputFormatType("PFX") + OUTPUT_FORMAT_JKS = OutputFormatType("JKS") +) diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go new file mode 100644 index 00000000..54ecfd58 --- /dev/null +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -0,0 +1,251 @@ +package ssh + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "path/filepath" + + xerrors "github.com/pkg/errors" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/utils/x509" +) + +type SshDeployerConfig struct { + // SSH 主机。 + // 空值时默认为 "localhost"。 + SshHost string `json:"sshHost,omitempty"` + // SSH 端口。 + // 空值时默认为 22。 + SshPort int32 `json:"sshPort,omitempty"` + // SSH 登录用户名。 + SshUsername string `json:"sshUsername,omitempty"` + // SSH 登录密码。 + SshPassword string `json:"sshPassword,omitempty"` + // SSH 登录私钥。 + SshKey string `json:"sshKey,omitempty"` + // SSH 登录私钥口令。 + SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` + // 前置命令。 + PreCommand string `json:"preCommand,omitempty"` + // 后置命令。 + PostCommand string `json:"postCommand,omitempty"` + // 输出证书格式。 + // 空值时默认为 [OUTPUT_FORMAT_PEM]。 + OutputFormat OutputFormatType `json:"outputFormat,omitempty"` + // 输出证书文件路径。 + OutputCertPath string `json:"outputCertPath,omitempty"` + // 输出私钥文件路径。 + OutputKeyPath string `json:"outputKeyPath,omitempty"` + // PFX 导出密码。 + // 证书格式为 PFX 时必填。 + PfxPassword string `json:"pfxPassword,omitempty"` + // JKS 别名。 + // 证书格式为 JKS 时必填。 + JksAlias string `json:"jksAlias,omitempty"` + // JKS 密钥密码。 + // 证书格式为 JKS 时必填。 + JksKeypass string `json:"jksKeypass,omitempty"` + // JKS 存储密码。 + // 证书格式为 JKS 时必填。 + JksStorepass string `json:"jksStorepass,omitempty"` +} + +type SshDeployer struct { + config *SshDeployerConfig + logger deployer.Logger +} + +func New(config *SshDeployerConfig) (*SshDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *SshDeployerConfig, logger deployer.Logger) (*SshDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + return &SshDeployer{ + logger: logger, + config: config, + }, nil +} + +func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 连接 + client, err := createSshClient( + d.config.SshHost, + d.config.SshPort, + d.config.SshUsername, + d.config.SshPassword, + d.config.SshKey, + d.config.SshKeyPassphrase, + ) + if err != nil { + return nil, err + } + defer client.Close() + + d.logger.Appendt("SSH connected") + + // 执行前置命令 + if d.config.PreCommand != "" { + stdout, stderr, err := execSshCommand(client, d.config.PreCommand) + if err != nil { + return nil, xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr) + } + + d.logger.Appendt("SSH pre-command executed", stdout) + } + + // 上传证书和私钥文件 + switch d.config.OutputFormat { + case "", OUTPUT_FORMAT_PEM: + if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file uploaded") + + if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil { + return nil, err + } + + d.logger.Appendt("private key file uploaded") + + case OUTPUT_FORMAT_PFX: + pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) + if err != nil { + return nil, err + } + + d.logger.Appendt("certificate transformed to PFX") + + if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file uploaded") + + case OUTPUT_FORMAT_JKS: + jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) + if err != nil { + return nil, err + } + + d.logger.Appendt("certificate transformed to JKS") + + if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil { + return nil, err + } + + d.logger.Appendt("certificate file uploaded") + + default: + return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat) + } + + // 执行后置命令 + if d.config.PostCommand != "" { + stdout, stderr, err := execSshCommand(client, d.config.PostCommand) + if err != nil { + return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr) + } + + d.logger.Appendt("SSH post-command executed", stdout) + } + + return &deployer.DeployResult{}, nil +} + +func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) { + if host == "" { + host = "localhost" + } + + if port == 0 { + port = 22 + } + + var authMethod ssh.AuthMethod + if key != "" { + var signer ssh.Signer + var err error + + if keyPassphrase != "" { + signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase)) + } else { + signer, err = ssh.ParsePrivateKey([]byte(key)) + } + + if err != nil { + return nil, err + } + authMethod = ssh.PublicKeys(signer) + } else { + authMethod = ssh.Password(password) + } + + return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{authMethod}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) +} + +func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) { + session, err := sshCli.NewSession() + if err != nil { + return "", "", xerrors.Wrap(err, "failed to create ssh session") + } + + defer session.Close() + var stdoutBuf bytes.Buffer + session.Stdout = &stdoutBuf + var stderrBuf bytes.Buffer + session.Stderr = &stderrBuf + err = session.Run(command) + if err != nil { + return "", "", xerrors.Wrap(err, "failed to execute ssh command") + } + + return stdoutBuf.String(), stderrBuf.String(), nil +} + +func writeSftpFileString(sshCli *ssh.Client, path string, content string) error { + return writeSftpFile(sshCli, path, []byte(content)) +} + +func writeSftpFile(sshCli *ssh.Client, path string, data []byte) error { + sftpCli, err := sftp.NewClient(sshCli) + if err != nil { + return xerrors.Wrap(err, "failed to create sftp client") + } + defer sftpCli.Close() + + if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil { + return xerrors.Wrap(err, "failed to create remote directory") + } + + file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) + if err != nil { + return xerrors.Wrap(err, "failed to open remote file") + } + defer file.Close() + + _, err = file.Write(data) + if err != nil { + return xerrors.Wrap(err, "failed to write to remote file") + } + + return nil +} diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go new file mode 100644 index 00000000..4de107ff --- /dev/null +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -0,0 +1,81 @@ +package webhook + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" + "strings" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/utils/x509" + xhttp "github.com/usual2970/certimate/internal/utils/http" +) + +type WebhookDeployerConfig struct { + // Webhook URL。 + Url string `json:"url"` + // Webhook 变量字典。 + Variables map[string]string `json:"variables,omitempty"` +} + +type WebhookDeployer struct { + config *WebhookDeployerConfig + logger deployer.Logger +} + +var _ deployer.Deployer = (*WebhookDeployer)(nil) + +func New(config *WebhookDeployerConfig) (*WebhookDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *WebhookDeployerConfig, logger deployer.Logger) (*WebhookDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + return &WebhookDeployer{ + config: config, + logger: logger, + }, nil +} + +type webhookData struct { + SubjectAltNames string `json:"subjectAltNames"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privateKey"` + Variables map[string]string `json:"variables"` +} + +func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + certX509, err := x509.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + data := &webhookData{ + SubjectAltNames: strings.Join(certX509.DNSNames, ","), + Certificate: certPem, + PrivateKey: privkeyPem, + Variables: d.config.Variables, + } + body, _ := json.Marshal(data) + resp, err := xhttp.Req(d.config.Url, http.MethodPost, bytes.NewReader(body), map[string]string{ + "Content-Type": "application/json", + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to send webhook request") + } + + d.logger.Appendt("Webhook Response", string(resp)) + + return &deployer.DeployResult{}, nil +} From 51fb9dca58078f78ffb01506333290fdd3e312fc Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 20:03:11 +0800 Subject: [PATCH 05/17] test: add some unit test cases for new `Deployer` --- internal/notify/factory.go | 28 ++--- .../deployer/providers/local/local_test.go | 119 ++++++++++++++++++ .../core/deployer/providers/ssh/ssh_test.go | 56 +++++++++ .../deployer/providers/webhook/webhook.go | 6 +- .../providers/webhook/webhook_test.go | 40 ++++++ .../notifier/providers/email/email_test.go | 55 ++++---- .../providers/webhook/webhook_test.go | 41 ++++++ 7 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/local/local_test.go create mode 100644 internal/pkg/core/deployer/providers/ssh/ssh_test.go create mode 100644 internal/pkg/core/deployer/providers/webhook/webhook_test.go create mode 100644 internal/pkg/core/notifier/providers/webhook/webhook_test.go diff --git a/internal/notify/factory.go b/internal/notify/factory.go index ccdd5389..d4ce1e8f 100644 --- a/internal/notify/factory.go +++ b/internal/notify/factory.go @@ -5,20 +5,20 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" - notifierBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" - notifierDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" - notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" - notifierLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" - notifierServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" - notifierTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" - notifierWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + npBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" + npDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + npEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + npLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + npServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" + npTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + npWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" "github.com/usual2970/certimate/internal/pkg/utils/maps" ) func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) { switch channel { case domain.NotifyChannelEmail: - return notifierEmail.New(¬ifierEmail.EmailNotifierConfig{ + return npEmail.New(&npEmail.EmailNotifierConfig{ SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"), SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"), SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true), @@ -29,34 +29,34 @@ func createNotifier(channel string, channelConfig map[string]any) (notifier.Noti }) case domain.NotifyChannelWebhook: - return notifierWebhook.New(¬ifierWebhook.WebhookNotifierConfig{ + return npWebhook.New(&npWebhook.WebhookNotifierConfig{ Url: maps.GetValueAsString(channelConfig, "url"), }) case domain.NotifyChannelDingtalk: - return notifierDingTalk.New(¬ifierDingTalk.DingTalkNotifierConfig{ + return npDingTalk.New(&npDingTalk.DingTalkNotifierConfig{ AccessToken: maps.GetValueAsString(channelConfig, "accessToken"), Secret: maps.GetValueAsString(channelConfig, "secret"), }) case domain.NotifyChannelLark: - return notifierLark.New(¬ifierLark.LarkNotifierConfig{ + return npLark.New(&npLark.LarkNotifierConfig{ WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"), }) case domain.NotifyChannelTelegram: - return notifierTelegram.New(¬ifierTelegram.TelegramNotifierConfig{ + return npTelegram.New(&npTelegram.TelegramNotifierConfig{ ApiToken: maps.GetValueAsString(channelConfig, "apiToken"), ChatId: maps.GetValueAsInt64(channelConfig, "chatId"), }) case domain.NotifyChannelServerChan: - return notifierServerChan.New(¬ifierServerChan.ServerChanNotifierConfig{ + return npServerChan.New(&npServerChan.ServerChanNotifierConfig{ Url: maps.GetValueAsString(channelConfig, "url"), }) case domain.NotifyChannelBark: - return notifierBark.New(¬ifierBark.BarkNotifierConfig{ + return npBark.New(&npBark.BarkNotifierConfig{ DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"), ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"), }) diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go new file mode 100644 index 00000000..819c98cf --- /dev/null +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -0,0 +1,119 @@ +package local_test + +import ( + "context" + "os" + "testing" + + dLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" +) + +/* +Shell command to run this test: + + CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_OUTPUTKEYPATH="/path/to/your-output-key.pem" \ + go test -v -run TestDeploy local_test.go +*/ +func TestDeploy(t *testing.T) { + envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" + tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) + tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) + tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") + tOutputKeyPath := os.Getenv(envPrefix + "OUTPUTKEYPATH") + + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputCertPath: tOutputCertPath, + OutputKeyPath: tOutputKeyPath, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} + +/* +Shell command to run this test: + + CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_PFXPASSWORD="your-pfx-password" \ + go test -v -run TestDeploy_PFX local_test.go +*/ +func TestDeploy_PFX(t *testing.T) { + envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" + tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) + tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) + tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") + tPfxPassword := os.Getenv(envPrefix + "PFXPASSWORD") + + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputFormat: dLocal.OUTPUT_FORMAT_PFX, + OutputCertPath: tOutputCertPath, + PfxPassword: tPfxPassword, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} + +/* +Shell command to run this test: + + CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ + CERTIMATE_DEPLOYER_LOCAL_JKSALIAS="your-jks-alias" \ + CERTIMATE_DEPLOYER_LOCAL_JKSKEYPASS="your-jks-keypass" \ + CERTIMATE_DEPLOYER_LOCAL_JKSSTOREPASS="your-jks-storepass" \ + go test -v -run TestDeploy_JKS local_test.go +*/ +func TestDeploy_JKS(t *testing.T) { + envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" + tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) + tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) + tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") + tJksAlias := os.Getenv(envPrefix + "JKSALIAS") + tJksKeypass := os.Getenv(envPrefix + "JKSKEYPASS") + tJksStorepass := os.Getenv(envPrefix + "JKSSTOREPASS") + + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputFormat: dLocal.OUTPUT_FORMAT_JKS, + OutputCertPath: tOutputCertPath, + JksAlias: tJksAlias, + JksKeypass: tJksKeypass, + JksStorepass: tJksStorepass, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go new file mode 100644 index 00000000..3e841f0c --- /dev/null +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -0,0 +1,56 @@ +package ssh_test + +import ( + "context" + "os" + "strconv" + "testing" + + dSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" +) + +/* +Shell command to run this test: + + CERTIMATE_DEPLOYER_SSH_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + CERTIMATE_DEPLOYER_SSH_INPUTKEYPATH="/path/to/your-input-key.pem" \ + CERTIMATE_DEPLOYER_SSH_SSHHOST="localhost" \ + CERTIMATE_DEPLOYER_SSH_SSHPORT=22 \ + CERTIMATE_DEPLOYER_SSH_SSHUSERNAME="root" \ + CERTIMATE_DEPLOYER_SSH_SSHPASSWORD="password" \ + CERTIMATE_DEPLOYER_SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ + CERTIMATE_DEPLOYER_SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem" \ + go test -v -run TestDeploy ssh_test.go +*/ +func TestDeploy(t *testing.T) { + envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" + tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) + tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) + tSshHost := os.Getenv(envPrefix + "SSHHOST") + tSshPort, _ := strconv.ParseInt(os.Getenv(envPrefix+"SSHPORT"), 10, 32) + tSshUsername := os.Getenv(envPrefix + "SSHUSERNAME") + tSshPassword := os.Getenv(envPrefix + "SSHPASSWORD") + tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") + tOutputKeyPath := os.Getenv(envPrefix + "OUTPUTKEYPATH") + + deployer, err := dSsh.New(&dSsh.SshDeployerConfig{ + SshHost: tSshHost, + SshPort: int32(tSshPort), + SshUsername: tSshUsername, + SshPassword: tSshPassword, + OutputCertPath: tOutputCertPath, + OutputKeyPath: tOutputKeyPath, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 4de107ff..2e736fa2 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -77,5 +77,9 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem d.logger.Appendt("Webhook Response", string(resp)) - return &deployer.DeployResult{}, nil + return &deployer.DeployResult{ + DeploymentData: map[string]any{ + "responseText": string(resp), + }, + }, nil } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go new file mode 100644 index 00000000..c9ac73aa --- /dev/null +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -0,0 +1,40 @@ +package webhook_test + +import ( + "context" + "os" + "testing" + + dpWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" +) + +/* +Shell command to run this test: + + CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ + CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" \ + go test -v -run TestDeploy webhook_test.go +*/ +func TestDeploy(t *testing.T) { + envPrefix := "CERTIMATE_DEPLOYER_WEBHOOK_" + tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) + tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) + tUrl := os.Getenv(envPrefix + "URL") + + deployer, err := dpWebhook.New(&dpWebhook.WebhookDeployerConfig{ + Url: tUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 1197a1a6..8a9e860f 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -1,19 +1,25 @@ package email_test import ( + "context" "os" "strconv" "testing" - notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + npEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" +) + +const ( + MockSubject = "test_subject" + MockMessage = "test_message" ) /* Shell command to run this test: + CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \ CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \ CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \ - CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \ CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \ CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \ CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \ @@ -21,31 +27,34 @@ Shell command to run this test: go test -v -run TestNotify email_test.go */ func TestNotify(t *testing.T) { - smtpPort, err := strconv.ParseInt(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPPORT"), 10, 32) - if err != nil { - t.Errorf("invalid envvar: %+v", err) - panic(err) - } + envPrefix := "CERTIMATE_NOTIFIER_EMAIL_" + tSmtpHost := os.Getenv(envPrefix + "SMTPHOST") + tSmtpPort, _ := strconv.ParseInt(os.Getenv(envPrefix+"SMTPPORT"), 10, 32) + tSmtpTLS, _ := strconv.ParseBool(os.Getenv(envPrefix + "SMTPTLS")) + tSmtpUsername := os.Getenv(envPrefix + "USERNAME") + tSmtpPassword := os.Getenv(envPrefix + "PASSWORD") + tSenderAddress := os.Getenv(envPrefix + "SENDERADDRESS") + tReceiverAddress := os.Getenv(envPrefix + "RECEIVERADDRESS") - smtpTLS, err := strconv.ParseBool(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPTLS")) - if err != nil { - t.Errorf("invalid envvar: %+v", err) - panic(err) - } - - res, err := notifierEmail.New(¬ifierEmail.EmailNotifierConfig{ - SmtpHost: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPHOST"), - SmtpPort: int32(smtpPort), - SmtpTLS: smtpTLS, - Username: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_USERNAME"), - Password: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_PASSWORD"), - SenderAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS"), - ReceiverAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS"), + notifier, err := npEmail.New(&npEmail.EmailNotifierConfig{ + SmtpHost: tSmtpHost, + SmtpPort: int32(tSmtpPort), + SmtpTLS: tSmtpTLS, + Username: tSmtpUsername, + Password: tSmtpPassword, + SenderAddress: tSenderAddress, + ReceiverAddress: tReceiverAddress, }) if err != nil { - t.Errorf("invalid envvar: %+v", err) + t.Errorf("err: %+v", err) panic(err) } - t.Logf("notify result: %v", res) + res, err := notifier.Notify(context.Background(), MockSubject, MockMessage) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) } diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go new file mode 100644 index 00000000..eca46819 --- /dev/null +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -0,0 +1,41 @@ +package webhook_test + +import ( + "context" + "os" + "testing" + + npWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" +) + +const ( + MockSubject = "test_subject" + MockMessage = "test_message" +) + +/* +Shell command to run this test: + + CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" \ + go test -v -run TestNotify webhook_test.go +*/ +func TestNotify(t *testing.T) { + envPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_" + tUrl := os.Getenv(envPrefix + "URL") + + notifier, err := npWebhook.New(&npWebhook.WebhookNotifierConfig{ + Url: tUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + res, err := notifier.Notify(context.Background(), MockSubject, MockMessage) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + t.Logf("ok: %v", res) +} From 414d8d140e7f5ae775cd328b300ec012957ce8a5 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 21:18:36 +0800 Subject: [PATCH 06/17] test: use flag arguments in test cases for `Notifier` and `Deployer` --- .../deployer/providers/local/local_test.go | 247 +++++++++++------- .../core/deployer/providers/ssh/ssh_test.go | 112 +++++--- .../providers/webhook/webhook_test.go | 71 +++-- .../notifier/providers/email/email_test.go | 111 +++++--- .../providers/webhook/webhook_test.go | 60 +++-- 5 files changed, 386 insertions(+), 215 deletions(-) diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go index 819c98cf..74307734 100644 --- a/internal/pkg/core/deployer/providers/local/local_test.go +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -2,118 +2,185 @@ import ( "context" + "flag" + "fmt" "os" + "strings" "testing" dLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" ) -/* -Shell command to run this test: +var ( + fInputCertPath string + fInputKeyPath string + fOutputCertPath string + fOutputKeyPath string + fPfxPassword string + fJksAlias string + fJksKeypass string + fJksStorepass string +) - CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ - CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_OUTPUTKEYPATH="/path/to/your-output-key.pem" \ - go test -v -run TestDeploy local_test.go -*/ -func TestDeploy(t *testing.T) { - envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" - tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) - tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) - tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") - tOutputKeyPath := os.Getenv(envPrefix + "OUTPUTKEYPATH") +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_LOCAL_" - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputCertPath: tOutputCertPath, - OutputKeyPath: tOutputKeyPath, - }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - t.Logf("ok: %v", res) + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "") + flag.StringVar(&fOutputKeyPath, argsPrefix+"OUTPUTKEYPATH", "", "") + flag.StringVar(&fPfxPassword, argsPrefix+"PFXPASSWORD", "", "") + flag.StringVar(&fJksAlias, argsPrefix+"JKSALIAS", "", "") + flag.StringVar(&fJksKeypass, argsPrefix+"JKSKEYPASS", "", "") + flag.StringVar(&fJksStorepass, argsPrefix+"JKSSTOREPASS", "", "") } /* Shell command to run this test: - CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ - CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_PFXPASSWORD="your-pfx-password" \ - go test -v -run TestDeploy_PFX local_test.go + go test -v local_test.go -args \ + --CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert" \ + --CERTIMATE_DEPLOYER_LOCAL_OUTPUTKEYPATH="/path/to/your-output-key" \ + --CERTIMATE_DEPLOYER_LOCAL_PFXPASSWORD="your-pfx-password" \ + --CERTIMATE_DEPLOYER_LOCAL_JKSALIAS="your-jks-alias" \ + --CERTIMATE_DEPLOYER_LOCAL_JKSKEYPASS="your-jks-keypass" \ + --CERTIMATE_DEPLOYER_LOCAL_JKSSTOREPASS="your-jks-storepass" */ -func TestDeploy_PFX(t *testing.T) { - envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" - tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) - tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) - tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") - tPfxPassword := os.Getenv(envPrefix + "PFXPASSWORD") +func Test(t *testing.T) { + flag.Parse() - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_PFX, - OutputCertPath: tOutputCertPath, - PfxPassword: tPfxPassword, + t.Run("Deploy_PEM", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath), + fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), + }, "\n")) + + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputCertPath: fOutputCertPath, + OutputKeyPath: fOutputKeyPath, + }) + 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 + } + + fstat1, err := os.Stat(fOutputCertPath) + if err != nil { + t.Errorf("err: %+v", err) + return + } else if fstat1.Size() == 0 { + t.Errorf("err: empty output certificate file") + return + } + + fstat2, err := os.Stat(fOutputKeyPath) + if err != nil { + t.Errorf("err: %+v", err) + return + } else if fstat2.Size() == 0 { + t.Errorf("err: empty output private key file") + return + } + + t.Logf("ok: %v", res) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } + t.Run("Deploy_PFX", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath), + fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), + fmt.Sprintf("PFXPASSWORD: %v", fPfxPassword), + }, "\n")) - t.Logf("ok: %v", res) -} + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputFormat: dLocal.OUTPUT_FORMAT_PFX, + OutputCertPath: fOutputCertPath, + OutputKeyPath: fOutputKeyPath, + PfxPassword: fPfxPassword, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } -/* -Shell command to run this test: + 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 + } - CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \ - CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ - CERTIMATE_DEPLOYER_LOCAL_JKSALIAS="your-jks-alias" \ - CERTIMATE_DEPLOYER_LOCAL_JKSKEYPASS="your-jks-keypass" \ - CERTIMATE_DEPLOYER_LOCAL_JKSSTOREPASS="your-jks-storepass" \ - go test -v -run TestDeploy_JKS local_test.go -*/ -func TestDeploy_JKS(t *testing.T) { - envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" - tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) - tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) - tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") - tJksAlias := os.Getenv(envPrefix + "JKSALIAS") - tJksKeypass := os.Getenv(envPrefix + "JKSKEYPASS") - tJksStorepass := os.Getenv(envPrefix + "JKSSTOREPASS") + fstat, err := os.Stat(fOutputCertPath) + if err != nil { + t.Errorf("err: %+v", err) + return + } else if fstat.Size() == 0 { + t.Errorf("err: empty output certificate file") + return + } - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_JKS, - OutputCertPath: tOutputCertPath, - JksAlias: tJksAlias, - JksKeypass: tJksKeypass, - JksStorepass: tJksStorepass, + t.Logf("ok: %v", res) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } + t.Run("Deploy_JKS", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath), + fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), + fmt.Sprintf("JKSALIAS: %v", fJksAlias), + fmt.Sprintf("JKSKEYPASS: %v", fJksKeypass), + fmt.Sprintf("JKSSTOREPASS: %v", fJksStorepass), + }, "\n")) - t.Logf("ok: %v", res) + deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + OutputFormat: dLocal.OUTPUT_FORMAT_JKS, + OutputCertPath: fOutputCertPath, + OutputKeyPath: fOutputKeyPath, + JksAlias: fJksAlias, + JksKeypass: fJksKeypass, + JksStorepass: fJksStorepass, + }) + 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 + } + + fstat, err := os.Stat(fOutputCertPath) + if err != nil { + t.Errorf("err: %+v", err) + return + } else if fstat.Size() == 0 { + t.Errorf("err: empty output certificate file") + return + } + + t.Logf("ok: %v", res) + }) } diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index 3e841f0c..dbc0109c 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -2,55 +2,89 @@ import ( "context" + "flag" + "fmt" "os" - "strconv" + "strings" "testing" dSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" ) +var ( + fInputCertPath string + fInputKeyPath string + fSshHost string + fSshPort int + fSshUsername string + fSshPassword string + fOutputCertPath string + fOutputKeyPath string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_SSH_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSshHost, argsPrefix+"SSHHOST", "", "") + flag.IntVar(&fSshPort, argsPrefix+"SSHPORT", 0, "") + flag.StringVar(&fSshUsername, argsPrefix+"SSHUSERNAME", "", "") + flag.StringVar(&fSshPassword, argsPrefix+"SSHPASSWORD", "", "") + flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "") + flag.StringVar(&fOutputKeyPath, argsPrefix+"OUTPUTKEYPATH", "", "") +} + /* Shell command to run this test: - CERTIMATE_DEPLOYER_SSH_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - CERTIMATE_DEPLOYER_SSH_INPUTKEYPATH="/path/to/your-input-key.pem" \ - CERTIMATE_DEPLOYER_SSH_SSHHOST="localhost" \ - CERTIMATE_DEPLOYER_SSH_SSHPORT=22 \ - CERTIMATE_DEPLOYER_SSH_SSHUSERNAME="root" \ - CERTIMATE_DEPLOYER_SSH_SSHPASSWORD="password" \ - CERTIMATE_DEPLOYER_SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ - CERTIMATE_DEPLOYER_SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem" \ - go test -v -run TestDeploy ssh_test.go + go test -v ssh_test.go -args \ + --CERTIMATE_DEPLOYER_SSH_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_SSH_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_SSH_SSHHOST="localhost" \ + --CERTIMATE_DEPLOYER_SSH_SSHPORT=22 \ + --CERTIMATE_DEPLOYER_SSH_SSHUSERNAME="root" \ + --CERTIMATE_DEPLOYER_SSH_SSHPASSWORD="password" \ + --CERTIMATE_DEPLOYER_SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ + --CERTIMATE_DEPLOYER_SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem" */ -func TestDeploy(t *testing.T) { - envPrefix := "CERTIMATE_DEPLOYER_LOCAL_" - tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) - tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) - tSshHost := os.Getenv(envPrefix + "SSHHOST") - tSshPort, _ := strconv.ParseInt(os.Getenv(envPrefix+"SSHPORT"), 10, 32) - tSshUsername := os.Getenv(envPrefix + "SSHUSERNAME") - tSshPassword := os.Getenv(envPrefix + "SSHPASSWORD") - tOutputCertPath := os.Getenv(envPrefix + "OUTPUTCERTPATH") - tOutputKeyPath := os.Getenv(envPrefix + "OUTPUTKEYPATH") +func Test(t *testing.T) { + flag.Parse() - deployer, err := dSsh.New(&dSsh.SshDeployerConfig{ - SshHost: tSshHost, - SshPort: int32(tSshPort), - SshUsername: tSshUsername, - SshPassword: tSshPassword, - OutputCertPath: tOutputCertPath, - OutputKeyPath: tOutputKeyPath, + 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("SSHHOST: %v", fSshHost), + fmt.Sprintf("SSHPORT: %v", fSshPort), + fmt.Sprintf("SSHUSERNAME: %v", fSshUsername), + fmt.Sprintf("SSHPASSWORD: %v", fSshPassword), + fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath), + fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), + }, "\n")) + + deployer, err := dSsh.New(&dSsh.SshDeployerConfig{ + SshHost: fSshHost, + SshPort: int32(fSshPort), + SshUsername: fSshUsername, + SshPassword: fSshPassword, + OutputCertPath: fOutputCertPath, + OutputKeyPath: fOutputKeyPath, + }) + if err != nil { + t.Errorf("err: %+v", err) + panic(err) + } + + 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) + panic(err) + } + + t.Logf("ok: %v", res) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - t.Logf("ok: %v", res) } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index c9ac73aa..8c2cbde8 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -2,39 +2,64 @@ import ( "context" + "flag" + "fmt" "os" + "strings" "testing" dpWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" ) +var ( + fInputCertPath string + fInputKeyPath string + fUrl string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_WEBHOOK_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUrl, argsPrefix+"URL", "", "") +} + /* Shell command to run this test: - CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ - CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" \ - go test -v -run TestDeploy webhook_test.go + go test -v webhook_test.go -args \ + --CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" */ -func TestDeploy(t *testing.T) { - envPrefix := "CERTIMATE_DEPLOYER_WEBHOOK_" - tInputCertData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTCERTPATH")) - tInputKeyData, _ := os.ReadFile(os.Getenv(envPrefix + "INPUTKEYPATH")) - tUrl := os.Getenv(envPrefix + "URL") +func Test(t *testing.T) { + flag.Parse() - deployer, err := dpWebhook.New(&dpWebhook.WebhookDeployerConfig{ - Url: tUrl, + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("URL: %v", fUrl), + }, "\n")) + + deployer, err := dpWebhook.New(&dpWebhook.WebhookDeployerConfig{ + Url: fUrl, + }) + 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) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - res, err := deployer.Deploy(context.Background(), string(tInputCertData), string(tInputKeyData)) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - t.Logf("ok: %v", res) } diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 8a9e860f..a401ac19 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -2,59 +2,88 @@ import ( "context" - "os" - "strconv" + "flag" + "fmt" + "strings" "testing" npEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" ) const ( - MockSubject = "test_subject" - MockMessage = "test_message" + mockSubject = "test_subject" + mockMessage = "test_message" ) +var ( + fSmtpHost string + fSmtpPort int + fSmtpTLS bool + fUsername string + fPassword string + fSenderAddress string + fReceiverAddress string +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_EMAIL_" + + flag.StringVar(&fSmtpHost, argsPrefix+"SMTPHOST", "", "") + flag.IntVar(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "") + flag.BoolVar(&fSmtpTLS, argsPrefix+"SMTPTLS", false, "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fSenderAddress, argsPrefix+"SENDERADDRESS", "", "") + flag.StringVar(&fReceiverAddress, argsPrefix+"RECEIVERADDRESS", "", "") +} + /* Shell command to run this test: - CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \ - CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \ - CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \ - CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \ - CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \ - CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \ - CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" \ - go test -v -run TestNotify email_test.go + go test -v email_test.go -args \ + --CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \ + --CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \ + --CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \ + --CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \ + --CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \ + --CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \ + --CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" */ -func TestNotify(t *testing.T) { - envPrefix := "CERTIMATE_NOTIFIER_EMAIL_" - tSmtpHost := os.Getenv(envPrefix + "SMTPHOST") - tSmtpPort, _ := strconv.ParseInt(os.Getenv(envPrefix+"SMTPPORT"), 10, 32) - tSmtpTLS, _ := strconv.ParseBool(os.Getenv(envPrefix + "SMTPTLS")) - tSmtpUsername := os.Getenv(envPrefix + "USERNAME") - tSmtpPassword := os.Getenv(envPrefix + "PASSWORD") - tSenderAddress := os.Getenv(envPrefix + "SENDERADDRESS") - tReceiverAddress := os.Getenv(envPrefix + "RECEIVERADDRESS") +func Test(t *testing.T) { + flag.Parse() - notifier, err := npEmail.New(&npEmail.EmailNotifierConfig{ - SmtpHost: tSmtpHost, - SmtpPort: int32(tSmtpPort), - SmtpTLS: tSmtpTLS, - Username: tSmtpUsername, - Password: tSmtpPassword, - SenderAddress: tSenderAddress, - ReceiverAddress: tReceiverAddress, + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("SMTPHOST: %v", fSmtpHost), + fmt.Sprintf("SMTPPORT: %v", fSmtpPort), + fmt.Sprintf("SMTPTLS: %v", fSmtpTLS), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("SENDERADDRESS: %v", fSenderAddress), + fmt.Sprintf("RECEIVERADDRESS: %v", fReceiverAddress), + }, "\n")) + + notifier, err := npEmail.New(&npEmail.EmailNotifierConfig{ + SmtpHost: fSmtpHost, + SmtpPort: int32(fSmtpPort), + SmtpTLS: fSmtpTLS, + Username: fUsername, + Password: fPassword, + SenderAddress: fSenderAddress, + ReceiverAddress: fReceiverAddress, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - res, err := notifier.Notify(context.Background(), MockSubject, MockMessage) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - t.Logf("ok: %v", res) } diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index eca46819..fc9ded52 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -2,40 +2,56 @@ import ( "context" - "os" + "flag" + "fmt" + "strings" "testing" npWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" ) const ( - MockSubject = "test_subject" - MockMessage = "test_message" + mockSubject = "test_subject" + mockMessage = "test_message" ) +var fUrl string + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_" + + flag.StringVar(&fUrl, argsPrefix+"URL", "", "") +} + /* Shell command to run this test: - CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" \ - go test -v -run TestNotify webhook_test.go + go test -v webhook_test.go -args \ + --CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" */ -func TestNotify(t *testing.T) { - envPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_" - tUrl := os.Getenv(envPrefix + "URL") +func Test(t *testing.T) { + flag.Parse() - notifier, err := npWebhook.New(&npWebhook.WebhookNotifierConfig{ - Url: tUrl, + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("URL: %v", fUrl), + }, "\n")) + + notifier, err := npWebhook.New(&npWebhook.WebhookNotifierConfig{ + Url: fUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) }) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - res, err := notifier.Notify(context.Background(), MockSubject, MockMessage) - if err != nil { - t.Errorf("err: %+v", err) - panic(err) - } - - t.Logf("ok: %v", res) } From 6a151865f7f3652409edce952420f66d2c25da3b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 22:04:00 +0800 Subject: [PATCH 07/17] feat: implement k8s secret `Deployer` --- .../providers/k8s-secret/k8s_secret.go | 158 ++++++++++++++++++ .../providers/k8s-secret/k8s_secret_test.go | 80 +++++++++ .../providers/local/{define.go => defines.go} | 0 .../providers/ssh/{define.go => defines.go} | 0 .../providers/webhook/webhook_test.go | 2 +- 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go create mode 100644 internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go rename internal/pkg/core/deployer/providers/local/{define.go => defines.go} (100%) rename internal/pkg/core/deployer/providers/ssh/{define.go => defines.go} (100%) diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go new file mode 100644 index 00000000..1e75b615 --- /dev/null +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go @@ -0,0 +1,158 @@ +package k8ssecret + +import ( + "context" + "errors" + "strings" + + xerrors "github.com/pkg/errors" + k8sCore "k8s.io/api/core/v1" + k8sMeta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/utils/x509" +) + +type K8sSecretDeployerConfig struct { + // kubeconfig 文件内容。 + KubeConfig string `json:"kubeConfig,omitempty"` + // K8s 命名空间。 + Namespace string `json:"namespace,omitempty"` + // K8s Secret 名称。 + SecretName string `json:"secretName"` + // K8s Secret 中用于存放证书的 Key。 + SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"` + // K8s Secret 中用于存放私钥的 Key。 + SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"` +} + +type K8sSecretDeployer struct { + config *K8sSecretDeployerConfig + logger deployer.Logger +} + +func New(config *K8sSecretDeployerConfig) (*K8sSecretDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *K8sSecretDeployerConfig, logger deployer.Logger) (*K8sSecretDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + return &K8sSecretDeployer{ + logger: logger, + config: config, + }, nil +} + +func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.Namespace == "" { + return nil, errors.New("config `namespace` is required") + } + if d.config.SecretName == "" { + return nil, errors.New("config `secretName` is required") + } + if d.config.SecretDataKeyForCrt == "" { + return nil, errors.New("config `secretDataKeyForCrt` is required") + } + if d.config.SecretDataKeyForKey == "" { + return nil, errors.New("config `secretDataKeyForKey` is required") + } + + certX509, err := x509.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + // 连接 + client, err := createK8sClient(d.config.KubeConfig) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create k8s client") + } + + var secretPayload *k8sCore.Secret + secretAnnotations := map[string]string{ + "certimate/common-name": certX509.Subject.CommonName, + "certimate/subject-sn": certX509.Subject.SerialNumber, + "certimate/subject-alt-names": strings.Join(certX509.DNSNames, ","), + "certimate/issuer-sn": certX509.Issuer.SerialNumber, + "certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","), + } + + // 获取 Secret 实例,如果不存在则创建 + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8sMeta.GetOptions{}) + if err != nil { + secretPayload = &k8sCore.Secret{ + TypeMeta: k8sMeta.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: k8sMeta.ObjectMeta{ + Name: d.config.SecretName, + Annotations: secretAnnotations, + }, + Type: k8sCore.SecretType("kubernetes.io/tls"), + } + secretPayload.Data = make(map[string][]byte) + secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPem) + secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPem) + + _, err = client.CoreV1().Secrets(d.config.Namespace).Create(context.TODO(), secretPayload, k8sMeta.CreateOptions{}) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create k8s secret") + } else { + d.logger.Appendf("k8s secret created", secretPayload) + return &deployer.DeployResult{}, nil + } + } + + // 更新 Secret 实例 + secretPayload.Type = k8sCore.SecretType("kubernetes.io/tls") + if secretPayload.ObjectMeta.Annotations == nil { + secretPayload.ObjectMeta.Annotations = secretAnnotations + } else { + for k, v := range secretAnnotations { + secretPayload.ObjectMeta.Annotations[k] = v + } + } + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8sMeta.UpdateOptions{}) + if err != nil { + return nil, xerrors.Wrap(err, "failed to update k8s secret") + } + + d.logger.Appendf("k8s secret updated", secretPayload) + + return &deployer.DeployResult{}, nil +} + +func createK8sClient(kubeConfig string) (*kubernetes.Clientset, error) { + var config *rest.Config + var err error + if kubeConfig == "" { + config, err = rest.InClusterConfig() + } else { + kubeConfig, err := clientcmd.NewClientConfigFromBytes([]byte(kubeConfig)) + if err != nil { + return nil, err + } + config, err = kubeConfig.ClientConfig() + } + if err != nil { + return nil, err + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go new file mode 100644 index 00000000..f6028307 --- /dev/null +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go @@ -0,0 +1,80 @@ +package k8ssecret_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" +) + +var ( + fInputCertPath string + fInputKeyPath string + fNamespace string + fSecretName string + fSecretDataKeyForCrt string + fSecretDataKeyForKey string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_K8SSECRET_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fNamespace, argsPrefix+"NAMESPACE", "default", "") + flag.StringVar(&fSecretName, argsPrefix+"SECRETNAME", "", "") + flag.StringVar(&fSecretDataKeyForCrt, argsPrefix+"SECRETDATAKEYFORCRT", "tls.crt", "") + flag.StringVar(&fSecretDataKeyForKey, argsPrefix+"SECRETDATAKEYFORKEY", "tls.key", "") +} + +/* +Shell command to run this test: + + go test -v webhook_test.go -args \ + --CERTIMATE_DEPLOYER_K8SSECRET_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_K8SSECRET_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_K8SSECRET_NAMESPACE="default" \ + --CERTIMATE_DEPLOYER_K8SSECRET_SECRETNAME="secret" \ + --CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORCRT="tls.crt" \ + --CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORKEY="tls.key" +*/ +func Test(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("NAMESPACE: %v", fNamespace), + fmt.Sprintf("SECRETNAME: %v", fSecretName), + fmt.Sprintf("SECRETDATAKEYFORCRT: %v", fSecretDataKeyForCrt), + fmt.Sprintf("SECRETDATAKEYFORKEY: %v", fSecretDataKeyForKey), + }, "\n")) + + deployer, err := dpK8sSecret.New(&dpK8sSecret.K8sSecretDeployerConfig{ + Namespace: fNamespace, + SecretName: fSecretName, + SecretDataKeyForCrt: fSecretDataKeyForCrt, + SecretDataKeyForKey: fSecretDataKeyForKey, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/local/define.go b/internal/pkg/core/deployer/providers/local/defines.go similarity index 100% rename from internal/pkg/core/deployer/providers/local/define.go rename to internal/pkg/core/deployer/providers/local/defines.go diff --git a/internal/pkg/core/deployer/providers/ssh/define.go b/internal/pkg/core/deployer/providers/ssh/defines.go similarity index 100% rename from internal/pkg/core/deployer/providers/ssh/define.go rename to internal/pkg/core/deployer/providers/ssh/defines.go diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index 8c2cbde8..d30f9599 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -36,7 +36,7 @@ Shell command to run this test: func Test(t *testing.T) { flag.Parse() - t.Run("Notify", func(t *testing.T) { + t.Run("Deploy", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), From a6c93ef9b81232b1310db153bf7c7ac6cab3b02e Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 22:11:47 +0800 Subject: [PATCH 08/17] test: fix typo --- .../pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go index f6028307..7f1f48f5 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go @@ -34,7 +34,7 @@ func init() { /* Shell command to run this test: - go test -v webhook_test.go -args \ + go test -v k8s_secret_test.go -args \ --CERTIMATE_DEPLOYER_K8SSECRET_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_K8SSECRET_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_K8SSECRET_NAMESPACE="default" \ From 82807fcc1bb03b98635b92bf31f2b5daa8f02933 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Nov 2024 22:43:15 +0800 Subject: [PATCH 09/17] refactor: clean code --- internal/deployer/volcengine_cdn.go | 2 +- internal/deployer/volcengine_live.go | 2 +- .../core/deployer/providers/local/local.go | 12 ++-- .../pkg/core/deployer/providers/ssh/ssh.go | 18 ++--- .../deployer/providers/webhook/webhook.go | 2 +- .../providers/aliyun-cas/aliyun_cas.go | 14 ++-- .../providers/aliyun-slb/aliyun_slb.go | 14 ++-- .../providers/byteplus-cdn/byteplus_cdn.go | 57 ++++++++------- .../volcengine-cdn/volcengine_cdn.go | 70 ++++++++++--------- .../volcengine-live/volcengine_live.go | 47 +++++++------ 10 files changed, 127 insertions(+), 111 deletions(-) diff --git a/internal/deployer/volcengine_cdn.go b/internal/deployer/volcengine_cdn.go index 6955716f..a154d3fa 100644 --- a/internal/deployer/volcengine_cdn.go +++ b/internal/deployer/volcengine_cdn.go @@ -29,7 +29,7 @@ func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) { client := cdn.NewInstance() client.Client.SetAccessKey(access.AccessKeyID) client.Client.SetSecretKey(access.SecretAccessKey) - uploader, err := volcenginecdn.New(&volcenginecdn.VolcengineCDNUploaderConfig{ + uploader, err := volcenginecdn.New(&volcenginecdn.VolcEngineCDNUploaderConfig{ AccessKeyId: access.AccessKeyID, AccessKeySecret: access.SecretAccessKey, }) diff --git a/internal/deployer/volcengine_live.go b/internal/deployer/volcengine_live.go index f456bb83..2d037742 100644 --- a/internal/deployer/volcengine_live.go +++ b/internal/deployer/volcengine_live.go @@ -33,7 +33,7 @@ func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) { AccessKeyID: access.AccessKeyID, SecretAccessKey: access.SecretAccessKey, }) - uploader, err := volcenginelive.New(&volcenginelive.VolcengineLiveUploaderConfig{ + uploader, err := volcenginelive.New(&volcenginelive.VolcEngineLiveUploaderConfig{ AccessKeyId: access.AccessKeyID, AccessKeySecret: access.SecretAccessKey, }) diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go index 073014db..9770025d 100644 --- a/internal/pkg/core/deployer/providers/local/local.go +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -83,13 +83,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s switch d.config.OutputFormat { case "", OUTPUT_FORMAT_PEM: if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to save certificate file") } d.logger.Appendt("certificate file saved") if err := fs.WriteFileString(d.config.OutputKeyPath, privkeyPem); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to save private key file") } d.logger.Appendt("private key file saved") @@ -97,13 +97,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s case OUTPUT_FORMAT_PFX: pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to transform certificate to PFX") } d.logger.Appendt("certificate transformed to PFX") if err := fs.WriteFile(d.config.OutputCertPath, pfxData); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to save certificate file") } d.logger.Appendt("certificate file saved") @@ -111,13 +111,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s case OUTPUT_FORMAT_JKS: jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to transform certificate to JKS") } d.logger.Appendt("certificate transformed to JKS") if err := fs.WriteFile(d.config.OutputCertPath, jksData); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to save certificate file") } d.logger.Appendt("certificate file uploaded") diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 54ecfd58..982e1ff4 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -91,7 +91,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str d.config.SshKeyPassphrase, ) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to create ssh client") } defer client.Close() @@ -111,13 +111,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str switch d.config.OutputFormat { case "", OUTPUT_FORMAT_PEM: if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to upload certificate file") } d.logger.Appendt("certificate file uploaded") if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to upload private key file") } d.logger.Appendt("private key file uploaded") @@ -125,13 +125,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str case OUTPUT_FORMAT_PFX: pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to transform certificate to PFX") } d.logger.Appendt("certificate transformed to PFX") if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to upload certificate file") } d.logger.Appendt("certificate file uploaded") @@ -139,13 +139,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str case OUTPUT_FORMAT_JKS: jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to transform certificate to JKS") } d.logger.Appendt("certificate transformed to JKS") if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to upload certificate file") } d.logger.Appendt("certificate file uploaded") @@ -205,7 +205,7 @@ func createSshClient(host string, port int32, username string, password string, func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) { session, err := sshCli.NewSession() if err != nil { - return "", "", xerrors.Wrap(err, "failed to create ssh session") + return "", "", err } defer session.Close() @@ -215,7 +215,7 @@ func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) session.Stderr = &stderrBuf err = session.Run(command) if err != nil { - return "", "", xerrors.Wrap(err, "failed to execute ssh command") + return "", "", err } return stdoutBuf.String(), stderrBuf.String(), nil diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 2e736fa2..d669a9e4 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -58,7 +58,7 @@ type webhookData struct { func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { certX509, err := x509.ParseCertificateFromPEM(certPem) if err != nil { - return nil, err + return nil, xerrors.Wrap(err, "failed to parse x509") } data := &webhookData{ diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index 463d10bd..f13d2b33 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -144,11 +144,6 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 } - aConfig := &aliyunOpen.Config{ - AccessKeyId: tea.String(accessKeyId), - AccessKeySecret: tea.String(accessKeySecret), - } - var endpoint string switch region { case "cn-hangzhou": @@ -156,9 +151,14 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl default: endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region) } - aConfig.Endpoint = tea.String(endpoint) - client, err := aliyunCas.NewClient(aConfig) + config := &aliyunOpen.Config{ + Endpoint: tea.String(endpoint), + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + } + + client, err := aliyunCas.NewClient(config) if err != nil { return nil, err } diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index 9b62361c..df14aea9 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -121,11 +121,6 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州 } - aConfig := &aliyunOpen.Config{ - AccessKeyId: tea.String(accessKeyId), - AccessKeySecret: tea.String(accessKeySecret), - } - var endpoint string switch region { case @@ -137,9 +132,14 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl default: endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region) } - aConfig.Endpoint = tea.String(endpoint) - client, err := aliyunSlb.NewClient(aConfig) + config := &aliyunOpen.Config{ + Endpoint: tea.String(endpoint), + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + } + + client, err := aliyunSlb.NewClient(config) if err != nil { return nil, err } diff --git a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go index f27aebbc..a5364a02 100644 --- a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go @@ -2,6 +2,7 @@ package bytepluscdn import ( "context" + "crypto/sha1" "crypto/sha256" "encoding/hex" "errors" @@ -9,9 +10,11 @@ import ( "strings" "time" - "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn" + bpCdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn" xerrors "github.com/pkg/errors" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + "github.com/usual2970/certimate/internal/pkg/utils/cast" "github.com/usual2970/certimate/internal/pkg/utils/x509" ) @@ -22,7 +25,7 @@ type ByteplusCDNUploaderConfig struct { type ByteplusCDNUploader struct { config *ByteplusCDNUploaderConfig - sdkClient *cdn.CDN + sdkClient *bpCdn.CDN } var _ uploader.Uploader = (*ByteplusCDNUploader)(nil) @@ -32,14 +35,13 @@ func New(config *ByteplusCDNUploaderConfig) (*ByteplusCDNUploader, error) { return nil, errors.New("config is nil") } - instance := cdn.NewInstance() - client := instance.Client - client.SetAccessKey(config.AccessKey) - client.SetSecretKey(config.SecretKey) + client := bpCdn.NewInstance() + client.Client.SetAccessKey(config.AccessKey) + client.Client.SetSecretKey(config.SecretKey) return &ByteplusCDNUploader{ config: config, - sdkClient: instance, + sdkClient: client, }, nil } @@ -49,17 +51,17 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke if err != nil { return nil, err } + // 查询证书列表,避免重复上传 // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo - pageNum := int64(1) - pageSize := int64(100) - certSource := "cert_center" - listCertInfoReq := &cdn.ListCertInfoRequest{ - PageNum: &pageNum, - PageSize: &pageSize, - Source: &certSource, + listCertInfoPageNum := int64(1) + listCertInfoPageSize := int64(100) + listCertInfoTotal := 0 + listCertInfoReq := &bpCdn.ListCertInfoRequest{ + PageNum: cast.Int64Ptr(listCertInfoPageNum), + PageSize: cast.Int64Ptr(listCertInfoPageSize), + Source: cast.StringPtr("cert_center"), } - searchTotal := 0 for { listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) if err != nil { @@ -68,8 +70,10 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke if listCertInfoResp.Result.CertInfo != nil { for _, certDetail := range listCertInfoResp.Result.CertInfo { - hash := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(hash[:]), certDetail.CertFingerprint.Sha256) + fingerprintSha1 := sha1.Sum(certX509.Raw) + fingerprintSha256 := sha256.Sum256(certX509.Raw) + isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) && + strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256) // 如果已存在相同证书,直接返回已有的证书信息 if isSameCert { return &uploader.UploadResult{ @@ -80,23 +84,26 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke } } - searchTotal += len(listCertInfoResp.Result.CertInfo) - if int(listCertInfoResp.Result.Total) > searchTotal { - pageNum++ - } else { + listCertInfoLen := len(listCertInfoResp.Result.CertInfo) + if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen { break + } else { + listCertInfoPageNum++ + listCertInfoTotal += listCertInfoLen } - } + + // 生成新证书名(需符合 BytePlus 命名规则) var certId, certName string certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + // 上传新证书 // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-addcertificate - addCertificateReq := &cdn.AddCertificateRequest{ + addCertificateReq := &bpCdn.AddCertificateRequest{ Certificate: certPem, PrivateKey: privkeyPem, - Source: &certSource, - Desc: &certName, + Source: cast.StringPtr("cert_center"), + Desc: cast.StringPtr(certName), } addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq) if err != nil { diff --git a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go index b53ef4c5..7822a02d 100644 --- a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go @@ -2,6 +2,7 @@ package volcenginecdn import ( "context" + "crypto/sha1" "crypto/sha256" "encoding/hex" "errors" @@ -10,56 +11,57 @@ import ( "time" xerrors "github.com/pkg/errors" + veCdn "github.com/volcengine/volc-sdk-golang/service/cdn" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + "github.com/usual2970/certimate/internal/pkg/utils/cast" "github.com/usual2970/certimate/internal/pkg/utils/x509" - "github.com/volcengine/volc-sdk-golang/service/cdn" ) -type VolcengineCDNUploaderConfig struct { +type VolcEngineCDNUploaderConfig struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` } -type VolcengineCDNUploader struct { - config *VolcengineCDNUploaderConfig - sdkClient *cdn.CDN +type VolcEngineCDNUploader struct { + config *VolcEngineCDNUploaderConfig + sdkClient *veCdn.CDN } -var _ uploader.Uploader = (*VolcengineCDNUploader)(nil) +var _ uploader.Uploader = (*VolcEngineCDNUploader)(nil) -func New(config *VolcengineCDNUploaderConfig) (*VolcengineCDNUploader, error) { +func New(config *VolcEngineCDNUploaderConfig) (*VolcEngineCDNUploader, error) { if config == nil { return nil, errors.New("config is nil") } - instance := cdn.NewInstance() - client := instance.Client - client.SetAccessKey(config.AccessKeyId) - client.SetSecretKey(config.AccessKeySecret) + client := veCdn.NewInstance() + client.Client.SetAccessKey(config.AccessKeyId) + client.Client.SetSecretKey(config.AccessKeySecret) - return &VolcengineCDNUploader{ + return &VolcEngineCDNUploader{ config: config, - sdkClient: instance, + sdkClient: client, }, nil } -func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { +func (u *VolcEngineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { // 解析证书内容 certX509, err := x509.ParseCertificateFromPEM(certPem) if err != nil { return nil, err } + // 查询证书列表,避免重复上传 // REF: https://www.volcengine.com/docs/6454/125709 - pageNum := int64(1) - pageSize := int64(100) - certSource := "volc_cert_center" - listCertInfoReq := &cdn.ListCertInfoRequest{ - PageNum: &pageNum, - PageSize: &pageSize, - Source: certSource, + listCertInfoPageNum := int64(1) + listCertInfoPageSize := int64(100) + listCertInfoTotal := 0 + listCertInfoReq := &veCdn.ListCertInfoRequest{ + PageNum: cast.Int64Ptr(listCertInfoPageNum), + PageSize: cast.Int64Ptr(listCertInfoPageSize), + Source: "volc_cert_center", } - searchTotal := 0 for { listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) if err != nil { @@ -68,8 +70,10 @@ func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, priv if listCertInfoResp.Result.CertInfo != nil { for _, certDetail := range listCertInfoResp.Result.CertInfo { - hash := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(hash[:]), certDetail.CertFingerprint.Sha256) + fingerprintSha1 := sha1.Sum(certX509.Raw) + fingerprintSha256 := sha256.Sum256(certX509.Raw) + isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) && + strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256) // 如果已存在相同证书,直接返回已有的证书信息 if isSameCert { return &uploader.UploadResult{ @@ -80,24 +84,26 @@ func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, priv } } - searchTotal += len(listCertInfoResp.Result.CertInfo) - if int(listCertInfoResp.Result.Total) > searchTotal { - pageNum++ - } else { + listCertInfoLen := len(listCertInfoResp.Result.CertInfo) + if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen { break + } else { + listCertInfoPageNum++ + listCertInfoTotal += listCertInfoLen } - } + // 生成新证书名(需符合火山引擎命名规则) var certId, certName string certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + // 上传新证书 // REF: https://www.volcengine.com/docs/6454/1245763 - addCertificateReq := &cdn.AddCertificateRequest{ + addCertificateReq := &veCdn.AddCertificateRequest{ Certificate: certPem, PrivateKey: privkeyPem, - Source: &certSource, - Desc: &certName, + Source: cast.StringPtr("volc_cert_center"), + Desc: cast.StringPtr(certName), } addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq) if err != nil { diff --git a/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go index abc24d8c..a9ee7008 100644 --- a/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go @@ -8,77 +8,79 @@ import ( "time" xerrors "github.com/pkg/errors" + veLive "github.com/volcengine/volc-sdk-golang/service/live/v20230101" + "github.com/usual2970/certimate/internal/pkg/core/uploader" "github.com/usual2970/certimate/internal/pkg/utils/cast" "github.com/usual2970/certimate/internal/pkg/utils/x509" - live "github.com/volcengine/volc-sdk-golang/service/live/v20230101" ) -type VolcengineLiveUploaderConfig struct { +type VolcEngineLiveUploaderConfig struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` } -type VolcengineLiveUploader struct { - config *VolcengineLiveUploaderConfig - sdkClient *live.Live +type VolcEngineLiveUploader struct { + config *VolcEngineLiveUploaderConfig + sdkClient *veLive.Live } -var _ uploader.Uploader = (*VolcengineLiveUploader)(nil) +var _ uploader.Uploader = (*VolcEngineLiveUploader)(nil) -func New(config *VolcengineLiveUploaderConfig) (*VolcengineLiveUploader, error) { +func New(config *VolcEngineLiveUploaderConfig) (*VolcEngineLiveUploader, error) { if config == nil { return nil, errors.New("config is nil") } - client := live.NewInstance() + client := veLive.NewInstance() client.SetAccessKey(config.AccessKeyId) client.SetSecretKey(config.AccessKeySecret) - return &VolcengineLiveUploader{ + return &VolcEngineLiveUploader{ config: config, sdkClient: client, }, nil } -func (u *VolcengineLiveUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { +func (u *VolcEngineLiveUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { // 解析证书内容 certX509, err := x509.ParseCertificateFromPEM(certPem) if err != nil { return nil, err } + // 查询证书列表,避免重复上传 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E8%AF%A2%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8 - listCertReq := &live.ListCertV2Body{} + listCertReq := &veLive.ListCertV2Body{} listCertResp, err := u.sdkClient.ListCertV2(ctx, listCertReq) if err != nil { return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListCertV2'") } - if listCertResp.Result.CertList != nil { for _, certDetail := range listCertResp.Result.CertList { - - describeCertDetailSecretReq := &live.DescribeCertDetailSecretV2Body{ - ChainID: cast.StringPtr(certDetail.ChainID), - } // 查询证书详细信息 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85 - describeCertDetailSecretResp, detailErr := u.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq) - if detailErr != nil { + describeCertDetailSecretReq := &veLive.DescribeCertDetailSecretV2Body{ + ChainID: cast.StringPtr(certDetail.ChainID), + } + describeCertDetailSecretResp, err := u.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq) + if err != nil { continue } + var isSameCert bool certificate := strings.Join(describeCertDetailSecretResp.Result.SSL.Chain, "\n\n") if certificate == certPem { isSameCert = true } else { - cert, err := x509.ParseCertificateFromPEM(certificate) + oldCertX509, err := x509.ParseCertificateFromPEM(certificate) if err != nil { continue } - isSameCert = x509.EqualCertificate(cert, certX509) + isSameCert = x509.EqualCertificate(certX509, oldCertX509) } + // 如果已存在相同证书,直接返回已有的证书信息 if isSameCert { return &uploader.UploadResult{ @@ -92,13 +94,14 @@ func (u *VolcengineLiveUploader) Upload(ctx context.Context, certPem string, pri // 生成新证书名(需符合火山引擎命名规则) var certId, certName string certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + // 上传新证书 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6 - createCertReq := &live.CreateCertBody{ + createCertReq := &veLive.CreateCertBody{ CertName: &certName, UseWay: "https", ProjectName: cast.StringPtr("default"), - Rsa: live.CreateCertBodyRsa{ + Rsa: veLive.CreateCertBodyRsa{ Prikey: privkeyPem, Pubkey: certPem, }, From a59184ae5f5a4d5915b702cd2f3f13e328395d0c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 20 Nov 2024 07:49:50 +0800 Subject: [PATCH 10/17] fix: update `GetValueOrDefault` util functions to return default value for zero values --- internal/pkg/utils/maps/maps.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/internal/pkg/utils/maps/maps.go b/internal/pkg/utils/maps/maps.go index 6f3c6fe6..0c80e3a3 100644 --- a/internal/pkg/utils/maps/maps.go +++ b/internal/pkg/utils/maps/maps.go @@ -22,7 +22,7 @@ func GetValueAsString(dict map[string]any, key string) string { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在、值的类型不是字符串或者值为零值,则返回默认值。 func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string { if dict == nil { return defaultValue @@ -30,7 +30,9 @@ func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue str if value, ok := dict[key]; ok { if result, ok := value.(string); ok { - return result + if result != "" { + return result + } } } @@ -57,7 +59,7 @@ func GetValueAsInt32(dict map[string]any, key string) int32 { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在、值的类型不是 32 位整数或者值为零值,则返回默认值。 func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 { if dict == nil { return defaultValue @@ -65,13 +67,17 @@ func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int3 if value, ok := dict[key]; ok { if result, ok := value.(int32); ok { - return result + if result != 0 { + return result + } } // 兼容字符串类型的值 if str, ok := value.(string); ok { if result, err := strconv.ParseInt(str, 10, 32); err == nil { - return int32(result) + if result != 0 { + return int32(result) + } } } } @@ -99,7 +105,7 @@ func GetValueAsInt64(dict map[string]any, key string) int64 { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在、值的类型不是 64 位整数或者值为零值,则返回默认值。 func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 { if dict == nil { return defaultValue @@ -107,13 +113,17 @@ func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int6 if value, ok := dict[key]; ok { if result, ok := value.(int64); ok { - return result + if result != 0 { + return result + } } // 兼容字符串类型的值 if str, ok := value.(string); ok { if result, err := strconv.ParseInt(str, 10, 64); err == nil { - return result + if result != 0 { + return result + } } } } From 643a6668534a255df269ab716faeb0f518feba4a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 20 Nov 2024 21:02:29 +0800 Subject: [PATCH 11/17] feat: implement more `Deployer` --- internal/domain/domains.go | 4 +- .../providers/aliyun-alb/aliyun_alb.go | 289 +++++++++++++++++ .../providers/aliyun-alb/aliyun_alb_test.go | 118 +++++++ .../deployer/providers/aliyun-alb/defines.go | 10 + .../providers/aliyun-cdn/aliyun_cdn.go | 93 ++++++ .../providers/aliyun-cdn/aliyun_cdn_test.go | 75 +++++ .../providers/aliyun-clb/aliyun_clb.go | 291 ++++++++++++++++++ .../providers/aliyun-clb/aliyun_clb_test.go | 120 ++++++++ .../deployer/providers/aliyun-clb/defines.go | 10 + .../providers/aliyun-dcdn/aliyun_dcdn.go | 97 ++++++ .../providers/aliyun-dcdn/aliyun_dcdn_test.go | 75 +++++ .../providers/aliyun-nlb/aliyun_nlb.go | 251 +++++++++++++++ .../providers/aliyun-nlb/aliyun_nlb_test.go | 119 +++++++ .../deployer/providers/aliyun-nlb/defines.go | 10 + .../providers/aliyun-oss/aliyun_oss.go | 112 +++++++ .../providers/aliyun-oss/aliyun_oss_test.go | 85 +++++ .../providers/k8s-secret/k8s_secret.go | 2 + .../core/deployer/providers/local/local.go | 7 +- .../deployer/providers/local/local_test.go | 12 +- .../pkg/core/deployer/providers/ssh/ssh.go | 9 +- .../core/deployer/providers/ssh/ssh_test.go | 4 +- .../providers/aliyun-cas/aliyun_cas.go | 1 + .../providers/aliyun-slb/aliyun_slb.go | 1 + 23 files changed, 1778 insertions(+), 17 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-alb/defines.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-clb/defines.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-nlb/defines.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go diff --git a/internal/domain/domains.go b/internal/domain/domains.go index 2bc07074..6a228fff 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -41,7 +41,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string { // - defaultValue: 默认值。 // // 出参: -// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。 +// - 配置项的值。如果配置项不存在、类型不是字符串或者值为零值,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string { return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue) } @@ -64,7 +64,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 { // - defaultValue: 默认值。 // // 出参: -// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。 +// - 配置项的值。如果配置项不存在、类型不是 32 位整数或者值为零值,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 { return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue) } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go new file mode 100644 index 00000000..cda2011e --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -0,0 +1,289 @@ +package aliyunalb + +import ( + "context" + "errors" + "fmt" + "strings" + + aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client" + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunALBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type AliyunALBDeployer struct { + config *AliyunALBDeployerConfig + logger deployer.Logger + sdkClient *aliyunAlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunALBDeployer)(nil) + +func New(config *AliyunALBDeployerConfig) (*AliyunALBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunALBDeployerConfig, logger deployer.Logger) (*AliyunALBDeployer, 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.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + aliyunCasRegion := config.Region + if aliyunCasRegion != "" { + // 阿里云 CAS 服务接入点是独立于 ALB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if !strings.HasPrefix(aliyunCasRegion, "cn-") { + aliyunCasRegion = "ap-southeast-1" + } else { + aliyunCasRegion = "cn-hangzhou" + } + } + uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: aliyunCasRegion, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunALBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunALBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute + getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{ + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp) + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + listListenersReq := &aliyunAlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("HTTPS"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", listenerIds) + + // 查询 QUIC 监听列表 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners + listListenersPage = 1 + listListenersToken = nil + for { + listListenersReq := &aliyunAlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("QUIC"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds) + + // 批量更新监听证书 + var errs []error + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunALBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 查询监听的属性 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute + getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + } + getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'") + } + + d.logger.Appendt("已查询到 ALB 监听配置", getListenerAttributeResp) + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute + updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{ + CertificateId: tea.String(cloudCertId), + }}, + } + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'") + } + + d.logger.Appendt("已更新 ALB 监听配置", updateListenerAttributeResp) + + // TODO: #347 + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) { + if region == "" { + region = "cn-hangzhou" + } + + // 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-endpoint + var endpoint string + switch region { + case "cn-hangzhou-finance": + endpoint = "alb.cn-hangzhou.aliyuncs.com" + default: + endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunAlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go new file mode 100644 index 00000000..8c8962ba --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -0,0 +1,118 @@ +package aliyunalb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunAlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNALB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_alb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNALB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_LOADBALANCERID="your-alb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/defines.go b/internal/pkg/core/deployer/providers/aliyun-alb/defines.go new file mode 100644 index 00000000..927e990a --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/defines.go @@ -0,0 +1,10 @@ +package aliyunalb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go new file mode 100644 index 00000000..caf97753 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -0,0 +1,93 @@ +package aliyuncdn + +import ( + "context" + "errors" + "fmt" + "time" + + aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client" + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunCDNDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunCDNDeployer struct { + config *AliyunCDNDeployerConfig + logger deployer.Logger + sdkClient *aliyunCdn.Client +} + +var _ deployer.Deployer = (*AliyunCDNDeployer)(nil) + +func New(config *AliyunCDNDeployerConfig) (*AliyunCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunCDNDeployerConfig, logger deployer.Logger) (*AliyunCDNDeployer, 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.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 设置 CDN 域名域名证书 + // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate + setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{ + DomainName: tea.String(d.config.Domain), + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + CertType: tea.String("upload"), + SSLProtocol: tea.String("on"), + SSLPub: tea.String(certPem), + SSLPri: tea.String(privkeyPem), + } + setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'") + } + + d.logger.Appendt("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("cdn.aliyuncs.com"), + } + + client, err := aliyunCdn.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go new file mode 100644 index 00000000..57c15943 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -0,0 +1,75 @@ +package aliyuncdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunCdn.New(&dpAliyunCdn.AliyunCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go new file mode 100644 index 00000000..395b9bb5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -0,0 +1,291 @@ +package aliyunclb + +import ( + "context" + "errors" + "fmt" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" +) + +type AliyunCLBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听端口。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerPort int32 `json:"listenerPort,omitempty"` +} + +type AliyunCLBDeployer struct { + config *AliyunCLBDeployerConfig + logger deployer.Logger + sdkClient *aliyunSlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunCLBDeployer)(nil) + +func New(config *AliyunCLBDeployerConfig) (*AliyunCLBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunCLBDeployerConfig, logger deployer.Logger) (*AliyunCLBDeployer, 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.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := upAliyunSlb.New(&upAliyunSlb.AliyunSLBUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunCLBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SLB + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerPorts := make([]int32, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute + describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp) + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{ + RegionId: tea.String(d.config.Region), + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("https"), + } + describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerListeners'") + } + + if describeLoadBalancerListenersResp.Body.Listeners != nil { + for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { + listenerPorts = append(listenerPorts, *listener.ListenerPort) + } + } + + if describeLoadBalancerListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = describeLoadBalancerListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts) + + // 批量更新监听证书 + var errs []error + for _, listenerPort := range listenerPorts { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunCLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + if d.config.ListenerPort == 0 { + return errors.New("config `listenerPort` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerPort, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloudLoadbalancerId string, cloudListenerPort int32, cloudCertId string) error { + // 查询监听配置 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute + describeLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.DescribeLoadBalancerHTTPSListenerAttributeRequest{ + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + } + describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'") + } + + d.logger.Appendt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp) + + // 查询扩展域名 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions + describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + } + describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'") + } + + d.logger.Appendt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) + + // 遍历修改扩展域名 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute + // + // 这里仅修改跟被替换证书一致的扩展域名 + if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil { + for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension { + if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId { + continue + } + + setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{ + RegionId: tea.String(d.config.Region), + DomainExtensionId: tea.String(*domainExtension.DomainExtensionId), + ServerCertificateId: tea.String(cloudCertId), + } + _, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'") + } + } + } + + // 修改监听配置 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute + // + // 注意修改监听配置要放在修改扩展域名之后 + setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + ServerCertificateId: tea.String(cloudCertId), + } + setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'") + } + + d.logger.Appendt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) { + if region == "" { + region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州 + } + + // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint + var endpoint string + switch region { + case + "cn-hangzhou", + "cn-hangzhou-finance", + "cn-shanghai-finance-1", + "cn-shenzhen-finance-1": + endpoint = "slb.aliyuncs.com" + default: + endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunSlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go new file mode 100644 index 00000000..22a1168c --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -0,0 +1,120 @@ +package aliyunclb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerPort int +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCLB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") +} + +/* +Shell command to run this test: + + go test -v aliyun_clb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_LOADBALANCERID="your-clb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443 +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERPORT: %v", fListenerPort), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + LoadbalancerId: fLoadbalancerId, + ListenerPort: int32(fListenerPort), + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/defines.go b/internal/pkg/core/deployer/providers/aliyun-clb/defines.go new file mode 100644 index 00000000..4a02ab20 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/defines.go @@ -0,0 +1,10 @@ +package aliyunclb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go new file mode 100644 index 00000000..d71d24c0 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -0,0 +1,97 @@ +package aliyundcdn + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunDcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunDCDNDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunDCDNDeployer struct { + config *AliyunDCDNDeployerConfig + logger deployer.Logger + sdkClient *aliyunDcdn.Client +} + +var _ deployer.Deployer = (*AliyunDCDNDeployer)(nil) + +func New(config *AliyunDCDNDeployerConfig) (*AliyunDCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunDCDNDeployerConfig, logger deployer.Logger) (*AliyunDCDNDeployer, 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.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunDCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunDCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + + // 配置域名证书 + // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate + setDcdnDomainSSLCertificateReq := &aliyunDcdn.SetDcdnDomainSSLCertificateRequest{ + DomainName: tea.String(domain), + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + CertType: tea.String("upload"), + SSLProtocol: tea.String("on"), + SSLPub: tea.String(certPem), + SSLPri: tea.String(privkeyPem), + } + setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'") + } + + d.logger.Appendt("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("dcdn.aliyuncs.com"), + } + + client, err := aliyunDcdn.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go new file mode 100644 index 00000000..6418fa4d --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -0,0 +1,75 @@ +package aliyundcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunDcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_dcdn_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunDcdn.New(&dpAliyunDcdn.AliyunDCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go new file mode 100644 index 00000000..5a8e9b38 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -0,0 +1,251 @@ +package aliyunnlb + +import ( + "context" + "errors" + "fmt" + "strings" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunNlb "github.com/alibabacloud-go/nlb-20220430/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunNLBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type AliyunNLBDeployer struct { + config *AliyunNLBDeployerConfig + logger deployer.Logger + sdkClient *aliyunNlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunNLBDeployer)(nil) + +func New(config *AliyunNLBDeployerConfig) (*AliyunNLBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunNLBDeployerConfig, logger deployer.Logger) (*AliyunNLBDeployer, 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.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + aliyunCasRegion := config.Region + if aliyunCasRegion != "" { + // 阿里云 CAS 服务接入点是独立于 NLB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if !strings.HasPrefix(aliyunCasRegion, "cn-") { + aliyunCasRegion = "ap-southeast-1" + } else { + aliyunCasRegion = "cn-hangzhou" + } + } + uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: aliyunCasRegion, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunNLBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunNLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute + getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{ + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp) + + // 查询 TCPSSL 监听列表 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + listListenersReq := &aliyunNlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("TCPSSL"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds) + + // 批量更新监听证书 + var errs []error + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunNLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 查询监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute + getListenerAttributeReq := &aliyunNlb.GetListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + } + getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'") + } + + d.logger.Appendt("已查询到 NLB 监听配置", getListenerAttributeResp) + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute + updateListenerAttributeReq := &aliyunNlb.UpdateListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + CertificateIds: []*string{tea.String(cloudCertId)}, + } + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'") + } + + d.logger.Appendt("已更新 NLB 监听配置", updateListenerAttributeResp) + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) { + if region == "" { + region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州 + } + + // 接入点一览 https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-endpoint + var endpoint string + switch region { + default: + endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunNlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go new file mode 100644 index 00000000..a1b84257 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go @@ -0,0 +1,119 @@ +package aliyunnlb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNNLB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_nlb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_LOADBALANCERID="your-nlb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_LISTENERID="your-nlb-listener-id" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go b/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go new file mode 100644 index 00000000..14e3c2b2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go @@ -0,0 +1,10 @@ +package aliyunnlb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go new file mode 100644 index 00000000..656c9e10 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go @@ -0,0 +1,112 @@ +package aliyunoss + +import ( + "context" + "errors" + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunOSSDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 存储桶名。 + Bucket string `json:"bucket"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunOSSDeployer struct { + config *AliyunOSSDeployerConfig + logger deployer.Logger + sdkClient *oss.Client +} + +var _ deployer.Deployer = (*AliyunOSSDeployer)(nil) + +func New(config *AliyunOSSDeployerConfig) (*AliyunOSSDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunOSSDeployerConfig, logger deployer.Logger) (*AliyunOSSDeployer, 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.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunOSSDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunOSSDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.Bucket == "" { + return nil, errors.New("config `bucket` is required") + } + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // 为存储空间绑定自定义域名 + // REF: https://help.aliyun.com/zh/oss/developer-reference/putcname + err := d.sdkClient.PutBucketCnameWithCertificate(d.config.Bucket, oss.PutBucketCname{ + Cname: d.config.Domain, + CertificateConfiguration: &oss.CertificateConfiguration{ + Certificate: certPem, + PrivateKey: privkeyPem, + Force: true, + }, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'") + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) { + // 接入点一览 https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints + var endpoint string + switch region { + case "": + endpoint = "oss.aliyuncs.com" + case + "cn-hzjbp", + "cn-hzjbp-a", + "cn-hzjbp-b": + endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com" + case + "cn-shanghai-finance-1", + "cn-shenzhen-finance-1", + "cn-beijing-finance-1", + "cn-north-2-gov-1": + endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region) + default: + endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region) + } + + client, err := oss.New(endpoint, accessKeyId, accessKeySecret) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go new file mode 100644 index 00000000..50c707e5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go @@ -0,0 +1,85 @@ +package aliyunoss_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNOSS_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_oss_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-bucket" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunOss.New(&dpAliyunOss.AliyunOSSDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + 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/core/deployer/providers/k8s-secret/k8s_secret.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go index 1e75b615..7e5a353a 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go @@ -34,6 +34,8 @@ type K8sSecretDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*K8sSecretDeployer)(nil) + func New(config *K8sSecretDeployerConfig) (*K8sSecretDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go index 9770025d..f8fb1f23 100644 --- a/internal/pkg/core/deployer/providers/local/local.go +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -17,14 +17,13 @@ import ( type LocalDeployerConfig struct { // Shell 执行环境。 - // 空值时默认根据操作系统决定。 + // 零值时默认根据操作系统决定。 ShellEnv ShellEnvType `json:"shellEnv,omitempty"` // 前置命令。 PreCommand string `json:"preCommand,omitempty"` // 后置命令。 PostCommand string `json:"postCommand,omitempty"` // 输出证书格式。 - // 空值时默认为 [OUTPUT_FORMAT_PEM]。 OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` @@ -49,6 +48,8 @@ type LocalDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*LocalDeployer)(nil) + func New(config *LocalDeployerConfig) (*LocalDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } @@ -81,7 +82,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s // 写入证书和私钥文件 switch d.config.OutputFormat { - case "", OUTPUT_FORMAT_PEM: + case OUTPUT_FORMAT_PEM: if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil { return nil, xerrors.Wrap(err, "failed to save certificate file") } diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go index 74307734..c49df4a0 100644 --- a/internal/pkg/core/deployer/providers/local/local_test.go +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" + dpLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" ) var ( @@ -60,7 +60,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, }) @@ -108,8 +108,8 @@ func Test(t *testing.T) { fmt.Sprintf("PFXPASSWORD: %v", fPfxPassword), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_PFX, + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ + OutputFormat: dpLocal.OUTPUT_FORMAT_PFX, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, PfxPassword: fPfxPassword, @@ -151,8 +151,8 @@ func Test(t *testing.T) { fmt.Sprintf("JKSSTOREPASS: %v", fJksStorepass), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_JKS, + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ + OutputFormat: dpLocal.OUTPUT_FORMAT_JKS, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, JksAlias: fJksAlias, diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 982e1ff4..147662f8 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -18,10 +18,10 @@ import ( type SshDeployerConfig struct { // SSH 主机。 - // 空值时默认为 "localhost"。 + // 零值时默认为 "localhost"。 SshHost string `json:"sshHost,omitempty"` // SSH 端口。 - // 空值时默认为 22。 + // 零值时默认为 22。 SshPort int32 `json:"sshPort,omitempty"` // SSH 登录用户名。 SshUsername string `json:"sshUsername,omitempty"` @@ -36,7 +36,6 @@ type SshDeployerConfig struct { // 后置命令。 PostCommand string `json:"postCommand,omitempty"` // 输出证书格式。 - // 空值时默认为 [OUTPUT_FORMAT_PEM]。 OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` @@ -61,6 +60,8 @@ type SshDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*SshDeployer)(nil) + func New(config *SshDeployerConfig) (*SshDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } @@ -109,7 +110,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str // 上传证书和私钥文件 switch d.config.OutputFormat { - case "", OUTPUT_FORMAT_PEM: + case OUTPUT_FORMAT_PEM: if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index dbc0109c..bd31609a 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" + dpSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" ) var ( @@ -64,7 +64,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dSsh.New(&dSsh.SshDeployerConfig{ + deployer, err := dpSsh.New(&dpSsh.SshDeployerConfig{ SshHost: fSshHost, SshPort: int32(fSshPort), SshUsername: fSshUsername, diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index f13d2b33..b65cd1d2 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -144,6 +144,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 } + // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints var endpoint string switch region { case "cn-hangzhou": diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index df14aea9..f78c85ce 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -121,6 +121,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州 } + // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint var endpoint string switch region { case From bde51d8d387333b1b5d6b92500023e4210ff2fd9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 20 Nov 2024 22:58:01 +0800 Subject: [PATCH 12/17] feat: implement more `Deployer` --- internal/applicant/volcengine.go | 2 +- internal/deployer/volcengine_cdn.go | 4 +- internal/deployer/volcengine_live.go | 4 +- internal/domain/access.go | 7 +- internal/notify/factory.go | 28 +- .../providers/aliyun-alb/aliyun_alb.go | 4 +- .../providers/aliyun-alb/aliyun_alb_test.go | 10 +- .../providers/aliyun-cdn/aliyun_cdn_test.go | 6 +- .../providers/aliyun-clb/aliyun_clb.go | 4 +- .../providers/aliyun-clb/aliyun_clb_test.go | 10 +- .../providers/aliyun-dcdn/aliyun_dcdn_test.go | 6 +- .../providers/aliyun-nlb/aliyun_nlb.go | 4 +- .../providers/aliyun-nlb/aliyun_nlb_test.go | 10 +- .../providers/aliyun-oss/aliyun_oss_test.go | 8 +- .../baiducloud-cdn/baiducloud_cdn.go | 86 ++++ .../baiducloud-cdn/baiducloud_cdn_test.go | 75 ++++ .../providers/byteplus-cdn/byteplus_cdn.go | 136 ++++++ .../byteplus-cdn/byteplus_cdn_test.go | 75 ++++ .../providers/dogecloud-cdn/dogecloud_cdn.go | 85 ++++ .../dogecloud-cdn/dogecloud_cdn_test.go | 75 ++++ .../huaweicloud-cdn/huaweicloud_cdn.go | 152 +++++++ .../huaweicloud-cdn/huaweicloud_cdn_test.go | 80 ++++ .../providers/huaweicloud-elb/defines.go | 12 + .../huaweicloud-elb/huaweicloud_elb.go | 399 ++++++++++++++++++ .../huaweicloud-elb/huaweicloud_elb_test.go | 155 +++++++ .../providers/k8s-secret/k8s_secret_test.go | 4 +- .../deployer/providers/local/local_test.go | 12 +- .../deployer/providers/qiniu-cdn/qiniu_cdn.go | 106 +++++ .../providers/qiniu-cdn/qiniu_cdn_test.go | 75 ++++ .../core/deployer/providers/ssh/ssh_test.go | 4 +- .../volcengine-cdn/volcengine_cdn.go | 136 ++++++ .../volcengine-cdn/volcengine_cdn_test.go | 75 ++++ .../volcengine-live/volcengine_live.go | 146 +++++++ .../volcengine-live/volcengine_live_test.go | 75 ++++ .../providers/webhook/webhook_test.go | 4 +- .../notifier/providers/email/email_test.go | 4 +- .../providers/webhook/webhook_test.go | 4 +- 37 files changed, 2015 insertions(+), 67 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go create mode 100644 internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go create mode 100644 internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go create mode 100644 internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go create mode 100644 internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/huaweicloud-elb/defines.go create mode 100644 internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go create mode 100644 internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go create mode 100644 internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go create mode 100644 internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go create mode 100644 internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go create mode 100644 internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go diff --git a/internal/applicant/volcengine.go b/internal/applicant/volcengine.go index 93f88a99..7437bfda 100644 --- a/internal/applicant/volcengine.go +++ b/internal/applicant/volcengine.go @@ -23,7 +23,7 @@ func (a *volcengine) Apply() (*Certificate, error) { access := &domain.VolcengineAccess{} json.Unmarshal([]byte(a.option.Access), access) - os.Setenv("VOLC_ACCESSKEY", access.AccessKeyID) + os.Setenv("VOLC_ACCESSKEY", access.AccessKeyId) os.Setenv("VOLC_SECRETKEY", access.SecretAccessKey) os.Setenv("VOLC_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout)) dnsProvider, err := volcengineDns.NewDNSProvider() diff --git a/internal/deployer/volcengine_cdn.go b/internal/deployer/volcengine_cdn.go index a154d3fa..c1665993 100644 --- a/internal/deployer/volcengine_cdn.go +++ b/internal/deployer/volcengine_cdn.go @@ -27,10 +27,10 @@ func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) { return nil, xerrors.Wrap(err, "failed to get access") } client := cdn.NewInstance() - client.Client.SetAccessKey(access.AccessKeyID) + client.Client.SetAccessKey(access.AccessKeyId) client.Client.SetSecretKey(access.SecretAccessKey) uploader, err := volcenginecdn.New(&volcenginecdn.VolcEngineCDNUploaderConfig{ - AccessKeyId: access.AccessKeyID, + AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, }) if err != nil { diff --git a/internal/deployer/volcengine_live.go b/internal/deployer/volcengine_live.go index 2d037742..6d038ac6 100644 --- a/internal/deployer/volcengine_live.go +++ b/internal/deployer/volcengine_live.go @@ -30,11 +30,11 @@ func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) { } client := live.NewInstance() client.SetCredential(base.Credentials{ - AccessKeyID: access.AccessKeyID, + AccessKeyID: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, }) uploader, err := volcenginelive.New(&volcenginelive.VolcEngineLiveUploaderConfig{ - AccessKeyId: access.AccessKeyID, + AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, }) if err != nil { diff --git a/internal/domain/access.go b/internal/domain/access.go index 8229ca5f..b1302ee0 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -62,7 +62,12 @@ type PdnsAccess struct { } type VolcengineAccess struct { - AccessKeyID string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` + + // Deprecated: Use [AccessKey] and [SecretKey] instead in the future + AccessKeyId string `json:"accessKeyId"` + // Deprecated: Use [AccessKey] and [SecretKey] instead in the future SecretAccessKey string `json:"secretAccessKey"` } diff --git a/internal/notify/factory.go b/internal/notify/factory.go index d4ce1e8f..9ed0e3f4 100644 --- a/internal/notify/factory.go +++ b/internal/notify/factory.go @@ -5,20 +5,20 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" - npBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" - npDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" - npEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" - npLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" - npServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" - npTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" - npWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + providerBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" + providerDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + providerEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + providerLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + providerServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" + providerTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + providerWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" "github.com/usual2970/certimate/internal/pkg/utils/maps" ) func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) { switch channel { case domain.NotifyChannelEmail: - return npEmail.New(&npEmail.EmailNotifierConfig{ + return providerEmail.New(&providerEmail.EmailNotifierConfig{ SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"), SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"), SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true), @@ -29,34 +29,34 @@ func createNotifier(channel string, channelConfig map[string]any) (notifier.Noti }) case domain.NotifyChannelWebhook: - return npWebhook.New(&npWebhook.WebhookNotifierConfig{ + return providerWebhook.New(&providerWebhook.WebhookNotifierConfig{ Url: maps.GetValueAsString(channelConfig, "url"), }) case domain.NotifyChannelDingtalk: - return npDingTalk.New(&npDingTalk.DingTalkNotifierConfig{ + return providerDingTalk.New(&providerDingTalk.DingTalkNotifierConfig{ AccessToken: maps.GetValueAsString(channelConfig, "accessToken"), Secret: maps.GetValueAsString(channelConfig, "secret"), }) case domain.NotifyChannelLark: - return npLark.New(&npLark.LarkNotifierConfig{ + return providerLark.New(&providerLark.LarkNotifierConfig{ WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"), }) case domain.NotifyChannelTelegram: - return npTelegram.New(&npTelegram.TelegramNotifierConfig{ + return providerTelegram.New(&providerTelegram.TelegramNotifierConfig{ ApiToken: maps.GetValueAsString(channelConfig, "apiToken"), ChatId: maps.GetValueAsInt64(channelConfig, "chatId"), }) case domain.NotifyChannelServerChan: - return npServerChan.New(&npServerChan.ServerChanNotifierConfig{ + return providerServerChan.New(&providerServerChan.ServerChanNotifierConfig{ Url: maps.GetValueAsString(channelConfig, "url"), }) case domain.NotifyChannelBark: - return npBark.New(&npBark.BarkNotifierConfig{ + return providerBark.New(&providerBark.BarkNotifierConfig{ DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"), ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"), }) diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index cda2011e..2cbf674e 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" - upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" ) type AliyunALBDeployerConfig struct { @@ -71,7 +71,7 @@ func NewWithLogger(config *AliyunALBDeployerConfig, logger deployer.Logger) (*Al aliyunCasRegion = "cn-hangzhou" } } - uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: aliyunCasRegion, diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go index 8c8962ba..686e1e6c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunAlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" ) var ( @@ -59,11 +59,11 @@ func Test(t *testing.T) { fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), }, "\n")) - deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunALBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LOADBALANCER, + ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, LoadbalancerId: fLoadbalancerId, }) if err != nil { @@ -93,11 +93,11 @@ func Test(t *testing.T) { fmt.Sprintf("LISTENERID: %v", fListenerId), }, "\n")) - deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunALBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LISTENER, + ResourceType: provider.DEPLOY_RESOURCE_LISTENER, ListenerId: fListenerId, }) if err != nil { diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go index 57c15943..e5562603 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" ) var ( @@ -37,7 +37,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYSECRET="your-access-key-secret" \ - --CERTIMATE_DEPLOYER_ALIYUNCDN_DOMAIN="example.com" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_DOMAIN="example.com" */ func Test(t *testing.T) { flag.Parse() @@ -52,7 +52,7 @@ func Test(t *testing.T) { fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) - deployer, err := dpAliyunCdn.New(&dpAliyunCdn.AliyunCDNDeployerConfig{ + deployer, err := provider.New(&provider.AliyunCDNDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Domain: fDomain, diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 395b9bb5..8bf2b0c8 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -12,7 +12,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" - upAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" + providerSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" ) type AliyunCLBDeployerConfig struct { @@ -59,7 +59,7 @@ func NewWithLogger(config *AliyunCLBDeployerConfig, logger deployer.Logger) (*Al return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := upAliyunSlb.New(&upAliyunSlb.AliyunSLBUploaderConfig{ + uploader, err := providerSlb.New(&providerSlb.AliyunSLBUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: config.Region, diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go index 22a1168c..6a8cc45c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" ) var ( @@ -59,11 +59,11 @@ func Test(t *testing.T) { fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), }, "\n")) - deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunCLBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, LoadbalancerId: fLoadbalancerId, }) if err != nil { @@ -94,11 +94,11 @@ func Test(t *testing.T) { fmt.Sprintf("LISTENERPORT: %v", fListenerPort), }, "\n")) - deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunCLBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + ResourceType: provider.DEPLOY_RESOURCE_LISTENER, LoadbalancerId: fLoadbalancerId, ListenerPort: int32(fListenerPort), }) diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go index 6418fa4d..c5a6abec 100644 --- a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunDcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" ) var ( @@ -37,7 +37,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYSECRET="your-access-key-secret" \ - --CERTIMATE_DEPLOYER_ALIYUNDCDN_DOMAIN="example.com" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_DOMAIN="example.com" */ func Test(t *testing.T) { flag.Parse() @@ -52,7 +52,7 @@ func Test(t *testing.T) { fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) - deployer, err := dpAliyunDcdn.New(&dpAliyunDcdn.AliyunDCDNDeployerConfig{ + deployer, err := provider.New(&provider.AliyunDCDNDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Domain: fDomain, diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 5a8e9b38..f20aa70d 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/uploader" - upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" ) type AliyunNLBDeployerConfig struct { @@ -71,7 +71,7 @@ func NewWithLogger(config *AliyunNLBDeployerConfig, logger deployer.Logger) (*Al aliyunCasRegion = "cn-hangzhou" } } - uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: aliyunCasRegion, diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go index a1b84257..b915c1a7 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" ) var ( @@ -59,11 +59,11 @@ func Test(t *testing.T) { fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), }, "\n")) - deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunNLBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, LoadbalancerId: fLoadbalancerId, }) if err != nil { @@ -94,11 +94,11 @@ func Test(t *testing.T) { fmt.Sprintf("LISTENERID: %v", fListenerId), }, "\n")) - deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + deployer, err := provider.New(&provider.AliyunNLBDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, - ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + ResourceType: provider.DEPLOY_RESOURCE_LISTENER, ListenerId: fListenerId, }) if err != nil { diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go index 50c707e5..66834e24 100644 --- a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" ) var ( @@ -42,8 +42,8 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ - --CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-bucket" \ - --CERTIMATE_DEPLOYER_ALIYUNOSS_DOMAIN="example.com" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-oss-bucket" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_DOMAIN="example.com" */ func Test(t *testing.T) { flag.Parse() @@ -60,7 +60,7 @@ func Test(t *testing.T) { fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) - deployer, err := dpAliyunOss.New(&dpAliyunOss.AliyunOSSDeployerConfig{ + deployer, err := provider.New(&provider.AliyunOSSDeployerConfig{ AccessKeyId: fAccessKeyId, AccessKeySecret: fAccessKeySecret, Region: fRegion, diff --git a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go new file mode 100644 index 00000000..e976003e --- /dev/null +++ b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -0,0 +1,86 @@ +package baiducloudcdn + +import ( + "context" + "errors" + "fmt" + "time" + + bceCdn "github.com/baidubce/bce-sdk-go/services/cdn" + bceCdnApi "github.com/baidubce/bce-sdk-go/services/cdn/api" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type BaiduCloudCDNDeployerConfig struct { + // 百度智能云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 百度智能云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type BaiduCloudCDNDeployer struct { + config *BaiduCloudCDNDeployerConfig + logger deployer.Logger + sdkClient *bceCdn.Client +} + +var _ deployer.Deployer = (*BaiduCloudCDNDeployer)(nil) + +func New(config *BaiduCloudCDNDeployerConfig) (*BaiduCloudCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *BaiduCloudCDNDeployerConfig, logger deployer.Logger) (*BaiduCloudCDNDeployer, 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.AccessKeyId, config.SecretAccessKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &BaiduCloudCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 修改域名证书 + // REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8 + putCertResp, err := d.sdkClient.PutCert( + d.config.Domain, + &bceCdnApi.UserCertificate{ + CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + ServerData: certPem, + PrivateData: privkeyPem, + }, + "ON", + ) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'") + } + + d.logger.Appendt("已修改域名证书", putCertResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, secretAccessKey string) (*bceCdn.Client, error) { + client, err := bceCdn.NewClient(accessKeyId, secretAccessKey, "") + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go new file mode 100644 index 00000000..31e51937 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go @@ -0,0 +1,75 @@ +package baiducloudcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v baiducloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_DOMAIN="example.com" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.BaiduCloudCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go new file mode 100644 index 00000000..4d17b0b7 --- /dev/null +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -0,0 +1,136 @@ +package bytepluscdn + +import ( + "context" + "errors" + "fmt" + "strings" + + bpCdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn" +) + +type BytePlusCDNDeployerConfig struct { + // BytePlus AccessKey。 + AccessKey string `json:"accessKey"` + // BytePlus SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type BytePlusCDNDeployer struct { + config *BytePlusCDNDeployerConfig + logger deployer.Logger + sdkClient *bpCdn.CDN + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*BytePlusCDNDeployer)(nil) + +func New(config *BytePlusCDNDeployerConfig) (*BytePlusCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *BytePlusCDNDeployerConfig, logger deployer.Logger) (*BytePlusCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client := bpCdn.NewInstance() + client.Client.SetAccessKey(config.AccessKey) + client.Client.SetSecretKey(config.SecretKey) + + uploader, err := providerCdn.New(&providerCdn.ByteplusCDNUploaderConfig{ + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &BytePlusCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CDN + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + domains := make([]string, 0) + if strings.HasPrefix(d.config.Domain, "*.") { + // 获取指定证书可关联的域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17 + describeCertConfigReq := &bpCdn.DescribeCertConfigRequest{ + CertId: upres.CertId, + } + describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'") + } + + if describeCertConfigResp.Result.CertNotConfig != nil { + for i := range describeCertConfigResp.Result.CertNotConfig { + domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + } + } + + if describeCertConfigResp.Result.OtherCertConfig != nil { + for i := range describeCertConfigResp.Result.OtherCertConfig { + domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + } + } + + if len(domains) == 0 { + if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { + // 所有可关联的域名都配置了该证书,跳过部署 + } else { + return nil, xerrors.New("domain not found") + } + } + } else { + domains = append(domains, d.config.Domain) + } + + if len(domains) > 0 { + var errs []error + + for _, domain := range domains { + // 关联证书与加速域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert + batchDeployCertReq := &bpCdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + if err != nil { + errs = append(errs, err) + } else { + d.logger.Appendt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go new file mode 100644 index 00000000..4a051dd6 --- /dev/null +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go @@ -0,0 +1,75 @@ +package bytepluscdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BYTEPLUSCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v byteplus_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_BYTEPLUSCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BYTEPLUSCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BYTEPLUSCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_BYTEPLUSCDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_BYTEPLUSCDN_DOMAIN="example.com" +*/ +func Test(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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.BytePlusCDNDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go new file mode 100644 index 00000000..794eb29b --- /dev/null +++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go @@ -0,0 +1,85 @@ +package dogecloudcdn + +import ( + "context" + "errors" + "strconv" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud" + dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk" +) + +type DogeCloudCDNDeployerConfig struct { + // 多吉云 AccessKey。 + AccessKey string `json:"accessKey"` + // 多吉云 SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type DogeCloudCDNDeployer struct { + config *DogeCloudCDNDeployerConfig + logger deployer.Logger + sdkClient *dogesdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DogeCloudCDNDeployer)(nil) + +func New(config *DogeCloudCDNDeployerConfig) (*DogeCloudCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *DogeCloudCDNDeployerConfig, logger deployer.Logger) (*DogeCloudCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client := dogesdk.NewClient(config.AccessKey, config.SecretKey) + + uploader, err := providerDoge.New(&providerDoge.DogeCloudUploaderConfig{ + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DogeCloudCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CDN + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 绑定证书 + // REF: https://docs.dogecloud.com/cdn/api-cert-bind + bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64) + bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.config.Domain) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'") + } + + d.logger.Appendt("已绑定证书", bindCdnCertResp) + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go new file mode 100644 index 00000000..da08c09e --- /dev/null +++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go @@ -0,0 +1,75 @@ +package dogecloudcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_DOGECLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v dogecloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_DOGECLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_DOGECLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_DOGECLOUDCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_DOGECLOUDCDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_DOGECLOUDCDN_DOMAIN="example.com" +*/ +func Test(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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.DogeCloudCDNDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go new file mode 100644 index 00000000..40dd3691 --- /dev/null +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -0,0 +1,152 @@ +package huaweicloudcdn + +import ( + "context" + "errors" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" + hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2" + hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model" + hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm" + "github.com/usual2970/certimate/internal/pkg/utils/cast" + huaweicloudsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk" +) + +type HuaweiCloudCDNDeployerConfig struct { + // 华为云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 华为云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 华为云地域。 + Region string `json:"region"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type HuaweiCloudCDNDeployer struct { + config *HuaweiCloudCDNDeployerConfig + logger deployer.Logger + sdkClient *huaweicloudsdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*HuaweiCloudCDNDeployer)(nil) + +func New(config *HuaweiCloudCDNDeployerConfig) (*HuaweiCloudCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *HuaweiCloudCDNDeployerConfig, logger deployer.Logger) (*HuaweiCloudCDNDeployer, 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.AccessKeyId, + config.SecretAccessKey, + config.Region, + ) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := providerScm.New(&providerScm.HuaweiCloudSCMUploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &HuaweiCloudCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SCM + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 查询加速域名配置 + // REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html + showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{ + DomainName: d.config.Domain, + } + showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'") + } + + d.logger.Appendt("已查询到加速域名配置", showDomainFullConfigResp) + + // 更新加速域名配置 + // REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html + // REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html + updateDomainMultiCertificatesReqBodyContent := &huaweicloudsdk.UpdateDomainMultiCertificatesExRequestBodyContent{} + updateDomainMultiCertificatesReqBodyContent.DomainName = d.config.Domain + updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1 + updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2) + updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(upres.CertId) + updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(upres.CertName) + updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs) + updateDomainMultiCertificatesReq := &huaweicloudsdk.UpdateDomainMultiCertificatesExRequest{ + Body: &huaweicloudsdk.UpdateDomainMultiCertificatesExRequestBody{ + Https: updateDomainMultiCertificatesReqBodyContent, + }, + } + updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'") + } + + d.logger.Appendt("已更新加速域名配置", updateDomainMultiCertificatesResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, secretAccessKey, region string) (*huaweicloudsdk.Client, error) { + if region == "" { + region = "cn-north-1" // CDN 服务默认区域:华北一北京 + } + + auth, err := global.NewCredentialsBuilder(). + WithAk(accessKeyId). + WithSk(secretAccessKey). + SafeBuild() + if err != nil { + return nil, err + } + + hcRegion, err := hcCdnRegion.SafeValueOf(region) + if err != nil { + return nil, err + } + + hcClient, err := hcCdn.CdnClientBuilder(). + WithRegion(hcRegion). + WithCredential(auth). + SafeBuild() + if err != nil { + return nil, err + } + + client := huaweicloudsdk.NewClient(hcClient) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go new file mode 100644 index 00000000..fc45129a --- /dev/null +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go @@ -0,0 +1,80 @@ +package huaweicloudcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegion string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v huaweicloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_REGION="cn-north-1" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_DOMAIN="example.com" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.HuaweiCloudCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/defines.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/defines.go new file mode 100644 index 00000000..093ab829 --- /dev/null +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/defines.go @@ -0,0 +1,12 @@ +package huaweicloudelb + +type DeployResourceType string + +const ( + // 资源类型:替换指定证书。 + DEPLOY_RESOURCE_CERTIFICATE = DeployResourceType("certificate") + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go new file mode 100644 index 00000000..7b7df3b1 --- /dev/null +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -0,0 +1,399 @@ +package huaweicloudelb + +import ( + "context" + "errors" + "fmt" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" + hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3" + hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model" + hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region" + hcIam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3" + hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model" + hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region" + xerrors "github.com/pkg/errors" + "golang.org/x/exp/slices" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb" + "github.com/usual2970/certimate/internal/pkg/utils/cast" +) + +type HuaweiCloudELBDeployerConfig struct { + // 华为云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 华为云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 华为云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_CERTIFICATE] 时必填。 + CertificateId string `json:"certificateId,omitempty"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type HuaweiCloudELBDeployer struct { + config *HuaweiCloudELBDeployerConfig + logger deployer.Logger + sdkClient *hcElb.ElbClient + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*HuaweiCloudELBDeployer)(nil) + +func New(config *HuaweiCloudELBDeployerConfig) (*HuaweiCloudELBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *HuaweiCloudELBDeployerConfig, logger deployer.Logger) (*HuaweiCloudELBDeployer, 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.AccessKeyId, config.SecretAccessKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := providerElb.New(&providerElb.HuaweiCloudELBUploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &HuaweiCloudELBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SCM + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 更新证书 + // REF: https://support.huaweicloud.com/api-elb/UpdateCertificate.html + updateCertificateReq := &hcElbModel.UpdateCertificateRequest{ + CertificateId: d.config.CertificateId, + Body: &hcElbModel.UpdateCertificateRequestBody{ + Certificate: &hcElbModel.UpdateCertificateOption{ + Certificate: cast.StringPtr(certPem), + PrivateKey: cast.StringPtr(privkeyPem), + }, + }, + } + updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'") + } + + d.logger.Appendt("已更新 ELB 证书", updateCertificateResp) + + return nil +} + +func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询负载均衡器详情 + // REF: https://support.huaweicloud.com/api-elb/ShowLoadBalancer.html + showLoadBalancerReq := &hcElbModel.ShowLoadBalancerRequest{ + LoadbalancerId: d.config.LoadbalancerId, + } + showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'") + } + + d.logger.Appendt("已查询到 ELB 负载均衡器", showLoadBalancerResp) + + // 查询监听器列表 + // REF: https://support.huaweicloud.com/api-elb/ListListeners.html + listListenersLimit := int32(2000) + var listListenersMarker *string = nil + for { + listListenersReq := &hcElbModel.ListListenersRequest{ + Limit: cast.Int32Ptr(listListenersLimit), + Marker: listListenersMarker, + Protocol: &[]string{"HTTPS", "TERMINATED_HTTPS"}, + LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id}, + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListListeners'") + } + + if listListenersResp.Listeners != nil { + for _, listener := range *listListenersResp.Listeners { + listenerIds = append(listenerIds, listener.Id) + } + } + + if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) { + break + } else { + listListenersMarker = listListenersResp.PageInfo.NextMarker + } + } + + d.logger.Appendt("已查询到 ELB 负载均衡器下的监听器", listenerIds) + + // 上传证书到 SCM + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 批量更新监听器证书 + var errs []error + for _, listenerId := range listenerIds { + if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 上传证书到 SCM + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 更新监听器证书 + if err := d.modifyListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { + return err + } + + return nil +} + +func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 查询监听器详情 + // REF: https://support.huaweicloud.com/api-elb/ShowListener.html + showListenerReq := &hcElbModel.ShowListenerRequest{ + ListenerId: cloudListenerId, + } + showListenerResp, err := d.sdkClient.ShowListener(showListenerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'") + } + + d.logger.Appendt("已查询到 ELB 监听器", showListenerResp) + + // 更新监听器 + // REF: https://support.huaweicloud.com/api-elb/UpdateListener.html + updateListenerReq := &hcElbModel.UpdateListenerRequest{ + ListenerId: cloudListenerId, + Body: &hcElbModel.UpdateListenerRequestBody{ + Listener: &hcElbModel.UpdateListenerOption{ + DefaultTlsContainerRef: cast.StringPtr(cloudCertId), + }, + }, + } + if showListenerResp.Listener.SniContainerRefs != nil { + if len(showListenerResp.Listener.SniContainerRefs) > 0 { + // 如果开启 SNI,需替换同 SAN 的证书 + sniCertIds := make([]string, 0) + sniCertIds = append(sniCertIds, cloudCertId) + + listOldCertificateReq := &hcElbModel.ListCertificatesRequest{ + Id: &showListenerResp.Listener.SniContainerRefs, + } + listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'") + } + + showNewCertificateReq := &hcElbModel.ShowCertificateRequest{ + CertificateId: cloudCertId, + } + showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowCertificate'") + } + + for _, certificate := range *listOldCertificateResp.Certificates { + oldCertificate := certificate + newCertificate := showNewCertificateResp.Certificate + + if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil { + if slices.Equal(*oldCertificate.SubjectAlternativeNames, *newCertificate.SubjectAlternativeNames) { + continue + } + } else { + if oldCertificate.Domain == newCertificate.Domain { + continue + } + } + + sniCertIds = append(sniCertIds, certificate.Id) + } + + updateListenerReq.Body.Listener.SniContainerRefs = &sniCertIds + } + + if showListenerResp.Listener.SniMatchAlgo != "" { + updateListenerReq.Body.Listener.SniMatchAlgo = cast.StringPtr(showListenerResp.Listener.SniMatchAlgo) + } + } + updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateListener'") + } + + d.logger.Appendt("已更新 ELB 监听器", updateListenerResp) + + return nil +} + +func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) { + if region == "" { + region = "cn-north-4" // ELB 服务默认区域:华北四北京 + } + + projectId, err := (&HuaweiCloudELBDeployer{}).getSdkProjectId( + accessKeyId, + secretAccessKey, + region, + ) + if err != nil { + return nil, err + } + + auth, err := basic.NewCredentialsBuilder(). + WithAk(accessKeyId). + WithSk(secretAccessKey). + WithProjectId(projectId). + SafeBuild() + if err != nil { + return nil, err + } + + hcRegion, err := hcElbRegion.SafeValueOf(region) + if err != nil { + return nil, err + } + + hcClient, err := hcElb.ElbClientBuilder(). + WithRegion(hcRegion). + WithCredential(auth). + SafeBuild() + if err != nil { + return nil, err + } + + client := hcElb.NewElbClient(hcClient) + return client, nil +} + +func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) { + if region == "" { + region = "cn-north-4" // IAM 服务默认区域:华北四北京 + } + + auth, err := global.NewCredentialsBuilder(). + WithAk(accessKeyId). + WithSk(secretAccessKey). + SafeBuild() + if err != nil { + return "", err + } + + hcRegion, err := hcIamRegion.SafeValueOf(region) + if err != nil { + return "", err + } + + hcClient, err := hcIam.IamClientBuilder(). + WithRegion(hcRegion). + WithCredential(auth). + SafeBuild() + if err != nil { + return "", err + } + + client := hcIam.NewIamClient(hcClient) + + request := &hcIamModel.KeystoneListProjectsRequest{ + Name: ®ion, + } + response, err := client.KeystoneListProjects(request) + if err != nil { + return "", err + } else if response.Projects == nil || len(*response.Projects) == 0 { + return "", errors.New("no project found") + } + + return (*response.Projects)[0].Id, nil +} diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go new file mode 100644 index 00000000..33e8afb6 --- /dev/null +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go @@ -0,0 +1,155 @@ +package huaweicloudelb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegion string + fCertificateId string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_HUAWEICLOUDELB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v huaweicloud_elb_test.go -args \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_SECRETACCESSKEY="your-secret-access-key" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_REGION="cn-north-1" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_CERTIFICATEID="your-elb-cert-id" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LOADBALANCERID="your-elb-loadbalancer-id" \ + --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LISTENERID="your-elb-listener-id" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToCertificate", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListenerId", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go index 7f1f48f5..8e6992f9 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" ) var ( @@ -56,7 +56,7 @@ func Test(t *testing.T) { fmt.Sprintf("SECRETDATAKEYFORKEY: %v", fSecretDataKeyForKey), }, "\n")) - deployer, err := dpK8sSecret.New(&dpK8sSecret.K8sSecretDeployerConfig{ + deployer, err := provider.New(&provider.K8sSecretDeployerConfig{ Namespace: fNamespace, SecretName: fSecretName, SecretDataKeyForCrt: fSecretDataKeyForCrt, diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go index c49df4a0..e14f7b59 100644 --- a/internal/pkg/core/deployer/providers/local/local_test.go +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" ) var ( @@ -60,7 +60,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ + deployer, err := provider.New(&provider.LocalDeployerConfig{ OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, }) @@ -108,8 +108,8 @@ func Test(t *testing.T) { fmt.Sprintf("PFXPASSWORD: %v", fPfxPassword), }, "\n")) - deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ - OutputFormat: dpLocal.OUTPUT_FORMAT_PFX, + deployer, err := provider.New(&provider.LocalDeployerConfig{ + OutputFormat: provider.OUTPUT_FORMAT_PFX, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, PfxPassword: fPfxPassword, @@ -151,8 +151,8 @@ func Test(t *testing.T) { fmt.Sprintf("JKSSTOREPASS: %v", fJksStorepass), }, "\n")) - deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ - OutputFormat: dpLocal.OUTPUT_FORMAT_JKS, + deployer, err := provider.New(&provider.LocalDeployerConfig{ + OutputFormat: provider.OUTPUT_FORMAT_JKS, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, JksAlias: fJksAlias, diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go new file mode 100644 index 00000000..40150046 --- /dev/null +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -0,0 +1,106 @@ +package qiniucdn + +import ( + "context" + "errors" + "strings" + + xerrors "github.com/pkg/errors" + "github.com/qiniu/go-sdk/v7/auth" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" + qiniusdk "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk" +) + +type QiniuCDNDeployerConfig struct { + // 七牛云 AccessKey。 + AccessKey string `json:"accessKey"` + // 七牛云 SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type QiniuCDNDeployer struct { + config *QiniuCDNDeployerConfig + logger deployer.Logger + sdkClient *qiniusdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*QiniuCDNDeployer)(nil) + +func New(config *QiniuCDNDeployerConfig) (*QiniuCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *QiniuCDNDeployerConfig, logger deployer.Logger) (*QiniuCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client := qiniusdk.NewClient(auth.New(config.AccessKey, config.SecretKey)) + + uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{ + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &QiniuCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CDN + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + + // 获取域名信息 + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'") + } + + d.logger.Appendt("已获取域名信息", getDomainInfoResp) + + // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") + } + + d.logger.Appendt("已修改域名证书", modifyDomainHttpsConfResp) + } else { + enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'") + } + + d.logger.Appendt("已将域名升级为 HTTPS", enableDomainHttpsResp) + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go new file mode 100644 index 00000000..290dc93d --- /dev/null +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go @@ -0,0 +1,75 @@ +package qiniucdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_QINIUCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v qiniu_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_QINIUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_QINIUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_QINIUCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_QINIUCDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_QINIUCDN_DOMAIN="example.com" \ +*/ +func Test(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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.QiniuCDNDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index bd31609a..041feb65 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" ) var ( @@ -64,7 +64,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dpSsh.New(&dpSsh.SshDeployerConfig{ + deployer, err := provider.New(&provider.SshDeployerConfig{ SshHost: fSshHost, SshPort: int32(fSshPort), SshUsername: fSshUsername, diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go new file mode 100644 index 00000000..0531b306 --- /dev/null +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -0,0 +1,136 @@ +package volcenginecdn + +import ( + "context" + "errors" + "fmt" + "strings" + + xerrors "github.com/pkg/errors" + veCdn "github.com/volcengine/volc-sdk-golang/service/cdn" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn" +) + +type VolcEngineCDNDeployerConfig struct { + // 火山引擎 AccessKey。 + AccessKey string `json:"accessKey"` + // 火山引擎 SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type VolcEngineCDNDeployer struct { + config *VolcEngineCDNDeployerConfig + logger deployer.Logger + sdkClient *veCdn.CDN + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*VolcEngineCDNDeployer)(nil) + +func New(config *VolcEngineCDNDeployerConfig) (*VolcEngineCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *VolcEngineCDNDeployerConfig, logger deployer.Logger) (*VolcEngineCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client := veCdn.NewInstance() + client.Client.SetAccessKey(config.AccessKey) + client.Client.SetSecretKey(config.SecretKey) + + uploader, err := providerCdn.New(&providerCdn.VolcEngineCDNUploaderConfig{ + AccessKeyId: config.AccessKey, + AccessKeySecret: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &VolcEngineCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CDN + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + domains := make([]string, 0) + if strings.HasPrefix(d.config.Domain, "*.") { + // 获取指定证书可关联的域名 + // REF: https://www.volcengine.com/docs/6454/125711 + describeCertConfigReq := &veCdn.DescribeCertConfigRequest{ + CertId: upres.CertId, + } + describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'") + } + + if describeCertConfigResp.Result.CertNotConfig != nil { + for i := range describeCertConfigResp.Result.CertNotConfig { + domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + } + } + + if describeCertConfigResp.Result.OtherCertConfig != nil { + for i := range describeCertConfigResp.Result.OtherCertConfig { + domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + } + } + + if len(domains) == 0 { + if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { + // 所有可关联的域名都配置了该证书,跳过部署 + } else { + return nil, xerrors.New("domain not found") + } + } + } else { + domains = append(domains, d.config.Domain) + } + + if len(domains) > 0 { + var errs []error + + for _, domain := range domains { + // 关联证书与加速域名 + // REF: https://www.volcengine.com/docs/6454/125712 + batchDeployCertReq := &veCdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + if err != nil { + errs = append(errs, err) + } else { + d.logger.Appendt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go new file mode 100644 index 00000000..fad06e94 --- /dev/null +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go @@ -0,0 +1,75 @@ +package volcenginecdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_VOLCENGINECDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v volcengine_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_VOLCENGINECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_VOLCENGINECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_VOLCENGINECDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_VOLCENGINECDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_VOLCENGINECDN_DOMAIN="example.com" +*/ +func Test(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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.VolcEngineCDNDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go new file mode 100644 index 00000000..7b7eb8d6 --- /dev/null +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -0,0 +1,146 @@ +package volcenginelive + +import ( + "context" + "errors" + "fmt" + "strings" + + xerrors "github.com/pkg/errors" + veLive "github.com/volcengine/volc-sdk-golang/service/live/v20230101" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerLive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live" + "github.com/usual2970/certimate/internal/pkg/utils/cast" +) + +type VolcEngineLiveDeployerConfig struct { + // 火山引擎 AccessKey。 + AccessKey string `json:"accessKeyId"` + // 火山引擎 SecretKey。 + SecretKey string `json:"secretAccessKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type VolcEngineLiveDeployer struct { + config *VolcEngineLiveDeployerConfig + logger deployer.Logger + sdkClient *veLive.Live + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*VolcEngineLiveDeployer)(nil) + +func New(config *VolcEngineLiveDeployerConfig) (*VolcEngineLiveDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *VolcEngineLiveDeployerConfig, logger deployer.Logger) (*VolcEngineLiveDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client := veLive.NewInstance() + client.SetAccessKey(config.AccessKey) + client.SetSecretKey(config.SecretKey) + + uploader, err := providerLive.New(&providerLive.VolcEngineLiveUploaderConfig{ + AccessKeyId: config.AccessKey, + AccessKeySecret: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &VolcEngineLiveDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *VolcEngineLiveDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 Live + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + domains := make([]string, 0) + if strings.HasPrefix(d.config.Domain, "*.") { + listDomainDetailPageNum := int32(1) + listDomainDetailPageSize := int32(1000) + listDomainDetailTotal := 0 + for { + // 查询域名列表 + // REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8 + listDomainDetailReq := &veLive.ListDomainDetailBody{ + PageNum: listDomainDetailPageNum, + PageSize: listDomainDetailPageSize, + } + listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListDomainDetail'") + } + + if listDomainDetailResp.Result.DomainList != nil { + for _, item := range listDomainDetailResp.Result.DomainList { + // 仅匹配泛域名的下一级子域名 + wildcardDomain := strings.TrimPrefix(d.config.Domain, "*") + if strings.HasSuffix(item.Domain, wildcardDomain) && !strings.Contains(strings.TrimSuffix(item.Domain, wildcardDomain), ".") { + domains = append(domains, item.Domain) + } + } + } + + listDomainDetailLen := len(listDomainDetailResp.Result.DomainList) + if listDomainDetailLen < int(listDomainDetailPageSize) || int(listDomainDetailResp.Result.Total) <= listDomainDetailTotal+listDomainDetailLen { + break + } else { + listDomainDetailPageNum++ + listDomainDetailTotal += listDomainDetailLen + } + } + + if len(domains) == 0 { + return nil, xerrors.Errorf("未查询到匹配的域名: %s", d.config.Domain) + } + } else { + domains = append(domains, d.config.Domain) + } + + if len(domains) > 0 { + var errs []error + + for _, domain := range domains { + // 绑定证书 + // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 + bindCertReq := &veLive.BindCertBody{ + ChainID: upres.CertId, + Domain: domain, + HTTPS: cast.BoolPtr(true), + } + bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) + if err != nil { + errs = append(errs, err) + } else { + d.logger.Appendt(fmt.Sprintf("已绑定证书到域名 %s", domain), bindCertResp) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go new file mode 100644 index 00000000..76735426 --- /dev/null +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go @@ -0,0 +1,75 @@ +package volcenginelive_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_VOLCENGINELIVE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v volcengine_live_test.go -args \ + --CERTIMATE_DEPLOYER_VOLCENGINELIVE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_VOLCENGINELIVE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_VOLCENGINELIVE_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_VOLCENGINELIVE_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_VOLCENGINELIVE_DOMAIN="example.com" +*/ +func Test(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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.VolcEngineLiveDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index d30f9599..e3a9b3e7 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dpWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" ) var ( @@ -44,7 +44,7 @@ func Test(t *testing.T) { fmt.Sprintf("URL: %v", fUrl), }, "\n")) - deployer, err := dpWebhook.New(&dpWebhook.WebhookDeployerConfig{ + deployer, err := provider.New(&provider.WebhookDeployerConfig{ Url: fUrl, }) if err != nil { diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index a401ac19..5f1fcd9a 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - npEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" ) const ( @@ -64,7 +64,7 @@ func Test(t *testing.T) { fmt.Sprintf("RECEIVERADDRESS: %v", fReceiverAddress), }, "\n")) - notifier, err := npEmail.New(&npEmail.EmailNotifierConfig{ + notifier, err := provider.New(&provider.EmailNotifierConfig{ SmtpHost: fSmtpHost, SmtpPort: int32(fSmtpPort), SmtpTLS: fSmtpTLS, diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index fc9ded52..294c60e8 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - npWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" ) const ( @@ -38,7 +38,7 @@ func Test(t *testing.T) { fmt.Sprintf("URL: %v", fUrl), }, "\n")) - notifier, err := npWebhook.New(&npWebhook.WebhookNotifierConfig{ + notifier, err := provider.New(&provider.WebhookNotifierConfig{ Url: fUrl, }) if err != nil { From 0b9312b5497ea6421509b87a0bc85b1cf4ef812c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 20 Nov 2024 23:51:26 +0800 Subject: [PATCH 13/17] feat: implement more `Deployer` --- .../huaweicloud-elb/huaweicloud_elb.go | 2 +- .../tencentcloud-cdn/tencentcloud_cdn.go | 198 ++++++++++++ .../tencentcloud-cdn/tencentcloud_cdn_test.go | 75 +++++ .../providers/tencentcloud-clb/defines.go | 14 + .../tencentcloud-clb/tencentcloud_clb.go | 305 ++++++++++++++++++ .../tencentcloud-clb/tencentcloud_clb_test.go | 199 ++++++++++++ .../tencentcloud-cos/tencentcloud_cos.go | 115 +++++++ .../tencentcloud-cos/tencentcloud_cos_test.go | 85 +++++ .../tencentcloud-ecdn/tencentcloud_ecdn.go | 158 +++++++++ .../tencentcloud_ecdn_test.go | 75 +++++ .../tencentcloud-teo/tencentcloud_teo.go | 124 +++++++ .../tencentcloud-teo/tencentcloud_teo_test.go | 80 +++++ .../volcengine-live/volcengine_live.go | 4 +- 13 files changed, 1431 insertions(+), 3 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index 7b7df3b1..c22196c4 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -34,7 +34,7 @@ type HuaweiCloudELBDeployerConfig struct { // 证书 ID。 // 部署资源类型为 [DEPLOY_RESOURCE_CERTIFICATE] 时必填。 CertificateId string `json:"certificateId,omitempty"` - // 负载均衡实例 ID。 + // 负载均衡器 ID。 // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 LoadbalancerId string `json:"loadbalancerId,omitempty"` // 负载均衡监听 ID。 diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go new file mode 100644 index 00000000..23f9ad99 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -0,0 +1,198 @@ +package tencentcloudcdn + +import ( + "context" + "errors" + "strings" + + xerrors "github.com/pkg/errors" + tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + "golang.org/x/exp/slices" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudCDNDeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type TencentCloudCDNDeployer struct { + config *TencentCloudCDNDeployerConfig + logger deployer.Logger + sdkClients *tencentCloudCDNDeployerSdkClients + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudCDNDeployer)(nil) + +type tencentCloudCDNDeployerSdkClients struct { + ssl *tcSsl.Client + cdn *tcCdn.Client +} + +func New(config *TencentCloudCDNDeployerConfig) (*TencentCloudCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudCDNDeployerConfig, logger deployer.Logger) (*TencentCloudCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + clients, err := createSdkClients(config.SecretId, config.SecretKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudCDNDeployer{ + logger: logger, + config: config, + sdkClients: clients, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 获取待部署的 CDN 实例 + // 如果是泛域名,根据证书匹配 CDN 实例 + instanceIds := make([]string, 0) + if strings.HasPrefix(d.config.Domain, "*.") { + domains, err := d.getDomainsByCertificateId(upres.CertId) + if err != nil { + return nil, err + } + + instanceIds = domains + } else { + instanceIds = append(instanceIds, d.config.Domain) + } + + // 跳过已部署的 CDN 实例 + if len(instanceIds) > 0 { + deployedDomains, err := d.getDeployedDomainsByCertificateId(upres.CertId) + if err != nil { + return nil, err + } + + temp := make([]string, 0) + for _, instanceId := range instanceIds { + if !slices.Contains(deployedDomains, instanceId) { + temp = append(temp, instanceId) + } + } + instanceIds = temp + } + + if len(instanceIds) == 0 { + d.logger.Appendt("已部署过或没有要部署的 CDN 实例") + } else { + // 证书部署到 CDN 实例 + // REF: https://cloud.tencent.com/document/product/400/91667 + deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest() + deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) + deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn") + deployCertificateInstanceReq.Status = common.Int64Ptr(1) + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds) + deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") + } + + d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + } + + return &deployer.DeployResult{}, nil +} + +func (d *TencentCloudCDNDeployer) getDomainsByCertificateId(cloudCertId string) ([]string, error) { + // 获取证书中的可用域名 + // REF: https://cloud.tencent.com/document/product/228/42491 + describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest() + describeCertDomainsReq.CertId = common.StringPtr(cloudCertId) + describeCertDomainsReq.Product = common.StringPtr("cdn") + describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'") + } + + domains := make([]string, 0) + if describeCertDomainsResp.Response.Domains == nil { + for _, domain := range describeCertDomainsResp.Response.Domains { + domains = append(domains, *domain) + } + } + + return domains, nil +} + +func (d *TencentCloudCDNDeployer) getDeployedDomainsByCertificateId(cloudCertId string) ([]string, error) { + // 根据证书查询关联 CDN 域名 + // REF: https://cloud.tencent.com/document/product/400/62674 + describeDeployedResourcesReq := tcSsl.NewDescribeDeployedResourcesRequest() + describeDeployedResourcesReq.CertificateIds = common.StringPtrs([]string{cloudCertId}) + describeDeployedResourcesReq.ResourceType = common.StringPtr("cdn") + describeDeployedResourcesResp, err := d.sdkClients.ssl.DescribeDeployedResources(describeDeployedResourcesReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeDeployedResources'") + } + + domains := make([]string, 0) + if describeDeployedResourcesResp.Response.DeployedResources != nil { + for _, deployedResource := range describeDeployedResourcesResp.Response.DeployedResources { + for _, resource := range deployedResource.Resources { + domains = append(domains, *resource) + } + } + } + + return domains, nil +} + +func createSdkClients(secretId, secretKey string) (*tencentCloudCDNDeployerSdkClients, error) { + credential := common.NewCredential(secretId, secretKey) + + sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return &tencentCloudCDNDeployerSdkClients{ + ssl: sslClient, + cdn: cdnClient, + }, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go new file mode 100644 index 00000000..343a2d56 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go @@ -0,0 +1,75 @@ +package tencentcloudcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v tencentcloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_DOMAIN="example.com" +*/ +func Test(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("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCDNDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go new file mode 100644 index 00000000..47eedfb0 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go @@ -0,0 +1,14 @@ +package tencentcloudclb + +type DeployResourceType string + +const ( + // 资源类型:通过 SSL 服务部署到云资源实例。 + DEPLOY_RESOURCE_USE_SSLDEPLOY = DeployResourceType("ssl-deploy") + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") + // 资源类型:部署到指定转发规则域名。 + DEPLOY_RESOURCE_RULEDOMAIN = DeployResourceType("ruledomain") +) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go new file mode 100644 index 00000000..f012f660 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -0,0 +1,305 @@ +package tencentcloudclb + +import ( + "context" + "errors" + "fmt" + + xerrors "github.com/pkg/errors" + tcClb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudCLBDeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡器 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。 + ListenerId string `json:"listenerId,omitempty"` + // SNI 域名或七层转发规则域名(支持泛域名)。 + // 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY] 时选填;部署资源类型为 [DEPLOY_RESOURCE_RULEDOMAIN] 时必填。 + Domain string `json:"domain,omitempty"` +} + +type TencentCloudCLBDeployer struct { + config *TencentCloudCLBDeployerConfig + logger deployer.Logger + sdkClients *wSdkClients + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudCLBDeployer)(nil) + +type wSdkClients struct { + ssl *tcSsl.Client + clb *tcClb.Client +} + +func New(config *TencentCloudCLBDeployerConfig) (*TencentCloudCLBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudCLBDeployerConfig, logger deployer.Logger) (*TencentCloudCLBDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + clients, err := createSdkClients(config.SecretId, config.SecretKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudCLBDeployer{ + logger: logger, + config: config, + sdkClients: clients, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_USE_SSLDEPLOY: + if err := d.deployToInstanceUseSsl(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_RULEDOMAIN: + if err := d.deployToRuleDomain(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *TencentCloudCLBDeployer) deployToInstanceUseSsl(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 证书部署到 CLB 实例 + // REF: https://cloud.tencent.com/document/product/400/91667 + deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest() + deployCertificateInstanceReq.CertificateId = common.StringPtr(cloudCertId) + deployCertificateInstanceReq.ResourceType = common.StringPtr("clb") + deployCertificateInstanceReq.Status = common.Int64Ptr(1) + if d.config.Domain == "" { + // 未开启 SNI,只需指定到监听器 + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", d.config.LoadbalancerId, d.config.ListenerId)}) + } else { + // 开启 SNI,需指定到域名(支持泛域名) + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", d.config.LoadbalancerId, d.config.ListenerId, d.config.Domain)}) + } + deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") + } + + d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + + return nil +} + +func (d *TencentCloudCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询监听器列表 + // REF: https://cloud.tencent.com/document/api/214/30686 + describeListenersReq := tcClb.NewDescribeListenersRequest() + describeListenersReq.LoadBalancerId = common.StringPtr(d.config.LoadbalancerId) + describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'") + } else { + if describeListenersResp.Response.Listeners != nil { + for _, listener := range describeListenersResp.Response.Listeners { + if listener.Protocol == nil || (*listener.Protocol != "HTTPS" && *listener.Protocol != "TCP_SSL" && *listener.Protocol != "QUIC") { + continue + } + + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + } + + d.logger.Appendt("已查询到负载均衡器下的监听器", listenerIds) + + // 批量更新监听器证书 + if len(listenerIds) > 0 { + var errs []error + + for _, listenerId := range listenerIds { + if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *TencentCloudCLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听器证书 + if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *TencentCloudCLBDeployer) deployToRuleDomain(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + // 修改负载均衡七层监听器转发规则的域名级别属性 + // REF: https://cloud.tencent.com/document/api/214/38092 + modifyDomainAttributesReq := tcClb.NewModifyDomainAttributesRequest() + modifyDomainAttributesReq.LoadBalancerId = common.StringPtr(d.config.LoadbalancerId) + modifyDomainAttributesReq.ListenerId = common.StringPtr(d.config.ListenerId) + modifyDomainAttributesReq.Domain = common.StringPtr(d.config.Domain) + modifyDomainAttributesReq.Certificate = &tcClb.CertificateInput{ + SSLMode: common.StringPtr("UNIDIRECTIONAL"), + CertId: common.StringPtr(cloudCertId), + } + modifyDomainAttributesResp, err := d.sdkClients.clb.ModifyDomainAttributes(modifyDomainAttributesReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'") + } + + d.logger.Appendt("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response) + + return nil +} + +func (d *TencentCloudCLBDeployer) modifyListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error { + // 查询监听器列表 + // REF: https://cloud.tencent.com/document/api/214/30686 + describeListenersReq := tcClb.NewDescribeListenersRequest() + describeListenersReq.LoadBalancerId = common.StringPtr(cloudLoadbalancerId) + describeListenersReq.ListenerIds = common.StringPtrs([]string{cloudListenerId}) + describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'") + } + if len(describeListenersResp.Response.Listeners) == 0 { + return errors.New("listener not found") + } + + d.logger.Appendt("已查询到监听器属性", describeListenersResp.Response) + + // 修改监听器属性 + // REF: https://cloud.tencent.com/document/product/214/30681 + modifyListenerReq := tcClb.NewModifyListenerRequest() + modifyListenerReq.LoadBalancerId = common.StringPtr(cloudLoadbalancerId) + modifyListenerReq.ListenerId = common.StringPtr(cloudListenerId) + modifyListenerReq.Certificate = &tcClb.CertificateInput{CertId: common.StringPtr(cloudCertId)} + if describeListenersResp.Response.Listeners[0].Certificate != nil && describeListenersResp.Response.Listeners[0].Certificate.SSLMode != nil { + modifyListenerReq.Certificate.SSLMode = describeListenersResp.Response.Listeners[0].Certificate.SSLMode + modifyListenerReq.Certificate.CertCaId = describeListenersResp.Response.Listeners[0].Certificate.CertCaId + } else { + modifyListenerReq.Certificate.SSLMode = common.StringPtr("UNIDIRECTIONAL") + } + modifyListenerResp, err := d.sdkClients.clb.ModifyListener(modifyListenerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyListener'") + } + + d.logger.Appendt("已修改监听器属性", modifyListenerResp.Response) + + return nil +} + +func createSdkClients(secretId, secretKey, region string) (*wSdkClients, error) { + credential := common.NewCredential(secretId, secretKey) + + // 注意虽然官方文档中地域无需指定,但实际需要部署到 CLB 时必传 + sslClient, err := tcSsl.NewClient(credential, region, profile.NewClientProfile()) + if err != nil { + return nil, err + } + + clbClient, err := tcClb.NewClient(credential, region, profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return &wSdkClients{ + ssl: sslClient, + clb: clbClient, + }, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go new file mode 100644 index 00000000..1409c240 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go @@ -0,0 +1,199 @@ +package tencentcloudclb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fRegion string + fLoadbalancerId string + fListenerId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v tencentcloud_clb_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_REGION="ap-guangzhou" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_LOADBALANCERID="your-clb-lb-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_LISTENERID="your-clb-lbl-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_DOMAIN="example.com" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_UseSslDeploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_USE_SSLDEPLOY, + LoadbalancerId: fLoadbalancerId, + ListenerId: fListenerId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_LISTENER, + LoadbalancerId: fLoadbalancerId, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToRuleDomain", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + ResourceType: provider.DEPLOY_RESOURCE_RULEDOMAIN, + LoadbalancerId: fLoadbalancerId, + ListenerId: fListenerId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go new file mode 100644 index 00000000..a7382a11 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -0,0 +1,115 @@ +package tencentcloudcdn + +import ( + "context" + "errors" + "fmt" + + xerrors "github.com/pkg/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudCOSDeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云地域。 + Region string `json:"region"` + // 存储桶名。 + Bucket string `json:"bucket"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type TencentCloudCOSDeployer struct { + config *TencentCloudCOSDeployerConfig + logger deployer.Logger + sdkClient *tcSsl.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudCOSDeployer)(nil) + +func New(config *TencentCloudCOSDeployerConfig) (*TencentCloudCOSDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudCOSDeployerConfig, logger deployer.Logger) (*TencentCloudCOSDeployer, 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.SecretId, config.SecretKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudCOSDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudCOSDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.Bucket == "" { + return nil, errors.New("config `bucket` is required") + } + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 证书部署到 COS 实例 + // REF: https://cloud.tencent.com/document/product/400/91667 + deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest() + deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) + deployCertificateInstanceReq.ResourceType = common.StringPtr("cos") + deployCertificateInstanceReq.Status = common.Int64Ptr(1) + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", d.config.Region, d.config.Bucket, d.config.Domain)}) + deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") + } + + d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(secretId, secretKey, region string) (*tcSsl.Client, error) { + credential := common.NewCredential(secretId, secretKey) + client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go new file mode 100644 index 00000000..4043305d --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go @@ -0,0 +1,85 @@ +package tencentcloudcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fRegion string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v tencentcloud_cos_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_REGION="ap-guangzhou" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_BUCKET="your-cos-bucket" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_DOMAIN="example.com" +*/ +func Test(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("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudCOSDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + 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/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go new file mode 100644 index 00000000..39175b9d --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -0,0 +1,158 @@ +package tencentcloudecdn + +import ( + "context" + "errors" + "strings" + + xerrors "github.com/pkg/errors" + tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudECDNDeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type TencentCloudECDNDeployer struct { + config *TencentCloudECDNDeployerConfig + logger deployer.Logger + sdkClients *wSdkClients + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudECDNDeployer)(nil) + +type wSdkClients struct { + ssl *tcSsl.Client + cdn *tcCdn.Client +} + +func New(config *TencentCloudECDNDeployerConfig) (*TencentCloudECDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudECDNDeployerConfig, logger deployer.Logger) (*TencentCloudECDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + clients, err := createSdkClients(config.SecretId, config.SecretKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudECDNDeployer{ + logger: logger, + config: config, + sdkClients: clients, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudECDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 获取待部署的 CDN 实例 + // 如果是泛域名,根据证书匹配 CDN 实例 + instanceIds := make([]string, 0) + if strings.HasPrefix(d.config.Domain, "*.") { + domains, err := d.getDomainsByCertificateId(upres.CertId) + if err != nil { + return nil, err + } + + instanceIds = domains + } else { + instanceIds = append(instanceIds, d.config.Domain) + } + + if len(instanceIds) == 0 { + d.logger.Appendt("已部署过或没有要部署的 ECDN 实例") + } else { + // 证书部署到 ECDN 实例 + // REF: https://cloud.tencent.com/document/product/400/91667 + deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest() + deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) + deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn") + deployCertificateInstanceReq.Status = common.Int64Ptr(1) + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds) + deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") + } + + d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + } + + return &deployer.DeployResult{}, nil +} + +func (d *TencentCloudECDNDeployer) getDomainsByCertificateId(cloudCertId string) ([]string, error) { + // 获取证书中的可用域名 + // REF: https://cloud.tencent.com/document/product/228/42491 + describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest() + describeCertDomainsReq.CertId = common.StringPtr(cloudCertId) + describeCertDomainsReq.Product = common.StringPtr("ecdn") + describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'") + } + + domains := make([]string, 0) + if describeCertDomainsResp.Response.Domains == nil { + for _, domain := range describeCertDomainsResp.Response.Domains { + domains = append(domains, *domain) + } + } + + return domains, nil +} + +func createSdkClients(secretId, secretKey string) (*wSdkClients, error) { + credential := common.NewCredential(secretId, secretKey) + + sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return &wSdkClients{ + ssl: sslClient, + cdn: cdnClient, + }, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go new file mode 100644 index 00000000..4322d977 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go @@ -0,0 +1,75 @@ +package tencentcloudecdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v tencentcloud_ecdn_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_DOMAIN="example.com" +*/ +func Test(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("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudECDNDeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go new file mode 100644 index 00000000..7fde103e --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go @@ -0,0 +1,124 @@ +package tencentcloudeteo + +import ( + "context" + "errors" + + xerrors "github.com/pkg/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + tcTeo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudTEODeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 站点 ID。 + ZoneId string `json:"zoneId"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type TencentCloudTEODeployer struct { + config *TencentCloudTEODeployerConfig + logger deployer.Logger + sdkClients *wSdkClients + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudTEODeployer)(nil) + +type wSdkClients struct { + ssl *tcSsl.Client + teo *tcTeo.Client +} + +func New(config *TencentCloudTEODeployerConfig) (*TencentCloudTEODeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudTEODeployerConfig, logger deployer.Logger) (*TencentCloudTEODeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + clients, err := createSdkClients(config.SecretId, config.SecretKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudTEODeployer{ + logger: logger, + config: config, + sdkClients: clients, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudTEODeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.ZoneId == "" { + return nil, xerrors.New("config `zoneId` is required") + } + + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 配置域名证书 + // REF: https://cloud.tencent.com/document/product/1552/80764 + modifyHostsCertificateReq := tcTeo.NewModifyHostsCertificateRequest() + modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId) + modifyHostsCertificateReq.Mode = common.StringPtr("sslcert") + modifyHostsCertificateReq.Hosts = common.StringPtrs([]string{d.config.Domain}) + modifyHostsCertificateReq.ServerCertInfo = []*tcTeo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}} + modifyHostsCertificateResp, err := d.sdkClients.teo.ModifyHostsCertificate(modifyHostsCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'") + } + + d.logger.Appendt("已配置域名证书", modifyHostsCertificateResp.Response) + + return &deployer.DeployResult{}, nil +} + +func createSdkClients(secretId, secretKey string) (*wSdkClients, error) { + credential := common.NewCredential(secretId, secretKey) + + sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + teoClient, err := tcTeo.NewClient(credential, "", profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return &wSdkClients{ + ssl: sslClient, + teo: teoClient, + }, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go new file mode 100644 index 00000000..5cf4cc08 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go @@ -0,0 +1,80 @@ +package tencentcloudeteo_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-teo" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fZoneId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fZoneId, argsPrefix+"ZONEID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v tencentcloud_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_ZONEID="your-zone-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_DOMAIN="example.com" +*/ +func Test(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("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("ZONEID: %v", fZoneId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.TencentCloudTEODeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + ZoneId: fZoneId, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go index 7b7eb8d6..552ba294 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -17,9 +17,9 @@ import ( type VolcEngineLiveDeployerConfig struct { // 火山引擎 AccessKey。 - AccessKey string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` // 火山引擎 SecretKey。 - SecretKey string `json:"secretAccessKey"` + SecretKey string `json:"secretKey"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } From 13582d1a7ba28d616fba30cf0a4f11a8ab92bae8 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 21 Nov 2024 10:29:04 +0800 Subject: [PATCH 14/17] test: add unit test cases --- internal/deployer/factory.go | 35 ++++++++++ internal/notify/factory.go | 4 +- internal/pkg/core/deployer/logger.go | 12 +++- internal/pkg/core/deployer/logger_test.go | 56 ++++++++++++++++ .../providers/aliyun-alb/aliyun_alb_test.go | 2 +- .../providers/aliyun-cdn/aliyun_cdn_test.go | 2 +- .../providers/aliyun-clb/aliyun_clb_test.go | 2 +- .../providers/aliyun-dcdn/aliyun_dcdn_test.go | 2 +- .../providers/aliyun-nlb/aliyun_nlb_test.go | 2 +- .../providers/aliyun-oss/aliyun_oss_test.go | 2 +- .../baiducloud-cdn/baiducloud_cdn_test.go | 2 +- .../byteplus-cdn/byteplus_cdn_test.go | 2 +- .../dogecloud-cdn/dogecloud_cdn_test.go | 2 +- .../huaweicloud-cdn/huaweicloud_cdn_test.go | 2 +- .../huaweicloud-elb/huaweicloud_elb_test.go | 2 +- .../providers/k8s-secret/k8s_secret_test.go | 2 +- .../deployer/providers/local/local_test.go | 2 +- .../providers/qiniu-cdn/qiniu_cdn_test.go | 2 +- .../core/deployer/providers/ssh/ssh_test.go | 2 +- .../tencentcloud-cdn/tencentcloud_cdn_test.go | 2 +- .../tencentcloud-clb/tencentcloud_clb_test.go | 2 +- .../tencentcloud-cos/tencentcloud_cos_test.go | 2 +- .../tencentcloud_ecdn_test.go | 2 +- .../tencentcloud-teo/tencentcloud_teo_test.go | 2 +- .../volcengine-cdn/volcengine_cdn_test.go | 2 +- .../volcengine-live/volcengine_live_test.go | 2 +- .../providers/webhook/webhook_test.go | 2 +- .../core/notifier/providers/bark/bark_test.go | 64 +++++++++++++++++++ .../providers/dingtalk/dingtalk_test.go | 63 ++++++++++++++++++ .../notifier/providers/email/email_test.go | 2 +- .../core/notifier/providers/lark/lark_test.go | 57 +++++++++++++++++ .../providers/serverchan/serverchan_test.go | 57 +++++++++++++++++ .../providers/telegram/telegram_test.go | 64 +++++++++++++++++++ .../providers/webhook/webhook_test.go | 2 +- 34 files changed, 434 insertions(+), 28 deletions(-) create mode 100644 internal/deployer/factory.go create mode 100644 internal/pkg/core/deployer/logger_test.go create mode 100644 internal/pkg/core/notifier/providers/bark/bark_test.go create mode 100644 internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go create mode 100644 internal/pkg/core/notifier/providers/lark/lark_test.go create mode 100644 internal/pkg/core/notifier/providers/serverchan/serverchan_test.go create mode 100644 internal/pkg/core/notifier/providers/telegram/telegram_test.go diff --git a/internal/deployer/factory.go b/internal/deployer/factory.go new file mode 100644 index 00000000..ed18a7a5 --- /dev/null +++ b/internal/deployer/factory.go @@ -0,0 +1,35 @@ +package deployer + +import ( + "encoding/json" + "fmt" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/deployer" + providerAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" + "github.com/usual2970/certimate/internal/pkg/utils/maps" +) + +// TODO: 该方法目前未实际使用,将在后续迭代中替换 +func createDeployer(target string, accessConfig string, deployConfig map[string]any) (deployer.Deployer, deployer.Logger, error) { + logger := deployer.NewDefaultLogger() + + switch target { + case targetAliyunOSS: + access := &domain.AliyunAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, err + } + + deployer, err := providerAliyunOss.NewWithLogger(&providerAliyunOss.AliyunOSSDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(deployConfig, "region"), + Bucket: maps.GetValueAsString(deployConfig, "bucket"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + } + + return nil, nil, fmt.Errorf("unsupported deployer target: %s", target) +} diff --git a/internal/notify/factory.go b/internal/notify/factory.go index 9ed0e3f4..e244b071 100644 --- a/internal/notify/factory.go +++ b/internal/notify/factory.go @@ -1,7 +1,7 @@ package notify import ( - "errors" + "fmt" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" @@ -62,5 +62,5 @@ func createNotifier(channel string, channelConfig map[string]any) (notifier.Noti }) } - return nil, errors.New("unsupported notifier channel") + return nil, fmt.Errorf("unsupported notifier channel: %s", channelConfig) } diff --git a/internal/pkg/core/deployer/logger.go b/internal/pkg/core/deployer/logger.go index a97800b1..433b86ac 100644 --- a/internal/pkg/core/deployer/logger.go +++ b/internal/pkg/core/deployer/logger.go @@ -27,6 +27,9 @@ type Logger interface { // 获取所有日志记录。 GetRecords() []string + + // 清空。 + Flush() } // 表示默认的日志记录器类型。 @@ -43,7 +46,9 @@ func (l *DefaultLogger) Appendt(tag string, data ...any) { temp[0] = tag for i, v := range data { s := "" - if v != nil { + if v == nil { + s = "" + } else { switch reflect.ValueOf(v).Kind() { case reflect.String: s = v.(string) @@ -78,6 +83,10 @@ func (l *DefaultLogger) GetRecords() []string { return temp } +func (l *DefaultLogger) Flush() { + l.records = make([]string, 0) +} + func (l *DefaultLogger) ensureInitialized() { if l.records == nil { l.records = make([]string, 0) @@ -101,6 +110,7 @@ func (l *NilLogger) Appendf(string, ...any) {} func (l *NilLogger) GetRecords() []string { return make([]string, 0) } +func (l *NilLogger) Flush() {} func NewNilLogger() *NilLogger { return &NilLogger{} diff --git a/internal/pkg/core/deployer/logger_test.go b/internal/pkg/core/deployer/logger_test.go new file mode 100644 index 00000000..5e55df5c --- /dev/null +++ b/internal/pkg/core/deployer/logger_test.go @@ -0,0 +1,56 @@ +package deployer_test + +import ( + "testing" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +/* +Shell command to run this test: + + go test -v logger_test.go +*/ +func TestLogger(t *testing.T) { + t.Run("Logger_Appendt", func(t *testing.T) { + logger := deployer.NewDefaultLogger() + + logger.Appendt("test") + logger.Appendt("test_nil", nil) + logger.Appendt("test_int", 1024) + logger.Appendt("test_string", "certimate") + logger.Appendt("test_map", map[string]interface{}{"key": "value"}) + logger.Appendt("test_struct", struct{ Name string }{Name: "certimate"}) + logger.Appendt("test_slice", []string{"certimate"}) + t.Log(logger.GetRecords()) + if len(logger.GetRecords()) != 7 { + t.Errorf("expected 7 records, got %d", len(logger.GetRecords())) + } + + logger.Flush() + if len(logger.GetRecords()) != 0 { + t.Errorf("expected 0 records, got %d", len(logger.GetRecords())) + } + }) + + t.Run("Logger_Appendf", func(t *testing.T) { + logger := deployer.NewDefaultLogger() + + logger.Appendf("test") + logger.Appendf("test_nil: %v", nil) + logger.Appendf("test_int: %v", 1024) + logger.Appendf("test_string: %v", "certimate") + logger.Appendf("test_map: %v", map[string]interface{}{"key": "value"}) + logger.Appendf("test_struct: %v", struct{ Name string }{Name: "certimate"}) + logger.Appendf("test_slice: %v", []string{"certimate"}) + t.Log(logger.GetRecords()) + if len(logger.GetRecords()) != 7 { + t.Errorf("expected 7 records, got %d", len(logger.GetRecords())) + } + + logger.Flush() + if len(logger.GetRecords()) != 0 { + t.Errorf("expected 0 records, got %d", len(logger.GetRecords())) + } + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go index 686e1e6c..143b65b4 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -45,7 +45,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNALB_LOADBALANCERID="your-alb-instance-id" \ --CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go index e5562603..66464aa1 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_DEPLOYER_ALIYUNCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go index 6a8cc45c..f9aa800d 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -45,7 +45,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNCLB_LOADBALANCERID="your-clb-instance-id" \ --CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443 */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go index c5a6abec..8fdddf80 100644 --- a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_DEPLOYER_ALIYUNDCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go index b915c1a7..9c976dd9 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go @@ -45,7 +45,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNNLB_LOADBALANCERID="your-nlb-instance-id" \ --CERTIMATE_DEPLOYER_ALIYUNNLB_LISTENERID="your-nlb-listener-id" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go index 66834e24..afb31853 100644 --- a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go @@ -45,7 +45,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-oss-bucket" \ --CERTIMATE_DEPLOYER_ALIYUNOSS_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go index 31e51937..e3398505 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go +++ b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \ --CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go index 4a051dd6..0c411062 100644 --- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_BYTEPLUSCDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_BYTEPLUSCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go index da08c09e..2375e372 100644 --- a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go +++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_DOGECLOUDCDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_DOGECLOUDCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go index fc45129a..d50e3ea5 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go @@ -42,7 +42,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_REGION="cn-north-1" \ --CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go index 33e8afb6..f2e573ca 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb_test.go @@ -48,7 +48,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LOADBALANCERID="your-elb-loadbalancer-id" \ --CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LISTENERID="your-elb-listener-id" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_ToCertificate", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go index 8e6992f9..ac47deba 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret_test.go @@ -42,7 +42,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORCRT="tls.crt" \ --CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORKEY="tls.key" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go index e14f7b59..18838c63 100644 --- a/internal/pkg/core/deployer/providers/local/local_test.go +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -48,7 +48,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_LOCAL_JKSKEYPASS="your-jks-keypass" \ --CERTIMATE_DEPLOYER_LOCAL_JKSSTOREPASS="your-jks-storepass" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_PEM", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go index 290dc93d..df4ee008 100644 --- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_QINIUCDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_QINIUCDN_DOMAIN="example.com" \ */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index 041feb65..f1c25e63 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -48,7 +48,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \ --CERTIMATE_DEPLOYER_SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go index 343a2d56..70b3c59d 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go index 1409c240..fe0cf023 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go @@ -48,7 +48,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_LISTENERID="your-clb-lbl-id" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy_UseSslDeploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go index 4043305d..5f105efe 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos_test.go @@ -45,7 +45,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_BUCKET="your-cos-bucket" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go index 4322d977..95d617cf 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go index 5cf4cc08..8d875e55 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo_test.go @@ -42,7 +42,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_ZONEID="your-zone-id" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go index fad06e94..e24740be 100644 --- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_VOLCENGINECDN_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_VOLCENGINECDN_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go index 76735426..0ad66385 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_VOLCENGINELIVE_SECRETKEY="your-secret-key" \ --CERTIMATE_DEPLOYER_VOLCENGINELIVE_DOMAIN="example.com" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index e3a9b3e7..a3b36dda 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -33,7 +33,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" */ -func Test(t *testing.T) { +func TestDeploy(t *testing.T) { flag.Parse() t.Run("Deploy", func(t *testing.T) { diff --git a/internal/pkg/core/notifier/providers/bark/bark_test.go b/internal/pkg/core/notifier/providers/bark/bark_test.go new file mode 100644 index 00000000..3b9bef2d --- /dev/null +++ b/internal/pkg/core/notifier/providers/bark/bark_test.go @@ -0,0 +1,64 @@ +package bark_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var ( + fServerUrl string + fDeviceKey string +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_BARK_" + + flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "") + flag.StringVar(&fDeviceKey, argsPrefix+"DEVICEKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v bark_test.go -args \ + --CERTIMATE_NOTIFIER_BARK_SERVERURL="https://example.com/your-server-url" \ + --CERTIMATE_NOTIFIER_BARK_DEVICEKEY="your-device-key" +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("SERVERURL: %v", fServerUrl), + fmt.Sprintf("DEVICEKEY: %v", fDeviceKey), + }, "\n")) + + notifier, err := provider.New(&provider.BarkNotifierConfig{ + ServerUrl: fServerUrl, + DeviceKey: fDeviceKey, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go b/internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go new file mode 100644 index 00000000..ea2c9bc8 --- /dev/null +++ b/internal/pkg/core/notifier/providers/dingtalk/dingtalk_test.go @@ -0,0 +1,63 @@ +package dingtalk_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var ( + fAccessToken string + fSecret string +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_DINGTALK_" + + flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "") + flag.StringVar(&fSecret, argsPrefix+"SECRET", "", "") +} + +/* +Shell command to run this test: + + go test -v dingtalk_test.go -args \ + --CERTIMATE_NOTIFIER_DINGTALK_URL="https://example.com/your-webhook-url" +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken), + fmt.Sprintf("SECRET: %v", fSecret), + }, "\n")) + + notifier, err := provider.New(&provider.DingTalkNotifierConfig{ + AccessToken: fAccessToken, + Secret: fSecret, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 5f1fcd9a..34303d06 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -49,7 +49,7 @@ Shell command to run this test: --CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \ --CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" */ -func Test(t *testing.T) { +func TestNotify(t *testing.T) { flag.Parse() t.Run("Notify", func(t *testing.T) { diff --git a/internal/pkg/core/notifier/providers/lark/lark_test.go b/internal/pkg/core/notifier/providers/lark/lark_test.go new file mode 100644 index 00000000..b93d3606 --- /dev/null +++ b/internal/pkg/core/notifier/providers/lark/lark_test.go @@ -0,0 +1,57 @@ +package lark_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var fWebhookUrl string + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_LARK_" + + flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "") +} + +/* +Shell command to run this test: + + go test -v lark_test.go -args \ + --CERTIMATE_NOTIFIER_LARK_WEBHOOKURL="https://example.com/your-webhook-url" +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl), + }, "\n")) + + notifier, err := provider.New(&provider.LarkNotifierConfig{ + WebhookUrl: fWebhookUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go b/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go new file mode 100644 index 00000000..63407379 --- /dev/null +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan_test.go @@ -0,0 +1,57 @@ +package serverchan_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var fUrl string + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_SERVERCHAN_" + + flag.StringVar(&fUrl, argsPrefix+"URL", "", "") +} + +/* +Shell command to run this test: + + go test -v serverchan_test.go -args \ + --CERTIMATE_NOTIFIER_SERVERCHAN_URL="https://example.com/your-webhook-url" \ +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("URL: %v", fUrl), + }, "\n")) + + notifier, err := provider.New(&provider.ServerChanNotifierConfig{ + Url: fUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/telegram/telegram_test.go b/internal/pkg/core/notifier/providers/telegram/telegram_test.go new file mode 100644 index 00000000..062e2642 --- /dev/null +++ b/internal/pkg/core/notifier/providers/telegram/telegram_test.go @@ -0,0 +1,64 @@ +package telegram_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var ( + fApiToken string + fChartId int64 +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_TELEGRAM_" + + flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") + flag.Int64Var(&fChartId, argsPrefix+"CHATID", 0, "") +} + +/* +Shell command to run this test: + + go test -v telegram_test.go -args \ + --CERTIMATE_NOTIFIER_TELEGRAM_APITOKEN="your-api-token" \ + --CERTIMATE_NOTIFIER_TELEGRAM_CHATID=123456 +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("APITOKEN: %v", fApiToken), + fmt.Sprintf("CHATID: %v", fChartId), + }, "\n")) + + notifier, err := provider.New(&provider.TelegramNotifierConfig{ + ApiToken: fApiToken, + ChatId: fChartId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index 294c60e8..2f83f8e0 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -29,7 +29,7 @@ Shell command to run this test: go test -v webhook_test.go -args \ --CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" */ -func Test(t *testing.T) { +func TestNotify(t *testing.T) { flag.Parse() t.Run("Notify", func(t *testing.T) { From 30b66adc3b43c6e8fd7db7a8de0ac4bf288bfefd Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 21 Nov 2024 10:35:45 +0800 Subject: [PATCH 15/17] refactor: replace `Append*` to `Log*` in `DeployerLogger` --- internal/pkg/core/deployer/logger.go | 20 ++++++------ internal/pkg/core/deployer/logger_test.go | 32 +++++++++---------- .../providers/aliyun-alb/aliyun_alb.go | 16 +++++----- .../providers/aliyun-cdn/aliyun_cdn.go | 2 +- .../providers/aliyun-clb/aliyun_clb.go | 14 ++++---- .../providers/aliyun-dcdn/aliyun_dcdn.go | 2 +- .../providers/aliyun-nlb/aliyun_nlb.go | 12 +++---- .../baiducloud-cdn/baiducloud_cdn.go | 2 +- .../providers/byteplus-cdn/byteplus_cdn.go | 4 +-- .../providers/dogecloud-cdn/dogecloud_cdn.go | 4 +-- .../huaweicloud-cdn/huaweicloud_cdn.go | 6 ++-- .../huaweicloud-elb/huaweicloud_elb.go | 22 ++++++------- .../providers/k8s-secret/k8s_secret.go | 4 +-- .../core/deployer/providers/local/local.go | 16 +++++----- .../deployer/providers/qiniu-cdn/qiniu_cdn.go | 8 ++--- .../pkg/core/deployer/providers/ssh/ssh.go | 18 +++++------ .../tencentcloud-cdn/tencentcloud_cdn.go | 6 ++-- .../tencentcloud-clb/tencentcloud_clb.go | 12 +++---- .../tencentcloud-cos/tencentcloud_cos.go | 4 +-- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 6 ++-- .../tencentcloud-teo/tencentcloud_teo.go | 4 +-- .../volcengine-cdn/volcengine_cdn.go | 4 +-- .../volcengine-live/volcengine_live.go | 4 +-- .../deployer/providers/webhook/webhook.go | 2 +- 24 files changed, 110 insertions(+), 114 deletions(-) diff --git a/internal/pkg/core/deployer/logger.go b/internal/pkg/core/deployer/logger.go index 433b86ac..8a8b2f32 100644 --- a/internal/pkg/core/deployer/logger.go +++ b/internal/pkg/core/deployer/logger.go @@ -15,7 +15,7 @@ type Logger interface { // 入参: // - tag:标签。 // - data:数据。 - Appendt(tag string, data ...any) + Logt(tag string, data ...any) // 追加一条日志记录。 // 该方法会将 `args` 以 `format` 格式化。 @@ -23,13 +23,13 @@ type Logger interface { // 入参: // - format:格式化字符串。 // - args:格式化参数。 - Appendf(format string, args ...any) + Logf(format string, args ...any) // 获取所有日志记录。 GetRecords() []string - // 清空。 - Flush() + // 清空所有日志记录。 + FlushRecords() } // 表示默认的日志记录器类型。 @@ -39,7 +39,7 @@ type DefaultLogger struct { var _ Logger = (*DefaultLogger)(nil) -func (l *DefaultLogger) Appendt(tag string, data ...any) { +func (l *DefaultLogger) Logt(tag string, data ...any) { l.ensureInitialized() temp := make([]string, len(data)+1) @@ -69,7 +69,7 @@ func (l *DefaultLogger) Appendt(tag string, data ...any) { l.records = append(l.records, strings.Join(temp, ": ")) } -func (l *DefaultLogger) Appendf(format string, args ...any) { +func (l *DefaultLogger) Logf(format string, args ...any) { l.ensureInitialized() l.records = append(l.records, fmt.Sprintf(format, args...)) @@ -83,7 +83,7 @@ func (l *DefaultLogger) GetRecords() []string { return temp } -func (l *DefaultLogger) Flush() { +func (l *DefaultLogger) FlushRecords() { l.records = make([]string, 0) } @@ -105,12 +105,12 @@ type NilLogger struct{} var _ Logger = (*NilLogger)(nil) -func (l *NilLogger) Appendt(string, ...any) {} -func (l *NilLogger) Appendf(string, ...any) {} +func (l *NilLogger) Logt(string, ...any) {} +func (l *NilLogger) Logf(string, ...any) {} func (l *NilLogger) GetRecords() []string { return make([]string, 0) } -func (l *NilLogger) Flush() {} +func (l *NilLogger) FlushRecords() {} func NewNilLogger() *NilLogger { return &NilLogger{} diff --git a/internal/pkg/core/deployer/logger_test.go b/internal/pkg/core/deployer/logger_test.go index 5e55df5c..35021a99 100644 --- a/internal/pkg/core/deployer/logger_test.go +++ b/internal/pkg/core/deployer/logger_test.go @@ -15,19 +15,19 @@ func TestLogger(t *testing.T) { t.Run("Logger_Appendt", func(t *testing.T) { logger := deployer.NewDefaultLogger() - logger.Appendt("test") - logger.Appendt("test_nil", nil) - logger.Appendt("test_int", 1024) - logger.Appendt("test_string", "certimate") - logger.Appendt("test_map", map[string]interface{}{"key": "value"}) - logger.Appendt("test_struct", struct{ Name string }{Name: "certimate"}) - logger.Appendt("test_slice", []string{"certimate"}) + logger.Logt("test") + logger.Logt("test_nil", nil) + logger.Logt("test_int", 1024) + logger.Logt("test_string", "certimate") + logger.Logt("test_map", map[string]interface{}{"key": "value"}) + logger.Logt("test_struct", struct{ Name string }{Name: "certimate"}) + logger.Logt("test_slice", []string{"certimate"}) t.Log(logger.GetRecords()) if len(logger.GetRecords()) != 7 { t.Errorf("expected 7 records, got %d", len(logger.GetRecords())) } - logger.Flush() + logger.FlushRecords() if len(logger.GetRecords()) != 0 { t.Errorf("expected 0 records, got %d", len(logger.GetRecords())) } @@ -36,19 +36,19 @@ func TestLogger(t *testing.T) { t.Run("Logger_Appendf", func(t *testing.T) { logger := deployer.NewDefaultLogger() - logger.Appendf("test") - logger.Appendf("test_nil: %v", nil) - logger.Appendf("test_int: %v", 1024) - logger.Appendf("test_string: %v", "certimate") - logger.Appendf("test_map: %v", map[string]interface{}{"key": "value"}) - logger.Appendf("test_struct: %v", struct{ Name string }{Name: "certimate"}) - logger.Appendf("test_slice: %v", []string{"certimate"}) + logger.Logf("test") + logger.Logf("test_nil: %v", nil) + logger.Logf("test_int: %v", 1024) + logger.Logf("test_string: %v", "certimate") + logger.Logf("test_map: %v", map[string]interface{}{"key": "value"}) + logger.Logf("test_struct: %v", struct{ Name string }{Name: "certimate"}) + logger.Logf("test_slice: %v", []string{"certimate"}) t.Log(logger.GetRecords()) if len(logger.GetRecords()) != 7 { t.Errorf("expected 7 records, got %d", len(logger.GetRecords())) } - logger.Flush() + logger.FlushRecords() if len(logger.GetRecords()) != 0 { t.Errorf("expected 0 records, got %d", len(logger.GetRecords())) } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index 2cbf674e..cdef4ee8 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -95,7 +95,7 @@ func (d *AliyunALBDeployer) Deploy(ctx context.Context, certPem string, privkeyP return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 根据部署资源类型决定部署方式 switch d.config.ResourceType { @@ -133,7 +133,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'") } - d.logger.Appendt("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp) + d.logger.Logt("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp) // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners @@ -158,7 +158,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - if listListenersResp.Body.NextToken == nil { + if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break } else { listListenersToken = listListenersResp.Body.NextToken @@ -166,7 +166,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", listenerIds) + d.logger.Logt("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", listenerIds) // 查询 QUIC 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners @@ -190,7 +190,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - if listListenersResp.Body.NextToken == nil { + if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break } else { listListenersToken = listListenersResp.Body.NextToken @@ -198,7 +198,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds) + d.logger.Logt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds) // 批量更新监听证书 var errs []error @@ -238,7 +238,7 @@ func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'") } - d.logger.Appendt("已查询到 ALB 监听配置", getListenerAttributeResp) + d.logger.Logt("已查询到 ALB 监听配置", getListenerAttributeResp) // 修改监听的属性 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute @@ -253,7 +253,7 @@ func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'") } - d.logger.Appendt("已更新 ALB 监听配置", updateListenerAttributeResp) + d.logger.Logt("已更新 ALB 监听配置", updateListenerAttributeResp) // TODO: #347 diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go index caf97753..85005e8c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -72,7 +72,7 @@ func (d *AliyunCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyP return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'") } - d.logger.Appendt("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp) + d.logger.Logt("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 8bf2b0c8..380c8581 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -83,7 +83,7 @@ func (d *AliyunCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyP return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 根据部署资源类型决定部署方式 switch d.config.ResourceType { @@ -122,7 +122,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'") } - d.logger.Appendt("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp) + d.logger.Logt("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp) // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners @@ -148,7 +148,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - if describeLoadBalancerListenersResp.Body.NextToken == nil { + if len(describeLoadBalancerListenersResp.Body.Listeners) == 0 || describeLoadBalancerListenersResp.Body.NextToken == nil { break } else { listListenersToken = describeLoadBalancerListenersResp.Body.NextToken @@ -156,7 +156,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - d.logger.Appendt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts) + d.logger.Logt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts) // 批量更新监听证书 var errs []error @@ -200,7 +200,7 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'") } - d.logger.Appendt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp) + d.logger.Logt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp) // 查询扩展域名 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions @@ -214,7 +214,7 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'") } - d.logger.Appendt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) + d.logger.Logt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) // 遍历修改扩展域名 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute @@ -253,7 +253,7 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'") } - d.logger.Appendt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) + d.logger.Logt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) return nil } diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go index d71d24c0..41f21362 100644 --- a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -76,7 +76,7 @@ func (d *AliyunDCDNDeployer) Deploy(ctx context.Context, certPem string, privkey return nil, xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'") } - d.logger.Appendt("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp) + d.logger.Logt("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index f20aa70d..93ef6bf1 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -95,7 +95,7 @@ func (d *AliyunNLBDeployer) Deploy(ctx context.Context, certPem string, privkeyP return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 根据部署资源类型决定部署方式 switch d.config.ResourceType { @@ -133,7 +133,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'") } - d.logger.Appendt("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp) + d.logger.Logt("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp) // 查询 TCPSSL 监听列表 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners @@ -158,7 +158,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - if listListenersResp.Body.NextToken == nil { + if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break } else { listListenersToken = listListenersResp.Body.NextToken @@ -166,7 +166,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI } } - d.logger.Appendt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds) + d.logger.Logt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds) // 批量更新监听证书 var errs []error @@ -206,7 +206,7 @@ func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'") } - d.logger.Appendt("已查询到 NLB 监听配置", getListenerAttributeResp) + d.logger.Logt("已查询到 NLB 监听配置", getListenerAttributeResp) // 修改监听的属性 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute @@ -219,7 +219,7 @@ func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloud return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'") } - d.logger.Appendt("已更新 NLB 监听配置", updateListenerAttributeResp) + d.logger.Logt("已更新 NLB 监听配置", updateListenerAttributeResp) return nil } diff --git a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go index e976003e..28d6bed5 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -71,7 +71,7 @@ func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context, certPem string, priv return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'") } - d.logger.Appendt("已修改域名证书", putCertResp) + d.logger.Logt("已修改域名证书", putCertResp) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go index 4d17b0b7..27650e05 100644 --- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -72,7 +72,7 @@ func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privke return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) domains := make([]string, 0) if strings.HasPrefix(d.config.Domain, "*.") { @@ -123,7 +123,7 @@ func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privke if err != nil { errs = append(errs, err) } else { - d.logger.Appendt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) + d.logger.Logt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) } } diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go index 794eb29b..20ef5904 100644 --- a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go +++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go @@ -69,7 +69,7 @@ func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privk return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 绑定证书 // REF: https://docs.dogecloud.com/cdn/api-cert-bind @@ -79,7 +79,7 @@ func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privk return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'") } - d.logger.Appendt("已绑定证书", bindCdnCertResp) + d.logger.Logt("已绑定证书", bindCdnCertResp) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index 40dd3691..9c7131d1 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -82,7 +82,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pri return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 查询加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html @@ -94,7 +94,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pri return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'") } - d.logger.Appendt("已查询到加速域名配置", showDomainFullConfigResp) + d.logger.Logt("已查询到加速域名配置", showDomainFullConfigResp) // 更新加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html @@ -116,7 +116,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pri return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'") } - d.logger.Appendt("已更新加速域名配置", updateDomainMultiCertificatesResp) + d.logger.Logt("已更新加速域名配置", updateDomainMultiCertificatesResp) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index c22196c4..7cbc4f12 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -93,7 +93,7 @@ func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context, certPem string, pri return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 根据部署资源类型决定部署方式 switch d.config.ResourceType { @@ -140,7 +140,7 @@ func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context, certPe return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'") } - d.logger.Appendt("已更新 ELB 证书", updateCertificateResp) + d.logger.Logt("已更新 ELB 证书", updateCertificateResp) return nil } @@ -162,7 +162,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certP return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'") } - d.logger.Appendt("已查询到 ELB 负载均衡器", showLoadBalancerResp) + d.logger.Logt("已查询到 ELB 负载均衡器", showLoadBalancerResp) // 查询监听器列表 // REF: https://support.huaweicloud.com/api-elb/ListListeners.html @@ -193,7 +193,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certP } } - d.logger.Appendt("已查询到 ELB 负载均衡器下的监听器", listenerIds) + d.logger.Logt("已查询到 ELB 负载均衡器下的监听器", listenerIds) // 上传证书到 SCM upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) @@ -201,7 +201,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certP return xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 批量更新监听器证书 var errs []error @@ -228,7 +228,7 @@ func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context, certPem s return xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 更新监听器证书 if err := d.modifyListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { @@ -249,7 +249,7 @@ func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'") } - d.logger.Appendt("已查询到 ELB 监听器", showListenerResp) + d.logger.Logt("已查询到 ELB 监听器", showListenerResp) // 更新监听器 // REF: https://support.huaweicloud.com/api-elb/UpdateListener.html @@ -312,7 +312,7 @@ func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateListener'") } - d.logger.Appendt("已更新 ELB 监听器", updateListenerResp) + d.logger.Logt("已更新 ELB 监听器", updateListenerResp) return nil } @@ -322,11 +322,7 @@ func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbCli region = "cn-north-4" // ELB 服务默认区域:华北四北京 } - projectId, err := (&HuaweiCloudELBDeployer{}).getSdkProjectId( - accessKeyId, - secretAccessKey, - region, - ) + projectId, err := getSdkProjectId(accessKeyId, secretAccessKey, region) if err != nil { return nil, err } diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go index 7e5a353a..f14fe1f6 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go @@ -111,7 +111,7 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyP if err != nil { return nil, xerrors.Wrap(err, "failed to create k8s secret") } else { - d.logger.Appendf("k8s secret created", secretPayload) + d.logger.Logf("k8s secret created", secretPayload) return &deployer.DeployResult{}, nil } } @@ -130,7 +130,7 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyP return nil, xerrors.Wrap(err, "failed to update k8s secret") } - d.logger.Appendf("k8s secret updated", secretPayload) + d.logger.Logf("k8s secret updated", secretPayload) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go index f8fb1f23..d4eb7a66 100644 --- a/internal/pkg/core/deployer/providers/local/local.go +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -77,7 +77,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s return nil, xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr) } - d.logger.Appendt("pre-command executed", stdout) + d.logger.Logt("pre-command executed", stdout) } // 写入证书和私钥文件 @@ -87,13 +87,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s return nil, xerrors.Wrap(err, "failed to save certificate file") } - d.logger.Appendt("certificate file saved") + d.logger.Logt("certificate file saved") if err := fs.WriteFileString(d.config.OutputKeyPath, privkeyPem); err != nil { return nil, xerrors.Wrap(err, "failed to save private key file") } - d.logger.Appendt("private key file saved") + d.logger.Logt("private key file saved") case OUTPUT_FORMAT_PFX: pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) @@ -101,13 +101,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s return nil, xerrors.Wrap(err, "failed to transform certificate to PFX") } - d.logger.Appendt("certificate transformed to PFX") + d.logger.Logt("certificate transformed to PFX") if err := fs.WriteFile(d.config.OutputCertPath, pfxData); err != nil { return nil, xerrors.Wrap(err, "failed to save certificate file") } - d.logger.Appendt("certificate file saved") + d.logger.Logt("certificate file saved") case OUTPUT_FORMAT_JKS: jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) @@ -115,13 +115,13 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s return nil, xerrors.Wrap(err, "failed to transform certificate to JKS") } - d.logger.Appendt("certificate transformed to JKS") + d.logger.Logt("certificate transformed to JKS") if err := fs.WriteFile(d.config.OutputCertPath, jksData); err != nil { return nil, xerrors.Wrap(err, "failed to save certificate file") } - d.logger.Appendt("certificate file uploaded") + d.logger.Logt("certificate file uploaded") default: return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat) @@ -134,7 +134,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr) } - d.logger.Appendt("post-command executed", stdout) + d.logger.Logt("post-command executed", stdout) } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go index 40150046..7871bf17 100644 --- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -70,7 +70,7 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPe return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 domain := strings.TrimPrefix(d.config.Domain, "*") @@ -82,7 +82,7 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPe return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'") } - d.logger.Appendt("已获取域名信息", getDomainInfoResp) + d.logger.Logt("已获取域名信息", getDomainInfoResp) // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS // REF: https://developer.qiniu.com/fusion/4246/the-domain-name @@ -92,14 +92,14 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPe return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") } - d.logger.Appendt("已修改域名证书", modifyDomainHttpsConfResp) + d.logger.Logt("已修改域名证书", modifyDomainHttpsConfResp) } else { enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true) if err != nil { return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'") } - d.logger.Appendt("已将域名升级为 HTTPS", enableDomainHttpsResp) + d.logger.Logt("已将域名升级为 HTTPS", enableDomainHttpsResp) } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 147662f8..27e3fc1a 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -96,7 +96,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str } defer client.Close() - d.logger.Appendt("SSH connected") + d.logger.Logt("SSH connected") // 执行前置命令 if d.config.PreCommand != "" { @@ -105,7 +105,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str return nil, xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr) } - d.logger.Appendt("SSH pre-command executed", stdout) + d.logger.Logt("SSH pre-command executed", stdout) } // 上传证书和私钥文件 @@ -115,13 +115,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded") + d.logger.Logt("certificate file uploaded") if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil { return nil, xerrors.Wrap(err, "failed to upload private key file") } - d.logger.Appendt("private key file uploaded") + d.logger.Logt("private key file uploaded") case OUTPUT_FORMAT_PFX: pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword) @@ -129,13 +129,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str return nil, xerrors.Wrap(err, "failed to transform certificate to PFX") } - d.logger.Appendt("certificate transformed to PFX") + d.logger.Logt("certificate transformed to PFX") if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded") + d.logger.Logt("certificate file uploaded") case OUTPUT_FORMAT_JKS: jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass) @@ -143,13 +143,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str return nil, xerrors.Wrap(err, "failed to transform certificate to JKS") } - d.logger.Appendt("certificate transformed to JKS") + d.logger.Logt("certificate transformed to JKS") if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded") + d.logger.Logt("certificate file uploaded") default: return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat) @@ -162,7 +162,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr) } - d.logger.Appendt("SSH post-command executed", stdout) + d.logger.Logt("SSH post-command executed", stdout) } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go index 23f9ad99..68475257 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -81,7 +81,7 @@ func (d *TencentCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 获取待部署的 CDN 实例 // 如果是泛域名,根据证书匹配 CDN 实例 @@ -114,7 +114,7 @@ func (d *TencentCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pr } if len(instanceIds) == 0 { - d.logger.Appendt("已部署过或没有要部署的 CDN 实例") + d.logger.Logt("已部署过或没有要部署的 CDN 实例") } else { // 证书部署到 CDN 实例 // REF: https://cloud.tencent.com/document/product/400/91667 @@ -128,7 +128,7 @@ func (d *TencentCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") } - d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index f012f660..990e266d 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -91,7 +91,7 @@ func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 根据部署资源类型决定部署方式 switch d.config.ResourceType { @@ -148,7 +148,7 @@ func (d *TencentCloudCLBDeployer) deployToInstanceUseSsl(ctx context.Context, cl return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") } - d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) return nil } @@ -179,7 +179,7 @@ func (d *TencentCloudCLBDeployer) deployToLoadbalancer(ctx context.Context, clou } } - d.logger.Appendt("已查询到负载均衡器下的监听器", listenerIds) + d.logger.Logt("已查询到负载均衡器下的监听器", listenerIds) // 批量更新监听器证书 if len(listenerIds) > 0 { @@ -241,7 +241,7 @@ func (d *TencentCloudCLBDeployer) deployToRuleDomain(ctx context.Context, cloudC return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'") } - d.logger.Appendt("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response) + d.logger.Logt("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response) return nil } @@ -260,7 +260,7 @@ func (d *TencentCloudCLBDeployer) modifyListenerCertificate(ctx context.Context, return errors.New("listener not found") } - d.logger.Appendt("已查询到监听器属性", describeListenersResp.Response) + d.logger.Logt("已查询到监听器属性", describeListenersResp.Response) // 修改监听器属性 // REF: https://cloud.tencent.com/document/product/214/30681 @@ -279,7 +279,7 @@ func (d *TencentCloudCLBDeployer) modifyListenerCertificate(ctx context.Context, return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyListener'") } - d.logger.Appendt("已修改监听器属性", modifyListenerResp.Response) + d.logger.Logt("已修改监听器属性", modifyListenerResp.Response) return nil } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go index a7382a11..88f2ad60 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -85,7 +85,7 @@ func (d *TencentCloudCOSDeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 证书部署到 COS 实例 // REF: https://cloud.tencent.com/document/product/400/91667 @@ -99,7 +99,7 @@ func (d *TencentCloudCOSDeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") } - d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go index 39175b9d..b5be93ba 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -80,7 +80,7 @@ func (d *TencentCloudECDNDeployer) Deploy(ctx context.Context, certPem string, p return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 获取待部署的 CDN 实例 // 如果是泛域名,根据证书匹配 CDN 实例 @@ -97,7 +97,7 @@ func (d *TencentCloudECDNDeployer) Deploy(ctx context.Context, certPem string, p } if len(instanceIds) == 0 { - d.logger.Appendt("已部署过或没有要部署的 ECDN 实例") + d.logger.Logt("已部署过或没有要部署的 ECDN 实例") } else { // 证书部署到 ECDN 实例 // REF: https://cloud.tencent.com/document/product/400/91667 @@ -111,7 +111,7 @@ func (d *TencentCloudECDNDeployer) Deploy(ctx context.Context, certPem string, p return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") } - d.logger.Appendt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go index 7fde103e..96c301ab 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-teo/tencentcloud_teo.go @@ -85,7 +85,7 @@ func (d *TencentCloudTEODeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) // 配置域名证书 // REF: https://cloud.tencent.com/document/product/1552/80764 @@ -99,7 +99,7 @@ func (d *TencentCloudTEODeployer) Deploy(ctx context.Context, certPem string, pr return nil, xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'") } - d.logger.Appendt("已配置域名证书", modifyHostsCertificateResp.Response) + d.logger.Logt("已配置域名证书", modifyHostsCertificateResp.Response) return &deployer.DeployResult{}, nil } diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go index 0531b306..11906474 100644 --- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -72,7 +72,7 @@ func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, priv return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) domains := make([]string, 0) if strings.HasPrefix(d.config.Domain, "*.") { @@ -123,7 +123,7 @@ func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, priv if err != nil { errs = append(errs, err) } else { - d.logger.Appendt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) + d.logger.Logt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp) } } diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go index 552ba294..f492cf89 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -73,7 +73,7 @@ func (d *VolcEngineLiveDeployer) Deploy(ctx context.Context, certPem string, pri return nil, xerrors.Wrap(err, "failed to upload certificate file") } - d.logger.Appendt("certificate file uploaded", upres) + d.logger.Logt("certificate file uploaded", upres) domains := make([]string, 0) if strings.HasPrefix(d.config.Domain, "*.") { @@ -133,7 +133,7 @@ func (d *VolcEngineLiveDeployer) Deploy(ctx context.Context, certPem string, pri if err != nil { errs = append(errs, err) } else { - d.logger.Appendt(fmt.Sprintf("已绑定证书到域名 %s", domain), bindCertResp) + d.logger.Logt(fmt.Sprintf("已绑定证书到域名 %s", domain), bindCertResp) } } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index d669a9e4..76fcaaf8 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -75,7 +75,7 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem return nil, xerrors.Wrap(err, "failed to send webhook request") } - d.logger.Appendt("Webhook Response", string(resp)) + d.logger.Logt("Webhook Response", string(resp)) return &deployer.DeployResult{ DeploymentData: map[string]any{ From 4916757d59b40453b991ef62555bb52f503cacbf Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 21 Nov 2024 11:23:15 +0800 Subject: [PATCH 16/17] feat: add `Deployer` factory --- internal/applicant/volcengine.go | 2 +- internal/deployer/deployer.go | 12 +- internal/deployer/factory.go | 363 ++++++++++++++++++++++++++- internal/deployer/volcengine_cdn.go | 2 +- internal/deployer/volcengine_live.go | 2 +- internal/domain/access.go | 2 +- 6 files changed, 361 insertions(+), 22 deletions(-) diff --git a/internal/applicant/volcengine.go b/internal/applicant/volcengine.go index 7437bfda..3ab91741 100644 --- a/internal/applicant/volcengine.go +++ b/internal/applicant/volcengine.go @@ -20,7 +20,7 @@ func NewVolcengine(option *ApplyOption) Applicant { } func (a *volcengine) Apply() (*Certificate, error) { - access := &domain.VolcengineAccess{} + access := &domain.VolcEngineAccess{} json.Unmarshal([]byte(a.option.Access), access) os.Setenv("VOLC_ACCESSKEY", access.AccessKeyId) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 9450ba90..09ecd7bb 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -28,15 +28,15 @@ const ( targetHuaweiCloudCDN = "huaweicloud-cdn" targetHuaweiCloudELB = "huaweicloud-elb" targetBaiduCloudCDN = "baiducloud-cdn" + targetVolcEngineLive = "volcengine-live" + targetVolcEngineCDN = "volcengine-cdn" + targetBytePlusCDN = "byteplus-cdn" targetQiniuCdn = "qiniu-cdn" targetDogeCloudCdn = "dogecloud-cdn" targetLocal = "local" targetSSH = "ssh" targetWebhook = "webhook" targetK8sSecret = "k8s-secret" - targetVolcengineLive = "volcengine-live" - targetVolcengineCDN = "volcengine-cdn" - targetByteplusCDN = "byteplus-cdn" ) type DeployerOption struct { @@ -147,11 +147,11 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep return NewWebhookDeployer(option) case targetK8sSecret: return NewK8sSecretDeployer(option) - case targetVolcengineLive: + case targetVolcEngineLive: return NewVolcengineLiveDeployer(option) - case targetVolcengineCDN: + case targetVolcEngineCDN: return NewVolcengineCDNDeployer(option) - case targetByteplusCDN: + case targetBytePlusCDN: return NewByteplusCDNDeployer(option) } return nil, errors.New("unsupported deploy target") diff --git a/internal/deployer/factory.go b/internal/deployer/factory.go index ed18a7a5..d150576f 100644 --- a/internal/deployer/factory.go +++ b/internal/deployer/factory.go @@ -3,10 +3,33 @@ package deployer import ( "encoding/json" "fmt" + "strconv" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/deployer" + providerAliyunAlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" + providerAliyunCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" + providerAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" + providerAliyunDcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" + providerAliyunNlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" providerAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" + providerBaiduCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" + providerBytePlusCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" + providerDogeCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" + providerHuaweiCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn" + providerHuaweiCloudElb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb" + providerK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" + providerLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" + providerQiniuCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" + providerSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" + providerTencentCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" + providerTencentCloudClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb" + providerTencentCloudCos "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos" + providerTencentCloudEcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" + providerTencentCloudTeo "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-teo" + providerVolcEngineCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" + providerVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" + providerWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" "github.com/usual2970/certimate/internal/pkg/utils/maps" ) @@ -15,20 +38,336 @@ func createDeployer(target string, accessConfig string, deployConfig map[string] logger := deployer.NewDefaultLogger() switch target { - case targetAliyunOSS: - access := &domain.AliyunAccess{} - if err := json.Unmarshal([]byte(accessConfig), access); err != nil { - return nil, nil, err + case targetAliyunALB, targetAliyunCDN, targetAliyunCLB, targetAliyunDCDN, targetAliyunNLB, targetAliyunOSS: + { + access := &domain.AliyunAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + switch target { + case targetAliyunALB: + deployer, err := providerAliyunAlb.NewWithLogger(&providerAliyunAlb.AliyunALBDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(deployConfig, "region"), + ResourceType: providerAliyunAlb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + }, logger) + return deployer, logger, err + + case targetAliyunCDN: + deployer, err := providerAliyunCdn.NewWithLogger(&providerAliyunCdn.AliyunCDNDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetAliyunCLB: + deployer, err := providerAliyunClb.NewWithLogger(&providerAliyunClb.AliyunCLBDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(deployConfig, "region"), + ResourceType: providerAliyunClb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), + ListenerPort: maps.GetValueAsInt32(deployConfig, "listenerPort"), + }, logger) + return deployer, logger, err + + case targetAliyunDCDN: + deployer, err := providerAliyunDcdn.NewWithLogger(&providerAliyunDcdn.AliyunDCDNDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetAliyunNLB: + deployer, err := providerAliyunNlb.NewWithLogger(&providerAliyunNlb.AliyunNLBDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(deployConfig, "region"), + ResourceType: providerAliyunNlb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + }, logger) + return deployer, logger, err + + case targetAliyunOSS: + deployer, err := providerAliyunOss.NewWithLogger(&providerAliyunOss.AliyunOSSDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(deployConfig, "region"), + Bucket: maps.GetValueAsString(deployConfig, "bucket"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } } - deployer, err := providerAliyunOss.NewWithLogger(&providerAliyunOss.AliyunOSSDeployerConfig{ - AccessKeyId: access.AccessKeyId, - AccessKeySecret: access.AccessKeySecret, - Region: maps.GetValueAsString(deployConfig, "region"), - Bucket: maps.GetValueAsString(deployConfig, "bucket"), - Domain: maps.GetValueAsString(deployConfig, "domain"), - }, logger) - return deployer, logger, err + case targetBaiduCloudCDN: + { + access := &domain.BaiduCloudAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerBaiduCloudCdn.NewWithLogger(&providerBaiduCloudCdn.BaiduCloudCDNDeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + } + + case targetBytePlusCDN: + { + access := &domain.ByteplusAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerBytePlusCdn.NewWithLogger(&providerBytePlusCdn.BytePlusCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + } + + case targetDogeCloudCdn: + { + access := &domain.DogeCloudAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerDogeCdn.NewWithLogger(&providerDogeCdn.DogeCloudCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + } + + case targetHuaweiCloudCDN, targetHuaweiCloudELB: + { + access := &domain.HuaweiCloudAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + switch target { + case targetHuaweiCloudCDN: + deployer, err := providerHuaweiCloudCdn.NewWithLogger(&providerHuaweiCloudCdn.HuaweiCloudCDNDeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Region: maps.GetValueAsString(deployConfig, "region"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetHuaweiCloudELB: + deployer, err := providerHuaweiCloudElb.NewWithLogger(&providerHuaweiCloudElb.HuaweiCloudELBDeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Region: maps.GetValueAsString(deployConfig, "region"), + ResourceType: providerHuaweiCloudElb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), + CertificateId: maps.GetValueAsString(deployConfig, "certificateId"), + LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + }, logger) + return deployer, logger, err + + default: + break + } + } + + case targetLocal: + { + deployer, err := providerLocal.NewWithLogger(&providerLocal.LocalDeployerConfig{ + ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(deployConfig, "shellEnv")), + PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), + PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), + OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "outputFormat", "PEM")), + OutputCertPath: maps.GetValueAsString(deployConfig, "outputCertPath"), + OutputKeyPath: maps.GetValueAsString(deployConfig, "outputKeyPath"), + PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), + JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), + JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"), + JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"), + }, logger) + return deployer, logger, err + } + + case targetK8sSecret: + { + access := &domain.KubernetesAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerK8sSecret.NewWithLogger(&providerK8sSecret.K8sSecretDeployerConfig{ + KubeConfig: access.KubeConfig, + Namespace: maps.GetValueOrDefaultAsString(deployConfig, "namespace", "default"), + SecretName: maps.GetValueAsString(deployConfig, "secretName"), + SecretDataKeyForCrt: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForKey", "tls.key"), + }, logger) + return deployer, logger, err + } + + case targetQiniuCdn: + { + access := &domain.QiniuAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerQiniuCdn.NewWithLogger(&providerQiniuCdn.QiniuCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + } + + case targetSSH: + { + access := &domain.SSHAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + sshPort, _ := strconv.ParseInt(access.Port, 10, 32) + deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{ + SshHost: access.Host, + SshPort: int32(sshPort), + SshUsername: access.Username, + SshPassword: access.Password, + SshKey: access.Key, + SshKeyPassphrase: access.KeyPassphrase, + PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), + PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), + OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "outputFormat", "PEM")), + OutputCertPath: maps.GetValueAsString(deployConfig, "outputCertPath"), + OutputKeyPath: maps.GetValueAsString(deployConfig, "outputKeyPath"), + PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), + JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), + JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"), + JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"), + }, logger) + return deployer, logger, err + } + + case targetTencentCDN, targetTencentCLB, targetTencentCOS, targetTencentECDN, targetTencentTEO: + { + access := &domain.TencentAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + switch target { + case targetTencentCDN: + deployer, err := providerTencentCloudCdn.NewWithLogger(&providerTencentCloudCdn.TencentCloudCDNDeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetTencentCLB: + deployer, err := providerTencentCloudClb.NewWithLogger(&providerTencentCloudClb.TencentCloudCLBDeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Region: maps.GetValueAsString(deployConfig, "region"), + ResourceType: providerTencentCloudClb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")), + LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"), + ListenerId: maps.GetValueAsString(deployConfig, "listenerId"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetTencentCOS: + deployer, err := providerTencentCloudCos.NewWithLogger(&providerTencentCloudCos.TencentCloudCOSDeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Region: maps.GetValueAsString(deployConfig, "region"), + Bucket: maps.GetValueAsString(deployConfig, "bucket"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetTencentECDN: + deployer, err := providerTencentCloudEcdn.NewWithLogger(&providerTencentCloudEcdn.TencentCloudECDNDeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetTencentTEO: + deployer, err := providerTencentCloudTeo.NewWithLogger(&providerTencentCloudTeo.TencentCloudTEODeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + ZoneId: maps.GetValueAsString(deployConfig, "zoneId"), + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } + } + + case targetVolcEngineCDN, targetVolcEngineLive: + { + access := &domain.VolcEngineAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + switch target { + case targetVolcEngineCDN: + deployer, err := providerVolcEngineCdn.NewWithLogger(&providerVolcEngineCdn.VolcEngineCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + case targetVolcEngineLive: + deployer, err := providerVolcEngineLive.NewWithLogger(&providerVolcEngineLive.VolcEngineLiveDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(deployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } + } + + case targetWebhook: + { + access := &domain.WebhookAccess{} + if err := json.Unmarshal([]byte(accessConfig), access); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) + } + + deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{ + Url: access.Url, + Variables: nil, // TODO: 尚未实现 + }, logger) + return deployer, logger, err + } } return nil, nil, fmt.Errorf("unsupported deployer target: %s", target) diff --git a/internal/deployer/volcengine_cdn.go b/internal/deployer/volcengine_cdn.go index c1665993..6ba8a23d 100644 --- a/internal/deployer/volcengine_cdn.go +++ b/internal/deployer/volcengine_cdn.go @@ -22,7 +22,7 @@ type VolcengineCDNDeployer struct { } func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) { - access := &domain.VolcengineAccess{} + access := &domain.VolcEngineAccess{} if err := json.Unmarshal([]byte(option.Access), access); err != nil { return nil, xerrors.Wrap(err, "failed to get access") } diff --git a/internal/deployer/volcengine_live.go b/internal/deployer/volcengine_live.go index 6d038ac6..1795d79f 100644 --- a/internal/deployer/volcengine_live.go +++ b/internal/deployer/volcengine_live.go @@ -24,7 +24,7 @@ type VolcengineLiveDeployer struct { } func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) { - access := &domain.VolcengineAccess{} + access := &domain.VolcEngineAccess{} if err := json.Unmarshal([]byte(option.Access), access); err != nil { return nil, xerrors.Wrap(err, "failed to get access") } diff --git a/internal/domain/access.go b/internal/domain/access.go index b1302ee0..bad58900 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -61,7 +61,7 @@ type PdnsAccess struct { ApiKey string `json:"apiKey"` } -type VolcengineAccess struct { +type VolcEngineAccess struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` From a842b6b9252043bd3354292cbab6dd9bbb9c53b2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 21 Nov 2024 20:23:01 +0800 Subject: [PATCH 17/17] fix: illegal arguments --- internal/deployer/factory.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/deployer/factory.go b/internal/deployer/factory.go index d150576f..fee6f628 100644 --- a/internal/deployer/factory.go +++ b/internal/deployer/factory.go @@ -195,9 +195,9 @@ func createDeployer(target string, accessConfig string, deployConfig map[string] ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(deployConfig, "shellEnv")), PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), - OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "outputFormat", "PEM")), - OutputCertPath: maps.GetValueAsString(deployConfig, "outputCertPath"), - OutputKeyPath: maps.GetValueAsString(deployConfig, "outputKeyPath"), + OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")), + OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"), + OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"), PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"), @@ -255,9 +255,9 @@ func createDeployer(target string, accessConfig string, deployConfig map[string] SshKeyPassphrase: access.KeyPassphrase, PreCommand: maps.GetValueAsString(deployConfig, "preCommand"), PostCommand: maps.GetValueAsString(deployConfig, "postCommand"), - OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "outputFormat", "PEM")), - OutputCertPath: maps.GetValueAsString(deployConfig, "outputCertPath"), - OutputKeyPath: maps.GetValueAsString(deployConfig, "outputKeyPath"), + OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")), + OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"), + OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"), PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"), JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"), JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"),