mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-07 21:19:51 +00:00
feat: support more content-type in webhook
This commit is contained in:
parent
609a252ee0
commit
193a19b79c
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -68,14 +69,58 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse x509: %w", err)
|
||||
}
|
||||
|
||||
// 处理 Webhook URL
|
||||
webhookUrl, err := url.Parse(d.config.WebhookUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook url: %w", err)
|
||||
} else if webhookUrl.Scheme != "http" && webhookUrl.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported webhook url scheme: %s", webhookUrl.Scheme)
|
||||
} else {
|
||||
webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${DOMAIN}", url.PathEscape(certX509.Subject.CommonName))
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求谓词
|
||||
webhookMethod := strings.ToUpper(d.config.Method)
|
||||
if webhookMethod == "" {
|
||||
webhookMethod = http.MethodPost
|
||||
} else if webhookMethod != http.MethodGet &&
|
||||
webhookMethod != http.MethodPost &&
|
||||
webhookMethod != http.MethodPut &&
|
||||
webhookMethod != http.MethodPatch &&
|
||||
webhookMethod != http.MethodDelete {
|
||||
return nil, fmt.Errorf("unsupported webhook request method: %s", webhookMethod)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求标头
|
||||
webhookHeaders := make(http.Header)
|
||||
for k, v := range d.config.Headers {
|
||||
webhookHeaders.Set(k, v)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求内容类型
|
||||
const CONTENT_TYPE_JSON = "application/json"
|
||||
const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"
|
||||
const CONTENT_TYPE_MULTIPART = "multipart/form-data"
|
||||
webhookContentType := webhookHeaders.Get("Content-Type")
|
||||
if webhookContentType == "" {
|
||||
webhookContentType = CONTENT_TYPE_JSON
|
||||
webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON)
|
||||
} else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) &&
|
||||
strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) &&
|
||||
strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) {
|
||||
return nil, fmt.Errorf("unsupported webhook content type: %s", webhookContentType)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求数据
|
||||
var webhookData interface{}
|
||||
if d.config.WebhookData == "" {
|
||||
webhookData = map[string]any{
|
||||
webhookData = map[string]string{
|
||||
"name": strings.Join(certX509.DNSNames, ";"),
|
||||
"cert": certPEM,
|
||||
"privkey": privkeyPEM,
|
||||
@ -83,29 +128,49 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
||||
} else {
|
||||
err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART {
|
||||
temp := make(map[string]string)
|
||||
jsonb, err := json.Marshal(webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else if err := json.Unmarshal(jsonb, &temp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else {
|
||||
webhookData = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求
|
||||
// 其中 GET 请求需转换为查询参数
|
||||
req := d.httpClient.R().
|
||||
SetContext(ctx).
|
||||
SetHeaders(d.config.Headers)
|
||||
req.URL = d.config.WebhookUrl
|
||||
req.Method = d.config.Method
|
||||
if req.Method == "" {
|
||||
req.Method = http.MethodPost
|
||||
SetHeaderMultiValues(webhookHeaders)
|
||||
req.URL = webhookUrl.String()
|
||||
req.Method = webhookMethod
|
||||
if webhookMethod == http.MethodGet {
|
||||
req.SetQueryParams(webhookData.(map[string]string))
|
||||
} else {
|
||||
switch webhookContentType {
|
||||
case CONTENT_TYPE_JSON:
|
||||
req.SetBody(webhookData)
|
||||
case CONTENT_TYPE_FORM:
|
||||
req.SetFormData(webhookData.(map[string]string))
|
||||
case CONTENT_TYPE_MULTIPART:
|
||||
req.SetMultipartFormData(webhookData.(map[string]string))
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := req.
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(webhookData).
|
||||
Send()
|
||||
// 发送请求
|
||||
resp, err := req.SetDebug(true).Send()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send webhook request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
|
@ -12,10 +12,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fWebhookUrl string
|
||||
fWebhookData string
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fWebhookUrl string
|
||||
fWebhookContentType string
|
||||
fWebhookData string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -24,6 +25,7 @@ func init() {
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fWebhookUrl, argsPrefix+"URL", "", "")
|
||||
flag.StringVar(&fWebhookContentType, argsPrefix+"CONTENTTYPE", "application/json", "")
|
||||
flag.StringVar(&fWebhookData, argsPrefix+"DATA", "", "")
|
||||
}
|
||||
|
||||
@ -34,7 +36,8 @@ Shell command to run this test:
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" \
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_DATA="{\"certificate\":\"${Certificate}\",\"privateKey\":\"${PrivateKey}\"}"
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_CONTENTTYPE="application/json" \
|
||||
--CERTIMATE_DEPLOYER_WEBHOOK_DATA="{\"certificate\":\"${CERTIFICATE}\",\"privateKey\":\"${PRIVATE_KEY}\"}"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
@ -45,12 +48,17 @@ func TestDeploy(t *testing.T) {
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl),
|
||||
fmt.Sprintf("WEBHOOKCONTENTTYPE: %v", fWebhookContentType),
|
||||
fmt.Sprintf("WEBHOOKDATA: %v", fWebhookData),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
WebhookUrl: fWebhookUrl,
|
||||
WebhookData: fWebhookData,
|
||||
WebhookUrl: fWebhookUrl,
|
||||
WebhookData: fWebhookData,
|
||||
Method: "POST",
|
||||
Headers: map[string]string{
|
||||
"Content-Type": fWebhookContentType,
|
||||
},
|
||||
AllowInsecureConnections: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -67,35 +68,97 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
// 处理 Webhook URL
|
||||
webhookUrl, err := url.Parse(n.config.WebhookUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook url: %w", err)
|
||||
} else if webhookUrl.Scheme != "http" && webhookUrl.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported webhook url scheme: %s", webhookUrl.Scheme)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求谓词
|
||||
webhookMethod := strings.ToUpper(n.config.Method)
|
||||
if webhookMethod == "" {
|
||||
webhookMethod = http.MethodPost
|
||||
} else if webhookMethod != http.MethodGet &&
|
||||
webhookMethod != http.MethodPost &&
|
||||
webhookMethod != http.MethodPut &&
|
||||
webhookMethod != http.MethodPatch &&
|
||||
webhookMethod != http.MethodDelete {
|
||||
return nil, fmt.Errorf("unsupported webhook request method: %s", webhookMethod)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求标头
|
||||
webhookHeaders := make(http.Header)
|
||||
for k, v := range n.config.Headers {
|
||||
webhookHeaders.Set(k, v)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求内容类型
|
||||
const CONTENT_TYPE_JSON = "application/json"
|
||||
const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"
|
||||
const CONTENT_TYPE_MULTIPART = "multipart/form-data"
|
||||
webhookContentType := webhookHeaders.Get("Content-Type")
|
||||
if webhookContentType == "" {
|
||||
webhookContentType = CONTENT_TYPE_JSON
|
||||
webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON)
|
||||
} else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) &&
|
||||
strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) &&
|
||||
strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) {
|
||||
return nil, fmt.Errorf("unsupported webhook content type: %s", webhookContentType)
|
||||
}
|
||||
|
||||
// 处理 Webhook 请求数据
|
||||
var webhookData interface{}
|
||||
if n.config.WebhookData == "" {
|
||||
webhookData = map[string]any{
|
||||
webhookData = map[string]string{
|
||||
"subject": subject,
|
||||
"message": message,
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal([]byte(n.config.WebhookData), &webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
}
|
||||
|
||||
replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject)
|
||||
replaceJsonValueRecursively(webhookData, "${MESSAGE}", message)
|
||||
|
||||
if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART {
|
||||
temp := make(map[string]string)
|
||||
jsonb, err := json.Marshal(webhookData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else if err := json.Unmarshal(jsonb, &temp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err)
|
||||
} else {
|
||||
webhookData = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求
|
||||
// 其中 GET 请求需转换为查询参数
|
||||
req := n.httpClient.R().
|
||||
SetContext(ctx).
|
||||
SetHeaders(n.config.Headers)
|
||||
req.URL = n.config.WebhookUrl
|
||||
req.Method = n.config.Method
|
||||
if req.Method == "" {
|
||||
req.Method = http.MethodPost
|
||||
SetHeaderMultiValues(webhookHeaders)
|
||||
req.URL = webhookUrl.String()
|
||||
req.Method = webhookMethod
|
||||
if webhookMethod == http.MethodGet {
|
||||
req.SetQueryParams(webhookData.(map[string]string))
|
||||
} else {
|
||||
switch webhookContentType {
|
||||
case CONTENT_TYPE_JSON:
|
||||
req.SetBody(webhookData)
|
||||
case CONTENT_TYPE_FORM:
|
||||
req.SetFormData(webhookData.(map[string]string))
|
||||
case CONTENT_TYPE_MULTIPART:
|
||||
req.SetMultipartFormData(webhookData.(map[string]string))
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := req.
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(webhookData).
|
||||
Send()
|
||||
// 发送请求
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send webhook request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
|
@ -15,19 +15,24 @@ const (
|
||||
mockMessage = "test_message"
|
||||
)
|
||||
|
||||
var fUrl string
|
||||
var (
|
||||
fWebhookUrl string
|
||||
fWebhookContentType string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_"
|
||||
|
||||
flag.StringVar(&fUrl, argsPrefix+"URL", "", "")
|
||||
flag.StringVar(&fWebhookUrl, argsPrefix+"URL", "", "")
|
||||
flag.StringVar(&fWebhookContentType, argsPrefix+"CONTENTTYPE", "application/json", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./webhook_test.go -args \
|
||||
--CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url"
|
||||
--CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" \
|
||||
--CERTIMATE_NOTIFIER_WEBHOOK_CONTENTTYPE="application/json"
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
flag.Parse()
|
||||
@ -35,11 +40,15 @@ func TestNotify(t *testing.T) {
|
||||
t.Run("Notify", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("URL: %v", fUrl),
|
||||
fmt.Sprintf("URL: %v", fWebhookUrl),
|
||||
}, "\n"))
|
||||
|
||||
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
WebhookUrl: fUrl,
|
||||
WebhookUrl: fWebhookUrl,
|
||||
Method: "POST",
|
||||
Headers: map[string]string{
|
||||
"Content-Type": fWebhookContentType,
|
||||
},
|
||||
AllowInsecureConnections: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user