Compare commits
9
Commits
16519fd97f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fdfd9e552 | ||
|
|
18523eae58 | ||
|
|
50db0ded4e | ||
|
|
62564a4796 | ||
|
|
f7ffa441f6 | ||
|
|
857be83724 | ||
|
|
de9b9c4a35 | ||
|
|
39abf7f0b8 | ||
|
|
bfd05f0064 |
@@ -11,6 +11,7 @@ jobs:
|
||||
container:
|
||||
image: golang:1.23-alpine
|
||||
steps:
|
||||
- run: apk add --no-cache nodejs
|
||||
- uses: actions/checkout@v4
|
||||
- run: gofmt -l . | tee /tmp/fmt.out && test ! -s /tmp/fmt.out
|
||||
- run: go vet ./...
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@ WORKDIR /src
|
||||
COPY go.mod ./
|
||||
COPY cmd/ ./cmd/
|
||||
RUN go build -ldflags="-s -w" -o /out/bridge ./cmd/bridge
|
||||
FROM scratch
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates && adduser -D -u 65532 app
|
||||
COPY --from=build /out/bridge /bridge
|
||||
USER 65532:65532
|
||||
EXPOSE 8080
|
||||
|
||||
+23
-8
@@ -131,14 +131,18 @@ func (g *glpiClient) openTicketFor(monitor string) (int, error) {
|
||||
q.Set("criteria[1][field]", "12") // status
|
||||
q.Set("criteria[1][searchtype]", "equals")
|
||||
q.Set("criteria[1][value]", "notold")
|
||||
var out []struct {
|
||||
ID int `json:"2"`
|
||||
var out struct {
|
||||
Data []map[string]any `json:"data"`
|
||||
}
|
||||
if err := g.do("GET", "/search/Ticket?"+q.Encode(), nil, &out); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(out) > 0 {
|
||||
return out[0].ID, nil
|
||||
for _, row := range out.Data {
|
||||
if v, ok := row["2"]; ok {
|
||||
var tid int
|
||||
fmt.Sscanf(fmt.Sprint(v), "%d", &tid)
|
||||
return tid, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
@@ -154,9 +158,17 @@ func (g *glpiClient) createTicket(name, content string) (int, error) {
|
||||
}
|
||||
|
||||
func (g *glpiClient) followup(ticket int, content string) error {
|
||||
// GLPI 11: followups live under the item endpoint; fall back to the
|
||||
// legacy path when the item endpoint rejects the payload.
|
||||
err := g.do("POST", fmt.Sprintf("/Ticket/%d/ITILFollowup", ticket), map[string]any{
|
||||
"input": map[string]any{"content": content},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return g.do("POST", "/followup", map[string]any{
|
||||
"input": map[string]any{"itemtype": "Ticket", "items_id": ticket, "content": content},
|
||||
}, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type bridge struct {
|
||||
@@ -187,7 +199,7 @@ func (b *bridge) handle(w http.ResponseWriter, r *http.Request) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
switch status {
|
||||
case 2: // DOWN (0=unknown,1=up,2=down per Kuma webhook)
|
||||
case 0: // Kuma heartbeat status: 0=down, 1=up, 2=pending, 3=maintenance
|
||||
if _, exists := b.open[k.Monitor.Name]; !exists {
|
||||
tid, err := b.glpi.openTicketFor(k.Monitor.Name)
|
||||
if err == nil && tid == 0 {
|
||||
@@ -204,16 +216,19 @@ func (b *bridge) handle(w http.ResponseWriter, r *http.Request) {
|
||||
case 1: // UP
|
||||
tid, exists := b.open[k.Monitor.Name]
|
||||
if !exists {
|
||||
// restarts wipe the in-memory map; search GLPI for an open ticket
|
||||
tid, _ = b.glpi.openTicketFor(k.Monitor.Name)
|
||||
if tid == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
delete(b.open, k.Monitor.Name)
|
||||
go func() {
|
||||
fu := fmt.Sprintf("RECOVERY: monitor %s back UP at %s. Ticket auto-closing as solved with recovery evidence.", k.Monitor.Name, k.Heartbeat.Time)
|
||||
if err := b.glpi.followup(tid, fu); err != nil {
|
||||
log.Printf("followup failed: %v", err)
|
||||
return
|
||||
log.Printf("followup failed (continuing to solve): %v", err)
|
||||
}
|
||||
if err := b.glpi.do("POST", fmt.Sprintf("/Ticket/%d", tid), map[string]any{"input": map[string]any{"status": 11}}, nil); err != nil { // 11 = solved
|
||||
if err := b.glpi.do("PUT", fmt.Sprintf("/Ticket/%d", tid), map[string]any{"input": map[string]any{"status": 5}}, nil); err != nil { // ticket status 5 = solved
|
||||
log.Printf("solve failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user