diff --git a/internal/deployer/factory.go b/internal/deployer/factory.go index fd617d6d..0338a0df 100644 --- a/internal/deployer/factory.go +++ b/internal/deployer/factory.go @@ -367,23 +367,9 @@ func createDeployer(target string, accessConfig string, deployConfig map[string] return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err) } - variables := make(map[string]string) - if deployConfig != nil { - value, ok := deployConfig["variables"] - if ok { - kvs := make([]domain.KV, 0) - bts, _ := json.Marshal(value) - if err := json.Unmarshal(bts, &kvs); err == nil { - for _, kv := range kvs { - variables[kv.Key] = kv.Value - } - } - } - } - deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{ - Url: access.Url, - Variables: variables, + WebhookUrl: access.Url, + WebhookData: maps.GetValueAsString(deployConfig, "webhookData"), }, logger) return deployer, logger, err } diff --git a/internal/domain/domains.go b/internal/domain/domains.go index f3521c5c..287046ed 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -17,9 +17,3 @@ type DeployConfig struct { Type string `json:"type"` Config map[string]any `json:"config"` } - -// Deprecated: TODO: 即将废弃 -type KV struct { - Key string `json:"key"` - Value string `json:"value"` -} diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 00cf1fca..6d86434c 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -209,8 +209,8 @@ func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) if err != nil { return "", "", err } - defer session.Close() + var stdoutBuf bytes.Buffer session.Stdout = &stdoutBuf var stderrBuf bytes.Buffer diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index b60b0e87..e9ed6f11 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -19,9 +19,9 @@ import ( type WebhookDeployerConfig struct { // Webhook URL。 - Url string `json:"url"` - // Webhook 变量字典。 - Variables map[string]string `json:"variables,omitempty"` + WebhookUrl string `json:"webhookUrl"` + // Webhook 回调数据(JSON 格式)。 + WebhookData string `json:"webhookData,omitempty"` } type WebhookDeployer struct { @@ -67,19 +67,25 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem return nil, xerrors.Wrap(err, "failed to parse x509") } - // TODO: 自定义回调数据 - reqBody, _ := json.Marshal(&webhookData{ - SubjectAltNames: strings.Join(certX509.DNSNames, ","), - Certificate: certPem, - PrivateKey: privkeyPem, - Variables: d.config.Variables, - }) - resp, err := d.httpClient.Post(d.config.Url, bytes.NewReader(reqBody), map[string][]string{"Content-Type": {"application/json"}}) + var webhookData interface{} + err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData) + if err != nil { + return nil, xerrors.Wrap(err, "failed to unmarshall webhook data") + } + + replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) + replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) + replaceJsonValueRecursively(webhookData, "${SUBJECT_ALT_NAMES}", strings.Join(certX509.DNSNames, ";")) + replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPem) + replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPem) + + reqBody, _ := json.Marshal(&webhookData) + resp, err := d.httpClient.Post(d.config.WebhookUrl, bytes.NewReader(reqBody), map[string][]string{"Content-Type": {"application/json"}}) if err != nil { return nil, xerrors.Wrap(err, "failed to send webhook request") } - defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, xerrors.Wrap(err, "failed to read response body") @@ -93,3 +99,19 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem }, }, nil } + +func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { + switch v := data.(type) { + case map[string]interface{}: + for k, val := range v { + v[k] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case []interface{}: + for i, val := range v { + v[i] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case string: + return strings.ReplaceAll(v, oldStr, newStr) + } + return data +} diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index a3b36dda..ddb719c4 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -14,7 +14,8 @@ import ( var ( fInputCertPath string fInputKeyPath string - fUrl string + fWebhookUrl string + fWebhookData string ) func init() { @@ -22,7 +23,8 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") - flag.StringVar(&fUrl, argsPrefix+"URL", "", "") + flag.StringVar(&fWebhookUrl, argsPrefix+"URL", "", "") + flag.StringVar(&fWebhookData, argsPrefix+"DATA", "", "") } /* @@ -31,7 +33,8 @@ Shell command to run this test: go test -v webhook_test.go -args \ --CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ - --CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" + --CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" \ + --CERTIMATE_DEPLOYER_WEBHOOK_DATA="{\"certificate\":\"${Certificate}\",\"privateKey\":\"${PrivateKey}\"}" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -41,11 +44,13 @@ func TestDeploy(t *testing.T) { "args:", fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), - fmt.Sprintf("URL: %v", fUrl), + fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl), + fmt.Sprintf("WEBHOOKDATA: %v", fWebhookData), }, "\n")) deployer, err := provider.New(&provider.WebhookDeployerConfig{ - Url: fUrl, + WebhookUrl: fWebhookUrl, + WebhookData: fWebhookData, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/node/DeployNodeFormWebhookFields.tsx b/ui/src/components/workflow/node/DeployNodeFormWebhookFields.tsx index d56b94b8..550520bb 100644 --- a/ui/src/components/workflow/node/DeployNodeFormWebhookFields.tsx +++ b/ui/src/components/workflow/node/DeployNodeFormWebhookFields.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, Input } from "antd"; +import { Alert, Button, Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -14,11 +14,23 @@ const DeployNodeFormWebhookFields = () => { } catch { return false; } - }, t("workflow_node.deploy.form.webhook_data.placeholder")), + }, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")), }); const formRule = createSchemaFieldRule(formSchema); const formInst = Form.useFormInstance(); + const initialValues: Partial> = { + webhookData: JSON.stringify( + { + name: "${DOMAINS}", + cert: "${CERTIFICATE}", + privkey: "${PRIVATE_KEY}", + }, + null, + 2 + ), + }; + const handleWebhookDataBlur = (e: React.FocusEvent) => { const value = e.target.value; try { @@ -29,19 +41,34 @@ const DeployNodeFormWebhookFields = () => { } }; + const handlePresetDataClick = () => { + formInst.setFieldValue("webhookData", initialValues.webhookData); + }; + return ( <> - } - > - + + + + + + + + + } /> ); diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index c1d8a213..7778369c 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -19,10 +19,10 @@ "settings.notification.template.card.title": "Template", "settings.notification.template.form.subject.label": "Subject", "settings.notification.template.form.subject.placeholder": "Please enter notification subject", - "settings.notification.template.form.subject.extra": "Optional variables (${COUNT}: number of expiring soon)", + "settings.notification.template.form.subject.extra": "Supported variables (${COUNT}: number of expiring soon)", "settings.notification.template.form.message.label": "Message", "settings.notification.template.form.message.placeholder": "Please enter notification message", - "settings.notification.template.form.message.extra": "Optional variables (${COUNT}: number of expiring soon. ${DOMAINS}: Domain list)", + "settings.notification.template.form.message.extra": "Supported variables (${COUNT}: number of expiring soon. ${DOMAINS}: Domain list)", "settings.notification.channels.card.title": "Channels", "settings.notification.channel.enabled.on": "On", "settings.notification.channel.enabled.off": "Off", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index b6641fa5..f95dc295 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -279,6 +279,9 @@ "workflow_node.deploy.form.volcengine_live_domain.tooltip": "For more information, see https://console.volcengine.com/live", "workflow_node.deploy.form.webhook_data.label": "Webhook data (JSON format)", "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", + "workflow_node.deploy.form.webhook_data.guide": "Tips: The Webhook data should be a key-value pair in JSON format. The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL.

Supported variables:
${DOMAIN}: The primary domain of the certificate (CommonName).
${DOMAINS}: The domain list of the certificate (SubjectAltNames).
${CERTIFICATE}: The PEM format content of the certificate file.
${PRIVATE_KEY}: The PEM format content of the private key file.", + "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "workflow_node.deploy.form.webhook_data_preset.button": "Use preset template", "workflow_node.notify.label": "Notification", "workflow_node.notify.form.subject.label": "Subject", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 601188a0..50b871c4 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -19,10 +19,10 @@ "settings.notification.template.card.title": "通知模板", "settings.notification.template.form.subject.label": "通知主题", "settings.notification.template.form.subject.placeholder": "请输入通知主题", - "settings.notification.template.form.subject.extra": "可选的变量(${COUNT}: 即将过期张数)", + "settings.notification.template.form.subject.extra": "支持的变量(${COUNT}: 即将过期张数)", "settings.notification.template.form.message.label": "通知内容", "settings.notification.template.form.message.placeholder": "请输入通知内容", - "settings.notification.template.form.message.extra": "可选的变量(${COUNT}: 即将过期张数;${DOMAINS}: 域名列表)", + "settings.notification.template.form.message.extra": "支持的变量(${COUNT}: 即将过期张数;${DOMAINS}: 域名列表)", "settings.notification.channels.card.title": "通知渠道", "settings.notification.channel.enabled.on": "启用", "settings.notification.channel.enabled.off": "未启用", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 5aac5db3..1b5133cf 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -279,6 +279,9 @@ "workflow_node.deploy.form.volcengine_live_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/live", "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(JSON 格式)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据", + "workflow_node.deploy.form.webhook_data.guide": "小贴士:回调数据是一个 JSON 格式的键值对。其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。

支持的变量:
${DOMAIN}:证书的主域名(即 CommonName
${DOMAINS}:证书的多域名列表(即 SubjectAltNames
${CERTIFICATE}:证书文件 PEM 格式内容
${PRIVATE_KEY}:私钥文件 PEM 格式内容", + "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", + "workflow_node.deploy.form.webhook_data_preset.button": "使用预设模板", "workflow_node.notify.label": "通知", "workflow_node.notify.form.subject.label": "通知主题",