Skip to content

Commit c671139

Browse files
authored
Send MS Teams V2 notifications (#248)
* refactor: replace interface{} with any and simplify code - Use `any` alias instead of `interface{}` in event handlers - Remove redundant `== true` in boolean checks - Simplify `ObjectMeta.Annotations` to `Annotations` * chore: add asdf tool-versions to gitignore * refactor: remove unused kubeclientset field from Controller - Remove kubeclientset from Controller struct - Fix error handling in job event handlers * feat: add MS Teams v2 notification support - Add HTTP client with retry logic - Implement MS Teams adaptive card structure - Register msteamsv2 in notification registry - Update README with Teams config examples - Refactor slack env check logic * chore: update module path and add MSTeamsV2 implementation - Change module path from yutachaos to remmercier - Add kube-job-notifier binary to gitignore - Implement MSTeamsV2 NotifyStart method - Add environment variable checks for notification enablement - Remove unused httpclient dependency in msteamsv2.go * chore: add .vscode to gitignore and reorder imports - Ignore .vscode directory - Group standard library imports before external packages * feat: implement MS Teams v2 notifications - Add message template with job details formatting - Implement NotifyStart, NotifySuccess, and NotifyFailed methods - Format text with double line breaks for Adaptive Cards - Update card version to 1.4 * feat: add cronjob filtering by regex pattern - Add CRONJOB_REGEX env var to filter monitored cronjobs - Check regex match early in event handlers - Move cronjob name lookup before pod wait - Update README with new config option * refactor: remove unused httpclient package - Delete pkg/httpclient/http.go with HTTPClient implementation * test: add comprehensive tests for MS Teams V2 notifications - Cover webhook initialization and validation - Test adaptive card payload generation - Verify notification methods (start, success, failed) - Add error handling test cases * refactor: improve error handling in job event handlers - Handle getPodFromControllerUID errors properly - Fix indentation in notification block - Remove unused isCronjobNameIncluded function
1 parent ab53099 commit c671139

File tree

9 files changed

+513
-46
lines changed

9 files changed

+513
-46
lines changed

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
/vendor/
2424
/Godeps/
2525

26+
kube-job-notifier
27+
2628
### Intellij ###
2729
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
2830
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
@@ -144,4 +146,9 @@ Temporary Items
144146
# direnv
145147
.envrc
146148

147-
*.tgz
149+
*.tgz
150+
151+
# asdf
152+
.tool-versions
153+
154+
.vscode

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ kubectl get po
3939
Configure using environment variables:
4040

4141
```bash
42+
export MSTEAMSV2_ENABLED=true
43+
export MSTEAMSV2_WEBHOOK_URL=YOUR_WEBHOOK_URL
44+
export SLACK_ENABLED=true
4245
export SLACK_TOKEN=YOUR_SLACK_TOKEN
4346
export SLACK_CHANNEL=YOUR_NOTIFICATION_CHANNEL_ID
4447
export SLACK_STARTED_NOTIFY=true # Optional, default: true
@@ -47,8 +50,9 @@ export SLACK_FAILED_NOTIFY=true # Optional, default: true
4750
export SLACK_USERNAME=YOUR_NOTIFICATION_USERNAME # Optional
4851
export SLACK_SUCCEED_CHANNEL=YOUR_NOTIFICATION_CHANNEL_ID # Optional
4952
export SLACK_FAILED_CHANNEL=YOUR_NOTIFICATION_CHANNEL_ID # Optional
50-
export DATADOG_ENABLED=true # Optional, default: false
51-
export NAMESPACE=KUBERNETES_NAMESPACE # Optional
53+
export DATADOG_ENABLED=true # Optional, default: false
54+
export NAMESPACE=KUBERNETES_NAMESPACE # Optional
55+
export CRONJOB_REGEX=REGEX # Optional, if empty all cronjobs will be included
5256
```
5357

5458
### Job Annotation Configuration

controller.go

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"regexp"
910
"strings"
1011
"time"
1112

13+
"github.com/remmercier/kube-job-notifier/pkg/monitoring"
14+
"github.com/remmercier/kube-job-notifier/pkg/notification"
1215
"github.com/thoas/go-funk"
13-
"github.com/yutachaos/kube-job-notifier/pkg/monitoring"
14-
"github.com/yutachaos/kube-job-notifier/pkg/notification"
1516
batchv1 "k8s.io/api/batch/v1"
1617
corev1 "k8s.io/api/core/v1"
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -47,10 +48,9 @@ var serverStartTime time.Time
4748

4849
// Controller is Kubernetes Controller struct
4950
type Controller struct {
50-
kubeclientset kubernetes.Interface
51-
jobsLister batcheslisters.JobLister
52-
jobsSynced cache.InformerSynced
53-
recorder record.EventRecorder
51+
jobsLister batcheslisters.JobLister
52+
jobsSynced cache.InformerSynced
53+
recorder record.EventRecorder
5454
}
5555

5656
// NewController returns a new controller
@@ -76,42 +76,56 @@ func NewController(
7676
subscriptions := monitoring.NewSubscription()
7777
datadogSubscription := subscriptions["datadog"]
7878

79+
regexEnv := os.Getenv("CRONJOB_REGEX")
80+
var regex *regexp.Regexp
81+
if regexEnv != "" {
82+
regex = regexp.MustCompile(regexEnv)
83+
}
84+
7985
klog.Info("Setting event handlers")
8086
jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
81-
AddFunc: func(new interface{}) {
87+
AddFunc: func(new any) {
8288
newJob := new.(*batchv1.Job)
8389
klog.Infof("Job added: %v", newJob.Status)
8490

91+
cronJob, err := getCronJobNameFromOwnerReferences(kubeclientset, newJob)
92+
if err != nil {
93+
klog.Errorf("Get cronjob failed: %v", err)
94+
}
95+
96+
if regex != nil && !regex.MatchString(cronJob) {
97+
return
98+
}
99+
85100
if newJob.CreationTimestamp.Sub(serverStartTime).Seconds() < 0 {
86101
return
87102
}
88103

89-
if notifiedJobs[newJob.Name] == true {
104+
if notifiedJobs[newJob.Name] {
90105
return
91106
}
92107

93108
klog.Infof("Job started: %v", newJob.Status)
94109

95110
jobPod, err := getPodFromControllerUID(kubeclientset, newJob)
96-
err = waitForPodRunning(kubeclientset, jobPod)
97-
98111
if err != nil {
99-
klog.Errorf("Error waiting for pod to become running: %v", jobPod)
112+
klog.Errorf("Get pods failed: %v", err)
100113
return
101114
}
102115

103-
cronJob, err := getCronJobNameFromOwnerReferences(kubeclientset, newJob)
104-
116+
err = waitForPodRunning(kubeclientset, jobPod)
105117
if err != nil {
106-
klog.Errorf("Get cronjob failed: %v", err)
118+
klog.Errorf("Error waiting for pod to become running: %v", jobPod)
119+
return
107120
}
121+
108122
klog.Infof("Job started: %v", newJob.Status)
109123
messageParam := notification.MessageTemplateParam{
110124
JobName: newJob.Name,
111125
CronJobName: cronJob,
112126
Namespace: newJob.Namespace,
113127
StartTime: newJob.Status.StartTime,
114-
Annotations: newJob.Spec.Template.ObjectMeta.Annotations,
128+
Annotations: newJob.Spec.Template.Annotations,
115129
}
116130
for name, n := range notifications {
117131
err := n.NotifyStart(messageParam)
@@ -121,23 +135,36 @@ func NewController(
121135
}
122136

123137
},
124-
UpdateFunc: func(old, new interface{}) {
138+
UpdateFunc: func(old, new any) {
125139
newJob := new.(*batchv1.Job)
126140
oldJob := old.(*batchv1.Job)
127141

128142
klog.Infof("oldJob.Status:%v", oldJob.Status)
129143
klog.Infof("newJob.Status:%v", newJob.Status)
144+
cronJobName, err := getCronJobNameFromOwnerReferences(kubeclientset, newJob)
145+
if err != nil {
146+
klog.Errorf("Get cronjob failed: %v", err)
147+
}
148+
149+
if regex != nil && !regex.MatchString(cronJobName) {
150+
return
151+
}
152+
130153
if newJob.CreationTimestamp.Sub(serverStartTime).Seconds() < 0 {
131154
return
132155
}
133156

134-
if notifiedJobs[newJob.Name] == true {
157+
if notifiedJobs[newJob.Name] {
135158
return
136159
}
137160

138161
jobPod, err := getPodFromControllerUID(kubeclientset, newJob)
139-
err = waitForPodRunning(kubeclientset, jobPod)
162+
if err != nil {
163+
klog.Errorf("Get pods failed: %v", err)
164+
return
165+
}
140166

167+
err = waitForPodRunning(kubeclientset, jobPod)
141168
if err != nil {
142169
klog.Errorf("Error waiting for pod to become running: %v", err)
143170
return
@@ -151,13 +178,7 @@ func NewController(
151178
return
152179
}
153180

154-
cronJobName, err := getCronJobNameFromOwnerReferences(kubeclientset, newJob)
155-
156-
if err != nil {
157-
klog.Errorf("Get cronjob failed: %v", err)
158-
return
159-
}
160-
annotations := newJob.Spec.Template.ObjectMeta.Annotations
181+
annotations := newJob.Spec.Template.Annotations
161182
lm := getLogMode(annotations, logModeAnnotationName)
162183
jobLogStr := getJobLogs(kubeclientset, jobPod, cronJobName, lm)
163184

@@ -185,7 +206,7 @@ func NewController(
185206
CronJobName: cronJobName,
186207
Name: newJob.Name,
187208
Namespace: newJob.Namespace,
188-
Annotations: newJob.Spec.Template.ObjectMeta.Annotations,
209+
Annotations: newJob.Spec.Template.Annotations,
189210
})
190211
if err != nil {
191212
klog.Errorf("Fail event subscribe.: %v", err)
@@ -207,7 +228,7 @@ func NewController(
207228
return
208229
}
209230

210-
annotations := newJob.Spec.Template.ObjectMeta.Annotations
231+
annotations := newJob.Spec.Template.Annotations
211232
lm := getLogMode(annotations, logModeAnnotationName)
212233
jobLogStr := getJobLogs(kubeclientset, jobPod, cronJobName, lm)
213234

@@ -232,7 +253,7 @@ func NewController(
232253
CronJobName: cronJobName,
233254
Name: newJob.Name,
234255
Namespace: newJob.Namespace,
235-
Annotations: newJob.Spec.Template.ObjectMeta.Annotations,
256+
Annotations: newJob.Spec.Template.Annotations,
236257
})
237258
if err != nil {
238259
klog.Errorf("Fail event subscribe.: %v", err)
@@ -241,7 +262,7 @@ func NewController(
241262
notifiedJobs[newJob.Name] = isCompletedJob(kubeclientset, newJob)
242263
}
243264
},
244-
DeleteFunc: func(obj interface{}) {
265+
DeleteFunc: func(obj any) {
245266
deletedJob := obj.(*batchv1.Job)
246267
delete(notifiedJobs, deletedJob.Name)
247268
},

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/yutachaos/kube-job-notifier
1+
module github.com/remmercier/kube-job-notifier
22

33
go 1.23
44

main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package main
22

33
import (
44
"flag"
5-
"github.com/yutachaos/kube-job-notifier/pkg/signals"
5+
"os"
6+
"os/user"
7+
"path/filepath"
8+
9+
"github.com/remmercier/kube-job-notifier/pkg/signals"
610
kubeinformers "k8s.io/client-go/informers"
711
"k8s.io/client-go/kubernetes"
812
"k8s.io/client-go/rest"
913
"k8s.io/client-go/tools/clientcmd"
1014
"k8s.io/klog"
11-
"os"
12-
"os/user"
13-
"path/filepath"
1415
)
1516

1617
var (

0 commit comments

Comments
 (0)