diff --git a/internal/certmgmt/deployers/sp_ksyun_slb.go b/internal/certmgmt/deployers/sp_ksyun_slb.go new file mode 100644 index 000000000..a95a7d971 --- /dev/null +++ b/internal/certmgmt/deployers/sp_ksyun_slb.go @@ -0,0 +1,27 @@ +package deployers + +import ( + "fmt" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/deployer" + ksyunslb "github.com/certimate-go/certimate/pkg/core/deployer/providers/ksyun-slb" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + Registries.MustRegister(domain.DeploymentProviderTypeKsyunSLB, func(options *ProviderFactoryOptions) (deployer.Provider, error) { + credentials := domain.AccessConfigForKsyun{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := ksyunslb.NewDeployer(&ksyunslb.DeployerConfig{ + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + ResourceType: xmaps.GetString(options.ProviderExtendedConfig, "resourceType"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + }) + return provider, err + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c982504e7..eded8d7e3 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -329,6 +329,7 @@ const ( DeploymentProviderTypeKong = DeploymentProviderType(AccessProviderTypeKong) DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret") DeploymentProviderTypeKsyunCDN = DeploymentProviderType(AccessProviderTypeKsyun + "-cdn") + DeploymentProviderTypeKsyunSLB = DeploymentProviderType(AccessProviderTypeKsyun + "-slb") DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal) DeploymentProviderTypeMohuaMVH = DeploymentProviderType(AccessProviderTypeMohua + "-mvh") diff --git a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go index ddcb6f8af..cc276571b 100644 --- a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -181,11 +181,10 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM "ServerCertificate": certPEM, "PrivateKey": privkeyPEM, } - setCertificateReq, setCertificateOutput := d.sdkClient.SetCertificatePostRequest(&setCertificateInput) - setCertificateErr := setCertificateReq.Send() + setCertificateOutput, err := d.sdkClient.SetCertificatePostWithContext(ctx, &setCertificateInput) d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput)) - if setCertificateErr != nil { - return fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", setCertificateErr) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", err) } return nil @@ -209,11 +208,10 @@ func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) { "PageNumber": getCdnDomainsPageNumber, "PageSize": getCdnDomainsPageSize, } - getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) - getCdnDomainsErr := getCdnDomainsReq.Send() + getCdnDomainsOutput, err := d.sdkClient.GetCdnDomainsPostWithContext(ctx, &getCdnDomainsInput) d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) - if getCdnDomainsErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", err) } type GetCdnDomainsResponse struct { @@ -268,11 +266,10 @@ func (d *Deployer) findDomainIdByDomain(ctx context.Context, domain string) (str "DomainName": domain, "FuzzyMatch": "off", } - getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) - getCdnDomainsErr := getCdnDomainsReq.Send() + getCdnDomainsOutput, err := d.sdkClient.GetCdnDomainsPostWithContext(ctx, &getCdnDomainsInput) d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) - if getCdnDomainsErr != nil { - return "", fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) + if err != nil { + return "", fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", err) } type GetCdnDomainsResponse struct { @@ -327,11 +324,10 @@ func (d *Deployer) updateDomainCertificate(ctx context.Context, domain string, c "ServerCertificate": certPEM, "PrivateKey": privkeyPEM, } - configCertificateReq, configCertificateOutput := d.sdkClient.ConfigCertificatePostRequest(&configCertificateInput) - configCertificateErr := configCertificateReq.Send() + configCertificateOutput, err := d.sdkClient.ConfigCertificatePostWithContext(ctx, &configCertificateInput) d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput)) - if configCertificateErr != nil { - return fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", configCertificateErr) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", err) } return nil diff --git a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn_test.go b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn_test.go index cbcb15f28..6f7397426 100644 --- a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn_test.go +++ b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn_test.go @@ -45,7 +45,7 @@ Shell command to run this test: func TestDeploy(t *testing.T) { flag.Parse() - t.Run("Deploy", func(t *testing.T) { + t.Run("Deploy_ToDomain", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), @@ -53,15 +53,46 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), fmt.Sprintf("DOMAIN: %v", fDomain), - fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), }, "\n")) provider, err := provider.NewDeployer(&provider.DeployerConfig{ AccessKeyId: fAccessKeyId, SecretAccessKey: fSecretAccessKey, + ResourceType: provider.RESOURCE_TYPE_DOMAIN, DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, Domain: fDomain, - CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := provider.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + 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("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + provider, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/deployer/providers/ksyun-slb/consts.go b/pkg/core/deployer/providers/ksyun-slb/consts.go new file mode 100644 index 000000000..ba7d7d8de --- /dev/null +++ b/pkg/core/deployer/providers/ksyun-slb/consts.go @@ -0,0 +1,6 @@ +package ksyunslb + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = "certificate" +) diff --git a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go new file mode 100644 index 000000000..1322c7518 --- /dev/null +++ b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go @@ -0,0 +1,102 @@ +package ksyunslb + +import ( + "context" + "errors" + "fmt" + "log/slog" + "time" + + "github.com/KscSDK/ksc-sdk-go/ksc" + ksckcm "github.com/KscSDK/ksc-sdk-go/service/kcm" + + "github.com/certimate-go/certimate/pkg/core/deployer" +) + +type DeployerConfig struct { + // 金山云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 金山云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // 部署资源类型。 + ResourceType string `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId string `json:"certificateId,omitempty"` +} + +type Deployer struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *ksckcm.Kcm +} + +var _ deployer.Provider = (*Deployer)(nil) + +func NewDeployer(config *DeployerConfig) (*Deployer, error) { + if config == nil { + return nil, errors.New("the configuration of the deployer provider is nil") + } + + client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey) + if err != nil { + return nil, fmt.Errorf("could not create client: %w", err) + } + + return &Deployer{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *Deployer) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } +} + +func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 更新证书信息 + // https://docs.ksyun.com/documents/2121 + modifyCertificateInput := map[string]any{ + "CertificateId": d.config.CertificateId, + "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), + "PublicKey": certPEM, + "PrivateKey": privkeyPEM, + } + modifyCertificateOutput, err := d.sdkClient.ModifyCertificateWithContext(ctx, &modifyCertificateInput) + d.logger.Debug("sdk request 'kcm.ModifyCertificate'", slog.Any("request", modifyCertificateInput), slog.Any("response", modifyCertificateOutput)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'kcm.ModifyCertificate': %w", err) + } + + return nil +} + +func createSDKClient(accessKeyId, secretAccessKey string) (*ksckcm.Kcm, error) { + region := "cn-beijing-6" + client := ksckcm.SdkNew(ksc.NewClient(accessKeyId, secretAccessKey), &ksc.Config{Region: ®ion}) + return client, nil +} diff --git a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go new file mode 100644 index 000000000..4bdc792f4 --- /dev/null +++ b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go @@ -0,0 +1,76 @@ +package ksyunslb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/deployer/providers/ksyun-slb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fCertificateId string +) + +func init() { + argsPrefix := "KSYUNSLB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./ksyun_slb_test.go -args \ + --KSYUNSLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --KSYUNSLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --KSYUNSLB_ACCESSKEYID="your-access-key-id" \ + --KSYUNSLB_SECRETACCESSKEY="your-secret-access-key" \ + --KSYUNSLB_CERTIFICATEID="your-certificate-id" +*/ +func TestDeploy(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("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + provider, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := provider.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx index 23050f4b7..a4c40bbd6 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx @@ -65,6 +65,7 @@ import BizDeployNodeConfigFieldsProviderJDCloudVOD from "./BizDeployNodeConfigFi import BizDeployNodeConfigFieldsProviderJDCloudWAF from "./BizDeployNodeConfigFieldsProviderJDCloudWAF"; import BizDeployNodeConfigFieldsProviderKong from "./BizDeployNodeConfigFieldsProviderKong"; import BizDeployNodeConfigFieldsProviderKsyunCDN from "./BizDeployNodeConfigFieldsProviderKsyunCDN"; +import BizDeployNodeConfigFieldsProviderKsyunSLB from "./BizDeployNodeConfigFieldsProviderKsyunSLB"; import BizDeployNodeConfigFieldsProviderKubernetesSecret from "./BizDeployNodeConfigFieldsProviderKubernetesSecret"; import BizDeployNodeConfigFieldsProviderLeCDN from "./BizDeployNodeConfigFieldsProviderLeCDN"; import BizDeployNodeConfigFieldsProviderLocal from "./BizDeployNodeConfigFieldsProviderLocal"; @@ -189,6 +190,7 @@ const providerComponentMap: Partial { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal([RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.shared_resource_type.placeholder")), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + if (!values.certificateId) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ksyun_slb_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFieldsProviderKsyunSLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 89fed376c..8dda588ad 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -623,6 +623,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ KONG: `${ACCESS_PROVIDERS.KONG}`, KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`, KSYUN_CDN: `${ACCESS_PROVIDERS.KSYUN}-cdn`, + KSYUN_SLB: `${ACCESS_PROVIDERS.KSYUN}-slb`, LECDN: `${ACCESS_PROVIDERS.LECDN}`, LOCAL: `${ACCESS_PROVIDERS.LOCAL}`, MOHUA_MVH: `${ACCESS_PROVIDERS.MOHUA}-mvh`, @@ -784,6 +785,7 @@ export const deploymentProvidersMap: Maphttps://cdn.console.ksyun.com/", + "workflow_node.deploy.form.ksyun_slb_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.ksyun_slb_certificate_id.label": "Kingsoft Cloud SLB certificate ID", + "workflow_node.deploy.form.ksyun_slb_certificate_id.placeholder": "Please enter Kingsoft Cloud SLB certificate ID", + "workflow_node.deploy.form.ksyun_slb_certificate_id.tooltip": "For more information, see https://network.console.ksyun.com/index.html#!/slb/slbIndex", "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN certificate ID", "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "Please enter LeCDN certificate ID", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index f7f31c70a..ec7241d92 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -133,6 +133,7 @@ "provider.kubernetes_secret": "Kubernetes - Secret", "provider.ksyun": "金山云", "provider.ksyun_cdn": "金山云 - 内容分发网络 CDN", + "provider.ksyun_slb": "金山云 - 负载均衡 SLB", "provider.larkbot": "飞书群机器人", "provider.lecdn": "LeCDN", "provider.letsencrypt": "Let's Encrypt", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a3eb5eb8c..c3729bad0 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -712,6 +712,10 @@ "workflow_node.deploy.form.ksyun_cdn_certificate_id.label": "金山云 CDN 证书 ID", "workflow_node.deploy.form.ksyun_cdn_certificate_id.placeholder": "请输入金山云 CDN 证书 ID", "workflow_node.deploy.form.ksyun_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.ksyun.com/", + "workflow_node.deploy.form.ksyun_slb_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.ksyun_slb_certificate_id.label": "金山云 SLB 证书 ID", + "workflow_node.deploy.form.ksyun_slb_certificate_id.placeholder": "请输入金山云 SLB 证书 ID", + "workflow_node.deploy.form.ksyun_slb_certificate_id.tooltip": "这是什么?请参阅 https://network.console.ksyun.com/index.html#!/slb/slbIndex", "workflow_node.deploy.form.lecdn_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.lecdn_certificate_id.label": "LeCDN 证书 ID", "workflow_node.deploy.form.lecdn_certificate_id.placeholder": "请输入 LeCDN 证书 ID",