Skip to content

Commit 87bc2b8

Browse files
authored
Change to using slack file api for deprecated (#252)
1 parent 5451c92 commit 87bc2b8

File tree

5 files changed

+161
-32
lines changed

5 files changed

+161
-32
lines changed

controller.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
"strings"
1111
"time"
1212

13-
"github.com/remmercier/kube-job-notifier/pkg/monitoring"
14-
"github.com/remmercier/kube-job-notifier/pkg/notification"
1513
"github.com/thoas/go-funk"
14+
"github.com/yutachaos/kube-job-notifier/pkg/monitoring"
15+
"github.com/yutachaos/kube-job-notifier/pkg/notification"
1616
batchv1 "k8s.io/api/batch/v1"
1717
corev1 "k8s.io/api/core/v1"
1818
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -134,6 +134,10 @@ func NewController(
134134
}
135135
}
136136

137+
// Record that start notification was sent (keep false as completion notification will be sent separately)
138+
// Completion notification will be sent in UpdateFunc, so we don't set it here
139+
klog.V(4).Infof("Job %s: Start notification sent, waiting for completion", newJob.Name)
140+
137141
},
138142
UpdateFunc: func(old, new any) {
139143
newJob := new.(*batchv1.Job)
@@ -159,6 +163,20 @@ func NewController(
159163
return
160164
}
161165

166+
// Check if status has changed (only notify when succeeded or failed status changes)
167+
oldSucceeded := oldJob.Status.Succeeded == intTrue
168+
newSucceeded := newJob.Status.Succeeded == intTrue
169+
oldFailed := oldJob.Status.Failed == intTrue
170+
newFailed := newJob.Status.Failed == intTrue
171+
172+
// Skip if status hasn't changed
173+
if oldSucceeded == newSucceeded && oldFailed == newFailed {
174+
klog.V(4).Infof("Job %s: Status unchanged (oldSucceeded=%v, newSucceeded=%v, oldFailed=%v, newFailed=%v), skipping notification",
175+
newJob.Name, oldSucceeded, newSucceeded, oldFailed, newFailed)
176+
return
177+
}
178+
179+
// Skip if completion notification has already been sent
162180
if notifiedJobs[newJob.Name] {
163181
klog.Infof("Job %s: Skipping notification - Already notified", newJob.Name)
164182
return
@@ -176,7 +194,8 @@ func NewController(
176194
return
177195
}
178196

179-
if newJob.Status.Succeeded == intTrue {
197+
// Only notify when succeeded status changes (newly succeeded)
198+
if newJob.Status.Succeeded == intTrue && !oldSucceeded {
180199
klog.Infof("Job succeeded: Name: %s: Status: %v", newJob.Name, newJob.Status)
181200
klog.Infof("Job %s: Starting success notification process", newJob.Name)
182201
jobPod, err := getPodFromControllerUID(kubeclientset, newJob)
@@ -227,7 +246,7 @@ func NewController(
227246
isCompleted := isCompletedJob(kubeclientset, newJob)
228247
klog.Infof("Job %s: Setting notified flag to %t", newJob.Name, isCompleted)
229248
notifiedJobs[newJob.Name] = isCompleted
230-
} else if newJob.Status.Failed == intTrue {
249+
} else if newJob.Status.Failed == intTrue && !oldFailed {
231250
klog.Infof("Job failed: Name: %s: Status: %v", newJob.Name, newJob.Status)
232251
klog.Infof("Job %s: Starting failure notification process", newJob.Name)
233252
jobPod, err := getPodFromControllerUID(kubeclientset, newJob)

go.mod

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

33
go 1.23
44

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"os/user"
77
"path/filepath"
88

9-
"github.com/remmercier/kube-job-notifier/pkg/signals"
9+
"github.com/yutachaos/kube-job-notifier/pkg/signals"
1010
kubeinformers "k8s.io/client-go/informers"
1111
"k8s.io/client-go/kubernetes"
1212
"k8s.io/client-go/rest"

pkg/notification/slack.go

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package notification
22

33
import (
44
"bytes"
5+
"context"
6+
"fmt"
57
"html/template"
68
"os"
79

@@ -10,9 +12,6 @@ import (
1012
)
1113

1214
const (
13-
START = "start"
14-
SUCCESS = "success"
15-
FAILED = "failed"
1615
SlackMessageTemplate = `
1716
{{if .CronJobName}} *CronJobName*: {{.CronJobName}}{{end}}
1817
*JobName*: {{.JobName}}
@@ -39,33 +38,44 @@ var slackColors = map[string]string{
3938

4039
type slackClient interface {
4140
PostMessage(channelID string, options ...slackapi.MsgOption) (string, string, error)
42-
UploadFile(params slackapi.FileUploadParameters) (file *slackapi.File, err error)
41+
UploadFileV2Context(ctx context.Context, params slackapi.UploadFileV2Parameters) (file *slackapi.FileSummary, err error)
42+
GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*slackapi.File, []slackapi.Comment, *slackapi.Paging, error)
43+
GetConversationsContext(ctx context.Context, params *slackapi.GetConversationsParameters) (channels []slackapi.Channel, nextCursor string, err error)
4344
}
4445

4546
type slack struct {
46-
client slackClient
47-
channel string
48-
username string
47+
client slackClient
48+
channel string
49+
channelID string
50+
username string
4951
}
5052

5153
func newSlack() slack {
54+
ctx := context.Background()
5255
token := os.Getenv("SLACK_TOKEN")
5356
if token == "" {
5457
panic("please set slack client")
5558
}
5659

57-
client := slackapi.New(token)
60+
newSlack := slackapi.New(token)
5861

62+
client := slack{
63+
client: newSlack,
64+
}
5965
channel := os.Getenv("SLACK_CHANNEL")
6066

67+
channelID := client.getChannelID(ctx, channel)
68+
if channelID == "" {
69+
channelID = channel
70+
}
71+
client.channelID = channelID
72+
6173
username := os.Getenv("SLACK_USERNAME")
6274

63-
return slack{
64-
client: client,
65-
channel: channel,
66-
username: username,
67-
}
75+
client.username = username
76+
client.channel = channel
6877

78+
return client
6979
}
7080

7181
func (s slack) NotifyStart(messageParam MessageTemplateParam) (err error) {
@@ -252,23 +262,103 @@ func (s slack) notify(attachment slackapi.Attachment) (err error) {
252262
}
253263

254264
func (s slack) uploadLog(param MessageTemplateParam) (file *slackapi.File, err error) {
255-
file, err = s.client.UploadFile(
256-
slackapi.FileUploadParameters{
257-
Title: param.Namespace + "_" + param.JobName,
258-
Content: param.Log,
259-
Filetype: "txt",
260-
Channels: []string{s.channel},
261-
})
265+
ctx := context.Background()
266+
content := param.Log
267+
filename := param.Namespace + "_" + param.JobName + ".txt"
268+
269+
fileSize := len([]byte(content))
270+
if fileSize == 0 {
271+
return nil, fmt.Errorf("file size cannot be 0")
272+
}
273+
274+
if filename == "" {
275+
return nil, fmt.Errorf("filename cannot be empty")
276+
}
277+
278+
if s.channel == "" {
279+
return nil, fmt.Errorf("channel cannot be empty")
280+
}
281+
282+
// Use filename if title is empty
283+
title := param.Namespace + "_" + param.JobName
284+
if title == "" || title == "_" {
285+
title = filename
286+
}
287+
288+
params := slackapi.UploadFileV2Parameters{
289+
Title: title,
290+
Content: content,
291+
FileSize: fileSize,
292+
Filename: filename,
293+
Channel: s.channelID,
294+
}
295+
296+
klog.V(4).Infof("Uploading file: title=%s, filename=%s, fileSize=%d, channel=%s, channelID=%s)", title, filename, fileSize, s.channel, s.channelID)
297+
298+
fileSummary, err := s.client.UploadFileV2Context(ctx, params)
299+
if err != nil {
300+
klog.Errorf("File uploadLog failed: %v (title=%s, filename=%s, fileSize=%d, channel=%s, channelID=%s, contentLength=%d)\n",
301+
err, title, filename, fileSize, s.channel, s.channelID, len(content))
302+
return
303+
}
304+
305+
// Get complete File information from FileSummary to get Permalink
306+
fileInfo, _, _, err := s.client.GetFileInfoContext(ctx, fileSummary.ID, 0, 0)
262307
if err != nil {
263-
klog.Errorf("File uploadLog failed %s\n", err)
308+
klog.Errorf("Get file info failed %s\n", err)
264309
return
265310
}
266311

267-
klog.Infof("File uploadLog successfully %s", file.Name)
268-
return
312+
klog.Infof("File uploadLog successfully %s", fileInfo.Name)
313+
return fileInfo, nil
269314
}
270315

271316
func isNotifyFromEnv(key string) bool {
272317
value := os.Getenv(key)
273318
return value != "false"
274319
}
320+
321+
// getChannelID converts a channel name (e.g., "#channel-name") to a channel ID (e.g., "C1234567890")
322+
// If the input is already a channel ID or lookup fails, it returns the original value
323+
func (s slack) getChannelID(ctx context.Context, channel string) string {
324+
// If channel starts with 'C', 'G', or 'D', it's likely already a channel ID
325+
if len(channel) > 0 && (channel[0] == 'C' || channel[0] == 'G' || channel[0] == 'D') {
326+
return channel
327+
}
328+
329+
// Remove '#' prefix if present
330+
channelName := channel
331+
if len(channelName) > 0 && channelName[0] == '#' {
332+
channelName = channelName[1:]
333+
}
334+
335+
// Try to find the channel by name
336+
params := &slackapi.GetConversationsParameters{
337+
Types: []string{"public_channel", "private_channel"},
338+
Limit: 1000,
339+
Cursor: "",
340+
}
341+
342+
for {
343+
channels, nextCursor, err := s.client.GetConversationsContext(ctx, params)
344+
if err != nil {
345+
klog.V(4).Infof("Failed to get conversations: %v", err)
346+
return ""
347+
}
348+
349+
for _, ch := range channels {
350+
if ch.Name == channelName {
351+
klog.V(4).Infof("Found channel ID %s for channel name %s", ch.ID, channelName)
352+
return ch.ID
353+
}
354+
}
355+
356+
if nextCursor == "" {
357+
break
358+
}
359+
params.Cursor = nextCursor
360+
}
361+
362+
klog.V(4).Infof("Channel ID not found for channel name %s", channelName)
363+
return ""
364+
}

pkg/notification/slack_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package notification
22

33
import (
4+
"context"
45
"os"
56
"testing"
67
"time"
@@ -330,9 +331,28 @@ func (c *MockSlackClient) PostMessage(channelID string, options ...slackapi.MsgO
330331
return args.String(0), args.String(1), args.Error(2)
331332
}
332333

333-
func (c *MockSlackClient) UploadFile(params slackapi.FileUploadParameters) (file *slackapi.File, err error) {
334-
args := c.Called(params)
335-
return args.Get(0).(*slackapi.File), args.Error(1)
334+
func (c *MockSlackClient) UploadFileV2Context(ctx context.Context, params slackapi.UploadFileV2Parameters) (file *slackapi.FileSummary, err error) {
335+
args := c.Called(ctx, params)
336+
if args.Get(0) == nil {
337+
return nil, args.Error(1)
338+
}
339+
return args.Get(0).(*slackapi.FileSummary), args.Error(1)
340+
}
341+
342+
func (c *MockSlackClient) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*slackapi.File, []slackapi.Comment, *slackapi.Paging, error) {
343+
args := c.Called(ctx, fileID, count, page)
344+
if args.Get(0) == nil {
345+
return nil, nil, nil, args.Error(3)
346+
}
347+
return args.Get(0).(*slackapi.File), args.Get(1).([]slackapi.Comment), args.Get(2).(*slackapi.Paging), args.Error(3)
348+
}
349+
350+
func (c *MockSlackClient) GetConversationsContext(ctx context.Context, params *slackapi.GetConversationsParameters) (channels []slackapi.Channel, nextCursor string, err error) {
351+
args := c.Called(ctx, params)
352+
if args.Get(0) == nil {
353+
return nil, "", args.Error(2)
354+
}
355+
return args.Get(0).([]slackapi.Channel), args.String(1), args.Error(2)
336356
}
337357

338358
func TestGetSlackMessage(t *testing.T) {

0 commit comments

Comments
 (0)