Files
p/main.go

204 lines
5.0 KiB
Go
Raw Normal View History

2024-11-21 14:59:10 -08:00
package main
import (
2024-11-21 21:42:03 -08:00
"bytes"
"encoding/json"
2024-11-21 14:59:10 -08:00
"fmt"
2024-11-22 15:01:49 -08:00
"html/template"
2024-11-21 21:42:03 -08:00
"io"
"log"
2024-11-21 14:59:10 -08:00
"net/http"
"os"
2024-11-22 15:01:49 -08:00
"strings"
2024-11-25 18:01:37 -06:00
"github.com/openai/openai-go"
2024-11-21 14:59:10 -08:00
)
2024-11-21 21:42:03 -08:00
type PDAlert struct {
RoutingKey string `json:"routing_key"`
EventAction string `json:"event_action"`
Payload PDPayload `json:"payload"`
}
type PDPayload struct {
Summary string `json:"summary"`
Source string `json:"source"`
Severity string `json:"severity"`
}
type PHandler struct {
2024-11-22 15:01:49 -08:00
tmpl *template.Template
2024-11-22 13:49:31 -08:00
routingKey string
2024-11-25 18:01:37 -06:00
mux *http.ServeMux
2024-11-21 21:42:03 -08:00
}
2024-11-22 15:01:49 -08:00
func NewPHandler(routingKey string) (*PHandler, error) {
tmpl := template.New("index.html")
tmpl.Funcs(template.FuncMap{
"replaceAll": func(o, n, s string) string { return strings.ReplaceAll(s, o, n) },
})
tmpl, err := tmpl.ParseFiles("static/index.html")
if err != nil {
return nil, fmt.Errorf("static/index.html: %w", err)
}
2024-11-25 18:01:37 -06:00
ph := &PHandler{
2024-11-22 15:01:49 -08:00
tmpl: tmpl,
2024-11-22 13:49:31 -08:00
routingKey: routingKey,
2024-11-25 18:01:37 -06:00
mux: http.NewServeMux(),
}
ph.mux.HandleFunc("/{$}", ph.serveRoot)
ph.mux.HandleFunc("/suggest", ph.serveSuggest)
return ph, nil
2024-11-21 21:42:03 -08:00
}
func (ph *PHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2024-11-25 18:01:37 -06:00
ph.mux.ServeHTTP(w, r)
}
func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
2024-11-21 21:42:03 -08:00
err := r.ParseForm()
if err != nil {
http.Error(w, fmt.Sprintf("invalid form: %s\n", err), http.StatusBadRequest)
return
}
2024-11-25 18:01:37 -06:00
log.Printf("%s %s %s", r.RemoteAddr, r.URL.Path, r.Form.Encode())
2024-11-22 13:49:31 -08:00
2024-11-23 06:35:38 -08:00
m := r.Form.Get("m")
if m == "" {
2024-11-22 15:01:49 -08:00
err = ph.tmpl.Execute(w, ph.envs())
if err != nil {
http.Error(w, fmt.Sprintf("execute %s: %s\n", ph.tmpl.Name(), err), http.StatusBadRequest)
return
}
2024-11-21 21:42:03 -08:00
return
}
2024-11-21 14:59:10 -08:00
2024-11-21 21:42:03 -08:00
buf := &bytes.Buffer{}
err = json.NewEncoder(buf).Encode(PDAlert{
2024-11-22 13:49:31 -08:00
RoutingKey: ph.routingKey,
2024-11-21 21:42:03 -08:00
EventAction: "trigger",
Payload: PDPayload{
2024-11-23 06:35:38 -08:00
Summary: m,
2024-11-22 13:49:31 -08:00
Source: r.RemoteAddr,
2024-11-21 21:42:03 -08:00
Severity: "critical",
},
})
if err != nil {
http.Error(w, fmt.Sprintf("failed to create PD request: %s\n", err), http.StatusBadRequest)
return
}
req, err := http.NewRequest("POST", "https://events.pagerduty.com/v2/enqueue", buf)
if err != nil {
http.Error(w, fmt.Sprintf("failed to create HTTP request: %s\n", err), http.StatusBadRequest)
return
}
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("error from PD: %s\n", err), http.StatusBadRequest)
2024-11-22 15:01:49 -08:00
return
2024-11-21 21:42:03 -08:00
}
body, _ := io.ReadAll(res.Body)
2024-11-25 18:01:37 -06:00
_ = res.Body.Close()
2024-11-21 21:42:03 -08:00
if res.StatusCode != 202 {
http.Error(w, fmt.Sprintf("error from PD: %s", string(body)), http.StatusBadRequest)
2024-11-22 15:01:49 -08:00
return
2024-11-21 21:42:03 -08:00
}
2024-11-22 15:01:49 -08:00
2024-11-25 18:01:37 -06:00
_, _ = w.Write([]byte("page sent\n"))
}
func (ph *PHandler) serveSuggest(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, fmt.Sprintf("invalid form: %s\n", err), http.StatusBadRequest)
return
}
log.Printf("%s %s %s", r.RemoteAddr, r.URL.Path, r.Form.Encode())
m := r.Form.Get("m")
if m == "" {
http.Error(w, "m param required", http.StatusBadRequest)
}
c := openai.NewClient()
comp, err := c.Chat.Completions.New(r.Context(), openai.ChatCompletionNewParams{
Messages: openai.F([]openai.ChatCompletionMessageParamUnion{
2024-11-25 18:12:21 -06:00
openai.SystemMessage(`You are an assistant helping users to write good text to include in an urgent page sent to a person. Good page text contains a very brief description of the problem (e.g. "down" or "slow"), the systems it affects (acronyms for system names are fine), the identity of the sender (first names are fine), and how to contact them (e.g. a phone number or incident Slack channel). The request will consist of just the user's proposed page text. Respond with just a very brief message suggesting improvements that the sender might make or saying "Looks good, send it!". Remember that the user is likely in an urgent, stressful situation, so make your response brief and err on the side of assuming that the message is sufficient if the text might be OK. Assume that the recipient already knows the message is urgent so the sender doesn't have to specify urgency.`),
2024-11-25 18:01:37 -06:00
openai.UserMessage(m),
}),
Model: openai.F(openai.ChatModelGPT4o),
})
if err != nil {
http.Error(w, fmt.Sprintf("error from openai: %s", err), http.StatusInternalServerError)
return
}
_, _ = w.Write([]byte(comp.Choices[0].Message.Content))
2024-11-21 21:42:03 -08:00
}
2024-11-22 15:01:49 -08:00
var allowedEnvs = []string{
2024-11-22 22:09:29 -08:00
"SHORT_NAME",
"CONTACT_NAME",
2024-11-22 15:01:49 -08:00
"CONTACT_PHONE",
2024-11-22 21:14:28 -08:00
"CONTACT_SMS",
"CONTACT_IMESSAGE",
"CONTACT_WHATSAPP",
"CONTACT_PAGE_EMAIL",
2024-11-22 15:01:49 -08:00
}
func (ph *PHandler) envs() map[string]string {
envs := map[string]string{}
for _, k := range allowedEnvs {
v := os.Getenv(k)
if v != "" {
envs[k] = v
}
}
return envs
}
2024-11-21 21:42:03 -08:00
func main() {
2024-11-22 13:49:31 -08:00
routingKey := os.Getenv("PD_ROUTING_KEY")
if routingKey == "" {
log.Fatalf("please set PD_ROUTING_KEY")
}
2024-11-22 15:01:49 -08:00
ph, err := NewPHandler(routingKey)
if err != nil {
log.Fatalf("NewPHandler: %s", err)
}
http.Handle("/", ph)
2024-11-21 14:59:10 -08:00
port := os.Getenv("PORT")
if port == "" {
port = "80"
}
2024-11-21 21:42:03 -08:00
bind := fmt.Sprintf(":%s", port)
log.Printf("listening on %s", bind)
2024-11-21 14:59:10 -08:00
2024-11-21 21:42:03 -08:00
if err := http.ListenAndServe(bind, nil); err != nil {
2024-11-22 13:49:31 -08:00
log.Fatalf("listen: %s", err)
2024-11-21 14:59:10 -08:00
}
}