feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
99
internal/sender/webhook.go
Обычный файл
99
internal/sender/webhook.go
Обычный файл
@@ -0,0 +1,99 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"rsgit.ru/rsmon/rsmon/app/models"
|
||||
"rsgit.ru/rsmon/rsmon/config/application"
|
||||
)
|
||||
|
||||
const envProduction = "production"
|
||||
|
||||
// RunWebhook provides functionality.
|
||||
func RunWebhook(message *models.Message) (*string, error) {
|
||||
if application.Env != envProduction {
|
||||
return nil, errors.New("not sending in env " + application.Env)
|
||||
}
|
||||
|
||||
body, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := httpClient.Post(
|
||||
message.Contact.Value,
|
||||
"application/json",
|
||||
bytes.NewBuffer(body),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
str := string(body)
|
||||
return &str, nil
|
||||
}
|
||||
|
||||
// SendWebhookWithCredential delivers a webhook notification using the
|
||||
// provided wire credential block. Phase 1 ships an empty signing secret;
|
||||
// phase 4 will plumb the per-account secret through Contact.Data. The
|
||||
// signature header is X-RSMon-Signature (hex-encoded HMAC-SHA256 of the
|
||||
// body), matching what webhook consumers in the existing fleet expect.
|
||||
//
|
||||
// The payload matches the wire.NotificationTask shape so customers
|
||||
// receiving the legacy Message JSON see one fewer breaking change.
|
||||
func SendWebhookWithCredential(payload []byte, contactValue, signingSecret string) (*string, error) {
|
||||
return SendWebhookWithCredentialContext(context.Background(), payload, contactValue, signingSecret)
|
||||
}
|
||||
|
||||
func SendWebhookWithCredentialContext(ctx context.Context, payload []byte, contactValue, signingSecret string) (*string, error) {
|
||||
if application.Env != envProduction {
|
||||
return nil, errors.New("not sending in env " + application.Env)
|
||||
}
|
||||
if contactValue == "" {
|
||||
return nil, errors.New("webhook contact value is empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, contactValue, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "rsmon-worker/1")
|
||||
if signingSecret != "" {
|
||||
mac := hmac.New(sha256.New, []byte(signingSecret))
|
||||
mac.Write(payload)
|
||||
req.Header.Set("X-RSMon-Signature", "sha256="+hex.EncodeToString(mac.Sum(nil)))
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode >= 500 {
|
||||
return nil, errors.New("webhook upstream " + resp.Status)
|
||||
}
|
||||
str := string(respBody)
|
||||
return &str, nil
|
||||
}
|
||||
Ссылка в новой задаче
Block a user