From bde51d8d387333b1b5d6b92500023e4210ff2fd9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 20 Nov 2024 22:58:01 +0800 Subject: [PATCH] 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 {