Skip to content

Commit

Permalink
Add support for Telegram (#10)
Browse files Browse the repository at this point in the history
* Add support for Telegram in chat package

* Replace ioutil.ReadFile with os.ReadFile

* Document use of Telegram as notification destination

* Make Telegram message more compact

* Simplify Dockerfile

* Use master tag by default for images
  • Loading branch information
lnsp authored Sep 27, 2022
1 parent 5948e47 commit 95b8b38
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 6 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ RUN apk update && \
mkdir -p "/build"

WORKDIR /build
COPY go.mod go.sum /build/
COPY go.mod go.sum .
RUN go mod download

COPY . /build/
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go build -a --installsuffix cgo --ldflags="-s" -o informer

FROM alpine
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ metadata:
You should use the Bot User OAuth Access Token as `token`. It can be copied from the Slack App admin interface after registering a new Slack API App and enabling the Bot feature.

##### If you use Telegram, use

```yaml
apiVersion: v1
data:
chatId: <chat-id>
token: <bot-token>
kind: ConfigMap
metadata:
name: telegram-informer-cfg
```

Extracting the chat ID in Telegram can be slightly finicky. The easiest way I've found is using the ID exposed in the URLs when using the web.telegram.org frontend.

This step is required to create a valid configuration for our crash informer.

### Step 2: Deploy the informer
Expand All @@ -43,6 +57,9 @@ kubectl apply -f manifests/mattermost-informer.yaml
# If you use Slack
kubectl apply -f manifests/slack-informer.yaml
# If you use Telegram
kubectl apply -f manifests/telegram-informer.yaml
```

You may want to update the `namespace` references, since the informer only watches a given namespace.
Expand Down
2 changes: 1 addition & 1 deletion manifests/mattermost-informer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ spec:
serviceAccountName: crash-informer
containers:
- name: informer
image: ghcr.io/lnsp/k8s-crash-informer:v0.2.1
image: ghcr.io/lnsp/k8s-crash-informer:master
imagePullPolicy: Always
env:
- name: MATTERMOST_CHANNEL
Expand Down
2 changes: 1 addition & 1 deletion manifests/slack-informer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ spec:
serviceAccountName: crash-informer
containers:
- name: informer
image: ghcr.io/lnsp/k8s-crash-informer:v0.2.1
image: ghcr.io/lnsp/k8s-crash-informer:master
imagePullPolicy: Always
env:
- name: SLACK_CHANNEL
Expand Down
69 changes: 69 additions & 0 deletions manifests/telegram-informer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: crash-informer
namespace: default
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "replicationcontrollers"]
verbs: ["get", "watch", "list"]
- apiGroups: ["apps", "extensions"]
resources: ["replicasets", "deployments"]
verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: crash-informer
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: crash-informer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: crash-informer
subjects:
- kind: ServiceAccount
name: crash-informer
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: telegram-informer
namespace: default
spec:
selector:
matchLabels:
app: telegram-informer
template:
metadata:
labels:
app: telegram-informer
spec:
restartPolicy: Always
serviceAccountName: crash-informer
containers:
- name: informer
image: ghcr.io/lnsp/k8s-crash-informer:master
imagePullPolicy: Always
env:
- name: TELEGRAM_CHATID
valueFrom:
configMapKeyRef:
name: telegram-informer-cfg
key: chatId
- name: TELEGRAM_TOKEN
valueFrom:
configMapKeyRef:
name: telegram-informer-cfg
key: token
- name: INFORMER_TYPE
value: telegram
resources:
limits:
memory: "128Mi"
cpu: "100m"
80 changes: 80 additions & 0 deletions pkg/chat/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package chat

import (
"fmt"
"strings"
"unicode/utf16"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/kelseyhightower/envconfig"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/slack-go/slack"
Expand Down Expand Up @@ -84,6 +87,8 @@ func NewClientFromEnv() (Client, error) {
client, err = NewMattermostClientFromEnv()
case "slack":
client, err = NewSlackClientFromEnv()
case "telegram":
client, err = NewTelegramClientFromEnv()
default:
err = fmt.Errorf("unknown client type: %s", cfg.Type)
}
Expand Down Expand Up @@ -163,3 +168,78 @@ func NewSlackClientFromEnv() (*SlackClient, error) {
Channel: cfg.Channel,
}, nil
}

type TelegramConfig struct {
Token string
ChatID int64
}

type TelegramClient struct {
chat tgbotapi.Chat
bot *tgbotapi.BotAPI
}

func (client *TelegramClient) Send(note *CrashNotification) {
// Generate text
type entityMarker struct {
Type string
Content string
}
contents := []entityMarker{
{"bold", note.Title},
{"", note.Message},
{"bold", "Logs"},
{"pre", note.Logs},
{"bold", "Reason"},
{"pre", note.Reason},
}
// Generate text
cleartext := []string{}
for _, c := range contents {
cleartext = append(cleartext, c.Content)
}
// Get all joined groups
message := tgbotapi.NewMessage(client.chat.ID, strings.Join(cleartext, "\n"))
offset := 0
// Generate list of entities
for _, c := range contents {
length := len(utf16.Encode([]rune(c.Content)))
if c.Type != "" {
message.Entities = append(message.Entities, tgbotapi.MessageEntity{
Type: c.Type,
Offset: offset,
Length: length,
})
}
offset += length + 1
}
_, err := client.bot.Send(message)
if err != nil {
fmt.Println(err)
}
}

// NewTelegramClientFromEnv instantiates and configures a Telegram client.
func NewTelegramClientFromEnv() (*TelegramClient, error) {
var cfg TelegramConfig
if err := envconfig.Process("telegram", &cfg); err != nil {
return nil, err
}
bot, err := tgbotapi.NewBotAPI(cfg.Token)
if err != nil {
return nil, fmt.Errorf("init bot: %w", err)
}
// Get chat to verify
chat, err := bot.GetChat(tgbotapi.ChatInfoConfig{
ChatConfig: tgbotapi.ChatConfig{
ChatID: cfg.ChatID,
},
})
if err != nil {
return nil, fmt.Errorf("get chat: %w", err)
}
return &TelegramClient{
chat: chat,
bot: bot,
}, nil
}
4 changes: 2 additions & 2 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package utils

import (
"fmt"
"io/ioutil"
"os"
)

const namespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"

// Namespace returns the namespace this pod is running in.
func Namespace() (string, error) {
nsfile, err := ioutil.ReadFile(namespaceFilePath)
nsfile, err := os.ReadFile(namespaceFilePath)
if err != nil {
return "", fmt.Errorf("could not read namespace: %v", err)
}
Expand Down

0 comments on commit 95b8b38

Please sign in to comment.