Skip to content
This repository was archived by the owner on Jun 25, 2024. It is now read-only.

Commit 8abb368

Browse files
authored
add notification in root level (#125)
* fix step notifications * generate pipeline with top level notify * parse pipeline notification from config * update plugin definition
1 parent 63ea4d2 commit 8abb368

File tree

7 files changed

+210
-106
lines changed

7 files changed

+210
-106
lines changed

.buildkite/pipeline.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ steps:
99
config:
1010
trigger: "foo-service"
1111
notify:
12-
13-
- slack: "@monebag"
14-
- webhook: "https://webhook.com"
12+
- slack: "#alerts"
1513
- path: "hello-service/"
1614
config:
1715
trigger: "this-pipeline-does-not-exists"
@@ -23,13 +21,13 @@ steps:
2321
plugins:
2422
- monebag/monorepo-diff:
2523
diff: "cat ./e2e/multiple-paths"
24+
notify:
25+
2626
watch:
2727
- path:
2828
- "user-service/infrastructure/"
2929
config:
3030
command: "echo i-am-running-in-a-group"
31-
notify:
32-
3331

3432
- label: ":bomb: Testing groups"
3533
plugins:

pipeline.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ func (WaitStep) MarshalYAML() (interface{}, error) {
2424
}, nil
2525
}
2626

27+
func (s Step) MarshalYAML() (interface{}, error) {
28+
if s.Group == "" {
29+
type Alias Step
30+
return (Alias)(s), nil
31+
}
32+
33+
label := s.Group
34+
s.Group = ""
35+
return Group{Label: label, Steps: []Step{s}}, nil
36+
}
37+
38+
func (n PluginNotify) MarshalYAML() (interface{}, error) {
39+
return n, nil
40+
}
41+
2742
// PipelineGenerator generates pipeline file
2843
type PipelineGenerator func(steps []Step, plugin Plugin) (*os.File, error)
2944

@@ -151,6 +166,7 @@ func generatePipeline(steps []Step, plugin Plugin) (*os.File, error) {
151166
}
152167

153168
yamlSteps := make([]yaml.Marshaler, len(steps))
169+
154170
for i, step := range steps {
155171
yamlSteps[i] = step
156172
}
@@ -163,10 +179,19 @@ func generatePipeline(steps []Step, plugin Plugin) (*os.File, error) {
163179
yamlSteps = append(yamlSteps, Step{Command: cmd.Command})
164180
}
165181

182+
yamlNotify := make([]yaml.Marshaler, len(plugin.Notify))
183+
for i, n := range plugin.Notify {
184+
yamlNotify[i] = n
185+
}
186+
166187
pipeline := map[string][]yaml.Marshaler{
167188
"steps": yamlSteps,
168189
}
169190

191+
if len(yamlNotify) > 0 {
192+
pipeline["notify"] = yamlNotify
193+
}
194+
170195
data, err := yaml.Marshal(&pipeline)
171196
if err != nil {
172197
return nil, fmt.Errorf("could not serialize the pipeline: %v", err)

pipeline_test.go

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -228,19 +228,15 @@ func TestGeneratePipeline(t *testing.T) {
228228
Trigger: "foo-service-pipeline",
229229
Build: Build{Message: "build message"},
230230
SoftFail: true,
231-
Notify: []Notify{
232-
{Email: "[email protected]"},
233-
{Email: "[email protected]"},
231+
Notify: []StepNotify{
232+
{Slack: "@adikari"},
234233
},
235234
},
236235
{
237236
Trigger: "notification-test",
238237
Command: "command-to-run",
239-
Notify: []Notify{
240-
{Email: "[email protected]"},
238+
Notify: []StepNotify{
241239
{Basecamp: "https://basecamp-url"},
242-
{Webhook: "https://webhook-url"},
243-
{PagerDuty: "636d22Yourc0418Key3b49eee3e8"},
244240
{GithubStatus: GithubStatusNotification{Context: "my-custom-status"}},
245241
{Slack: "@someuser", Condition: "build.state === \"passed\""},
246242
},
@@ -252,22 +248,50 @@ func TestGeneratePipeline(t *testing.T) {
252248
},
253249
}
254250

251+
plugin := Plugin{
252+
Wait: true,
253+
Notify: []PluginNotify{
254+
{Email: "[email protected]"},
255+
{Email: "[email protected]"},
256+
{Basecamp: "https://basecamp"},
257+
{Webhook: "https://webhook"},
258+
{Slack: "@adikari"},
259+
{GithubStatus: GithubStatusNotification{Context: "github-context"}},
260+
},
261+
Hooks: []HookConfig{
262+
{Command: "echo \"hello world\""},
263+
{Command: "cat ./file.txt"},
264+
},
265+
}
266+
267+
pipeline, err := generatePipeline(steps, plugin)
268+
269+
require.NoError(t, err)
270+
defer os.Remove(pipeline.Name())
271+
272+
got, err := ioutil.ReadFile(pipeline.Name())
273+
require.NoError(t, err)
274+
255275
want :=
256-
`steps:
276+
`notify:
277+
278+
279+
- basecamp_campfire: https://basecamp
280+
- webhook: https://webhook
281+
- slack: '@adikari'
282+
- github_commit_status:
283+
context: github-context
284+
steps:
257285
- trigger: foo-service-pipeline
258286
build:
259287
message: build message
260288
soft_fail: true
261289
notify:
262-
263-
290+
- slack: '@adikari'
264291
- trigger: notification-test
265292
command: command-to-run
266293
notify:
267-
268294
- basecamp_campfire: https://basecamp-url
269-
- webhook: https://webhook-url
270-
- pagerduty_change_event: 636d22Yourc0418Key3b49eee3e8
271295
- github_commit_status:
272296
context: my-custom-status
273297
- slack: '@someuser'
@@ -282,21 +306,6 @@ func TestGeneratePipeline(t *testing.T) {
282306
- command: cat ./file.txt
283307
`
284308

285-
plugin := Plugin{
286-
Wait: true,
287-
Hooks: []HookConfig{
288-
{Command: "echo \"hello world\""},
289-
{Command: "cat ./file.txt"},
290-
},
291-
}
292-
293-
pipeline, err := generatePipeline(steps, plugin)
294-
require.NoError(t, err)
295-
defer os.Remove(pipeline.Name())
296-
297-
got, err := ioutil.ReadFile(pipeline.Name())
298-
require.NoError(t, err)
299-
300309
assert.Equal(t, want, string(got))
301310
}
302311

plugin.go

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type Plugin struct {
2020
Watch []WatchConfig
2121
RawEnv interface{} `json:"env"`
2222
Env map[string]string
23+
RawNotify []map[string]interface{} `json:"notify" yaml:",omitempty"`
24+
Notify []PluginNotify `yaml:"notify,omitempty"`
2325
}
2426

2527
// HookConfig Plugin hook configuration
@@ -39,19 +41,28 @@ type Group struct {
3941
Steps []Step `yaml:"steps"`
4042
}
4143

44+
// GithubStatusNotification is notification config for github_commit_status
4245
type GithubStatusNotification struct {
4346
Context string `yaml:"context,omitempty"`
4447
}
4548

46-
// Notify is Buildkite notification definition
47-
type Notify struct {
48-
Email string `yaml:"email,omitempty"`
49-
Basecamp string `yaml:"basecamp_campfire,omitempty"`
49+
// PluginNotify is notify configuration for pipeline
50+
type PluginNotify struct {
5051
Slack string `yaml:"slack,omitempty"`
51-
Webhook string `yaml:"webhook,omitempty"`
52+
Email string `yaml:"email,omitempty"`
5253
PagerDuty string `yaml:"pagerduty_change_event,omitempty"`
54+
Webhook string `yaml:"webhook,omitempty"`
55+
Basecamp string `yaml:"basecamp_campfire,omitempty"`
56+
GithubStatus GithubStatusNotification `yaml:"github_commit_status,omitempty"`
5357
Condition string `yaml:"if,omitempty"`
58+
}
59+
60+
// Notify is Buildkite notification definition
61+
type StepNotify struct {
62+
Slack string `yaml:"slack,omitempty"`
63+
Basecamp string `yaml:"basecamp_campfire,omitempty"`
5464
GithubStatus GithubStatusNotification `yaml:"github_commit_status,omitempty"`
65+
Condition string `yaml:"if,omitempty"`
5566
}
5667

5768
// Step is buildkite pipeline definition
@@ -69,7 +80,7 @@ type Step struct {
6980
Async bool `yaml:"async,omitempty"`
7081
SoftFail interface{} `json:"soft_fail" yaml:"soft_fail,omitempty"`
7182
RawNotify []map[string]interface{} `json:"notify" yaml:",omitempty"`
72-
Notify []Notify `yaml:"notify,omitempty"`
83+
Notify []StepNotify `yaml:"notify,omitempty"`
7384
}
7485

7586
// Agent is Buildkite agent definition
@@ -85,45 +96,6 @@ type Build struct {
8596
// Notify []Notify `yaml:"notify,omitempty"`
8697
}
8798

88-
func (s Step) MarshalYAML() (interface{}, error) {
89-
if s.Group == "" {
90-
type Alias Step
91-
return (Alias)(s), nil
92-
}
93-
94-
label := s.Group
95-
s.Group = ""
96-
return Group{Label: label, Steps: []Step{s}}, nil
97-
}
98-
99-
func initializePlugin(data string) (Plugin, error) {
100-
log.Debugf("parsing plugin config: %v", data)
101-
102-
var pluginConfigs []map[string]json.RawMessage
103-
104-
if err := json.Unmarshal([]byte(data), &pluginConfigs); err != nil {
105-
log.Debug(err)
106-
return Plugin{}, errors.New("failed to parse plugin configuration")
107-
}
108-
109-
for _, p := range pluginConfigs {
110-
for key, pluginConfig := range p {
111-
if strings.HasPrefix(key, pluginName) {
112-
var plugin Plugin
113-
114-
if err := json.Unmarshal(pluginConfig, &plugin); err != nil {
115-
log.Debug(err)
116-
return Plugin{}, errors.New("failed to parse plugin configuration")
117-
}
118-
119-
return plugin, nil
120-
}
121-
}
122-
}
123-
124-
return Plugin{}, errors.New("could not initialize plugin")
125-
}
126-
12799
// UnmarshalJSON set defaults properties
128100
func (plugin *Plugin) UnmarshalJSON(data []byte) error {
129101
type plain Plugin
@@ -147,6 +119,8 @@ func (plugin *Plugin) UnmarshalJSON(data []byte) error {
147119
plugin.Env = parseResult
148120
plugin.RawEnv = nil
149121

122+
setPluginNotify(&plugin.Notify, &plugin.RawNotify)
123+
150124
// Path can be string or an array of strings,
151125
// handle both cases and create an array of paths.
152126
for i, p := range plugin.Watch {
@@ -175,9 +149,37 @@ func (plugin *Plugin) UnmarshalJSON(data []byte) error {
175149
return nil
176150
}
177151

178-
func setNotify(notifications *[]Notify, rawNotify *[]map[string]interface{}) {
152+
func initializePlugin(data string) (Plugin, error) {
153+
log.Debugf("parsing plugin config: %v", data)
154+
155+
var pluginConfigs []map[string]json.RawMessage
156+
157+
if err := json.Unmarshal([]byte(data), &pluginConfigs); err != nil {
158+
log.Debug(err)
159+
return Plugin{}, errors.New("failed to parse plugin configuration")
160+
}
161+
162+
for _, p := range pluginConfigs {
163+
for key, pluginConfig := range p {
164+
if strings.HasPrefix(key, pluginName) {
165+
var plugin Plugin
166+
167+
if err := json.Unmarshal(pluginConfig, &plugin); err != nil {
168+
log.Debug(err)
169+
return Plugin{}, errors.New("failed to parse plugin configuration")
170+
}
171+
172+
return plugin, nil
173+
}
174+
}
175+
}
176+
177+
return Plugin{}, errors.New("could not initialize plugin")
178+
}
179+
180+
func setPluginNotify(notifications *[]PluginNotify, rawNotify *[]map[string]interface{}) {
179181
for _, v := range *rawNotify {
180-
var notify Notify
182+
var notify PluginNotify
181183

182184
if condition, ok := isString(v["if"]); ok {
183185
notify.Condition = condition
@@ -225,6 +227,38 @@ func setNotify(notifications *[]Notify, rawNotify *[]map[string]interface{}) {
225227
*rawNotify = nil
226228
}
227229

230+
func setNotify(notifications *[]StepNotify, rawNotify *[]map[string]interface{}) {
231+
for _, v := range *rawNotify {
232+
var notify StepNotify
233+
234+
if condition, ok := isString(v["if"]); ok {
235+
notify.Condition = condition
236+
}
237+
238+
if basecamp, ok := isString(v["basecamp_campfire"]); ok {
239+
notify.Basecamp = basecamp
240+
*notifications = append(*notifications, notify)
241+
continue
242+
}
243+
244+
if slack, ok := isString(v["slack"]); ok {
245+
notify.Slack = slack
246+
*notifications = append(*notifications, notify)
247+
continue
248+
}
249+
250+
if github, ok := v["github_commit_status"].(map[string]interface{}); ok {
251+
if context, ok := isString(github["context"]); ok {
252+
notify.GithubStatus = GithubStatusNotification{Context: context}
253+
*notifications = append(*notifications, notify)
254+
}
255+
continue
256+
}
257+
}
258+
259+
*rawNotify = nil
260+
}
261+
228262
func setBuild(build *Build) {
229263
if build.Message == "" {
230264
build.Message = env("BUILDKITE_MESSAGE", "")

0 commit comments

Comments
 (0)