hermes: deploy Telegram readiness diagnostics
This commit is contained in:
parent
569a366cbb
commit
987bd7587c
@ -20,7 +20,7 @@ spec:
|
|||||||
app: hermes-chat-router
|
app: hermes-chat-router
|
||||||
annotations:
|
annotations:
|
||||||
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
|
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
|
||||||
ai.bstein.dev/config-rev: "20260813-telegram-readiness-v1"
|
ai.bstein.dev/config-rev: "20260813-telegram-readiness-v2"
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
vault.hashicorp.com/agent-init-first: "true"
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
@ -62,7 +62,7 @@ spec:
|
|||||||
values: [rpi5]
|
values: [rpi5]
|
||||||
containers:
|
containers:
|
||||||
- name: router
|
- name: router
|
||||||
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:d639e78934d36384eaebc631a95c7e151492c59af2d483c994f14ac585590eeb
|
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:e3794fb8b9ee76d699893ba303d7080f54698c0a06064dc32293e99e988cabff
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- {name: http, containerPort: 8080, protocol: TCP}
|
- {name: http, containerPort: 8080, protocol: TCP}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@ -100,14 +101,18 @@ func (bot *telegramBot) status() (string, bool, string) {
|
|||||||
return bot.botUsername, bot.ready, bot.lastError
|
return bot.botUsername, bot.ready, bot.lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *telegramBot) setStatus(username string, ready bool, lastError string) {
|
func (bot *telegramBot) setStatus(username string, ready bool, lastError string) bool {
|
||||||
bot.mu.Lock()
|
bot.mu.Lock()
|
||||||
defer bot.mu.Unlock()
|
defer bot.mu.Unlock()
|
||||||
|
previousUsername := bot.botUsername
|
||||||
|
previousReady := bot.ready
|
||||||
|
previousError := bot.lastError
|
||||||
if username != "" {
|
if username != "" {
|
||||||
bot.botUsername = strings.TrimPrefix(strings.TrimSpace(username), "@")
|
bot.botUsername = strings.TrimPrefix(strings.TrimSpace(username), "@")
|
||||||
}
|
}
|
||||||
bot.ready = ready
|
bot.ready = ready
|
||||||
bot.lastError = strings.TrimSpace(lastError)
|
bot.lastError = strings.TrimSpace(lastError)
|
||||||
|
return previousUsername != bot.botUsername || previousReady != bot.ready || previousError != bot.lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *telegramBot) slotLock(slot int) *sync.Mutex {
|
func (bot *telegramBot) slotLock(slot int) *sync.Mutex {
|
||||||
@ -140,11 +145,20 @@ func (bot *telegramBot) call(ctx context.Context, method string, values url.Valu
|
|||||||
return errors.New("read Telegram response")
|
return errors.New("read Telegram response")
|
||||||
}
|
}
|
||||||
var envelope struct {
|
var envelope struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
Result json.RawMessage `json:"result"`
|
Result json.RawMessage `json:"result"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
}
|
}
|
||||||
if response.StatusCode != http.StatusOK || json.Unmarshal(body, &envelope) != nil || !envelope.OK {
|
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||||
return fmt.Errorf("Telegram API returned status %d", response.StatusCode)
|
return fmt.Errorf("Telegram API returned undecodable status %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK || !envelope.OK {
|
||||||
|
description := strings.TrimSpace(envelope.Description)
|
||||||
|
if description == "" {
|
||||||
|
description = http.StatusText(response.StatusCode)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Telegram API error %d: %s", envelope.ErrorCode, description)
|
||||||
}
|
}
|
||||||
if result != nil && len(envelope.Result) > 0 {
|
if result != nil && len(envelope.Result) > 0 {
|
||||||
if err := json.Unmarshal(envelope.Result, result); err != nil {
|
if err := json.Unmarshal(envelope.Result, result); err != nil {
|
||||||
@ -164,10 +178,21 @@ func (bot *telegramBot) run() {
|
|||||||
err := bot.call(ctx, "getMe", url.Values{}, &me)
|
err := bot.call(ctx, "getMe", url.Values{}, &me)
|
||||||
cancel()
|
cancel()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bot.setStatus(me.Username, true, "")
|
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
break
|
err = bot.call(ctx, "deleteWebhook", url.Values{
|
||||||
|
"drop_pending_updates": {"false"},
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err == nil {
|
||||||
|
if bot.setStatus(me.Username, true, "") {
|
||||||
|
log.Printf("Telegram bot ready as @%s", strings.TrimPrefix(me.Username, "@"))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bot.setStatus("", false, "Telegram rejected the configured bot token or is temporarily unavailable.") {
|
||||||
|
log.Printf("Telegram bot readiness check failed: %v", err)
|
||||||
}
|
}
|
||||||
bot.setStatus("", false, "Telegram rejected the configured bot token or is temporarily unavailable.")
|
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@ -182,7 +207,9 @@ func (bot *telegramBot) run() {
|
|||||||
err := bot.call(ctx, "getUpdates", values, &updates)
|
err := bot.call(ctx, "getUpdates", values, &updates)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bot.setStatus("", false, "Hermes lost contact with the configured Telegram bot.")
|
if bot.setStatus("", false, "Hermes lost contact with the configured Telegram bot.") {
|
||||||
|
log.Printf("Telegram update polling failed: %v", err)
|
||||||
|
}
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadTelegramConfig(t *testing.T) {
|
func TestReadTelegramConfig(t *testing.T) {
|
||||||
@ -72,3 +73,21 @@ func TestSplitTelegramTextStaysUnderTelegramLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTelegramCallIncludesAPIDescription(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
writer.WriteHeader(http.StatusConflict)
|
||||||
|
_, _ = writer.Write([]byte(`{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
bot := &telegramBot{
|
||||||
|
apiBase: server.URL,
|
||||||
|
client: &http.Client{Timeout: time.Second},
|
||||||
|
}
|
||||||
|
err := bot.call(t.Context(), "getUpdates", nil, nil)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "terminated by other getUpdates request") {
|
||||||
|
t.Fatalf("expected Telegram API description, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user