forked from timoreimann/pdsync
-
Notifications
You must be signed in to change notification settings - Fork 0
/
syncer.go
183 lines (155 loc) · 5.62 KB
/
syncer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package main
import (
"bytes"
"context"
"fmt"
"os"
"strings"
"text/template"
)
type runSlackSync struct {
name string
pdSchedules pdSchedules
slackChannelID string
tmpl *template.Template
dryRun bool
}
type syncerParams struct {
pdClient *pagerDutyClient
slClient *slackMetaClient
slackUsers slackUsers
slackUserGroups UserGroups
}
func (sp syncerParams) createSlackSyncs(ctx context.Context, cfg config) ([]runSlackSync, error) {
var slSyncs []runSlackSync
fmt.Println("Getting Slack channels")
slChannels, err := sp.slClient.getChannels(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get channels: %s", err)
}
fmt.Printf("Got %d Slack channel(s)\n", len(slChannels))
for _, cfgSlSync := range cfg.SlackSyncs {
slSync := runSlackSync{
name: cfgSlSync.Name,
dryRun: cfgSlSync.DryRun,
}
if cfgSlSync.Template == "" {
fmt.Printf("Slack sync %s: skipping topic handling because template is undefined\n", slSync.name)
} else {
var err error
slSync.tmpl, err = template.New("topic").Parse(cfgSlSync.Template)
if err != nil {
return nil, fmt.Errorf("failed to create slack sync %q: failed to parse template %q: %s", slSync.name, cfgSlSync.Template, err)
}
cfgChannel := cfgSlSync.Channel
slChannel := slChannels.find(cfgChannel.ID, cfgChannel.Name)
if slChannel == nil {
return nil, fmt.Errorf("failed to create slack sync %q: failed to find configured Slack channel %s", slSync.name, cfgChannel)
}
slSync.slackChannelID = slChannel.ID
fmt.Printf("Slack sync %s: found Slack channel %q (ID %s)\n", slSync.name, slChannel.Name, slChannel.ID)
}
pdSchedules := pdSchedules{}
fmt.Printf("Slack sync %s: Getting PagerDuty schedules\n", slSync.name)
for _, schedule := range cfgSlSync.Schedules {
pdSchedule, err := sp.pdClient.getSchedule(schedule.ID, schedule.Name)
if err != nil {
return nil, fmt.Errorf("failed to create slack sync %q: failed to get schedule %s: %s", slSync.name, schedule, err)
}
if pdSchedule == nil {
return nil, fmt.Errorf("failed to create slack sync %q: schedule %s not found", slSync.name, schedule)
}
for _, cfgUserGroup := range schedule.UserGroups {
ug := sp.slackUserGroups.find(cfgUserGroup)
if ug == nil {
return nil, fmt.Errorf("failed to create slack sync %q: user group %s not found", slSync.name, cfgUserGroup)
}
fmt.Printf("Slack sync %s: assigning user group %s to schedule %s\n", slSync.name, ug, pdSchedule)
pdSchedule.userGroups = append(pdSchedule.userGroups, *ug)
}
pdSchedules.ensureSchedule(*pdSchedule)
for _, cfgUserGroup := range schedule.UserGroups {
ug := sp.slackUserGroups.find(cfgUserGroup)
if ug == nil {
return nil, fmt.Errorf("failed to create slack sync %q: user group %s not found", slSync.name, ug)
}
}
}
slSync.pdSchedules = pdSchedules
fmt.Printf("Slack sync %s: found %d PagerDuty schedule(s)\n", slSync.name, len(pdSchedules))
slSyncs = append(slSyncs, slSync)
}
return slSyncs, nil
}
type syncer struct {
syncerParams
}
func newSyncer(sp syncerParams) *syncer {
return &syncer{
syncerParams: sp,
}
}
func (s *syncer) Run(ctx context.Context, slackSyncs []runSlackSync) error {
for _, slackSync := range slackSyncs {
err := s.runSlackSync(ctx, slackSync)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to run Slack sync %s: %s\n", slackSync.name, err)
// TODO: aggregate and return errors
continue
}
}
return nil
}
func (s *syncer) runSlackSync(ctx context.Context, slackSync runSlackSync) error {
if !slackSync.dryRun {
joined, err := s.slClient.joinChannel(ctx, slackSync.slackChannelID)
if err != nil {
if strings.Contains(err.Error(), "missing_scope") {
fmt.Printf(`cannot automatically join channel with ID %s because of missing scope "channels:join" -- please add the scope or join pdsync manually`, slackSync.slackChannelID)
} else {
return fmt.Errorf("failed to join channel with ID %s: %s", slackSync.slackChannelID, err)
}
}
if joined {
fmt.Printf("joined channel with ID %s\n", slackSync.slackChannelID)
}
}
ocgs := oncallGroups{}
slackUserIDByScheduleName := map[string]string{}
for _, schedule := range slackSync.pdSchedules {
fmt.Printf("Processing schedule %s\n", schedule)
onCallUser, err := s.pdClient.getOnCallUser(schedule)
if err != nil {
return fmt.Errorf("failed to get on call user for schedule %q: %s", schedule.name, err)
}
slUser := s.slackUsers.findByPDUser(onCallUser)
if slUser == nil {
return fmt.Errorf("failed to find Slack user for PD user %s", pagerDutyUserString(onCallUser))
}
for _, userGroup := range schedule.userGroups {
fmt.Printf("Ensuring member %s for user group %s\n", slUser.id, userGroup)
ocgs.getOrCreate(userGroup).ensureMember(slUser.id)
}
cleanScheduleName := notAlphaNumRE.ReplaceAllString(schedule.name, "")
slackUserIDByScheduleName[cleanScheduleName] = slUser.id
}
if err := s.slClient.updateOncallGroupMembers(ctx, ocgs, slackSync.dryRun); err != nil {
return fmt.Errorf("failed to update on-call user group members: %s", err)
}
if slackSync.tmpl == nil {
fmt.Println("Skipping topic update")
} else {
var buf bytes.Buffer
fmt.Printf("Executing template with Slack user IDs by schedule name: %s\n", slackUserIDByScheduleName)
err := slackSync.tmpl.Execute(&buf, slackUserIDByScheduleName)
if err != nil {
return fmt.Errorf("failed to render template: %s", err)
}
topic := buf.String()
err = s.slClient.updateTopic(ctx, slackSync.slackChannelID, topic, slackSync.dryRun)
if err != nil {
return fmt.Errorf("failed to update topic: %s", err)
}
}
return nil
}