Skip to content

Commit 0337039

Browse files
authored
Merge pull request #74 from buildkite-plugins/SUP-3346-env-metadata-attributes
SUP-3346 Add the env and metadata attributes
2 parents f35443d + d4e75e6 commit 0337039

File tree

7 files changed

+298
-28
lines changed

7 files changed

+298
-28
lines changed

README.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ steps:
4646
- label: "Triggering pipelines"
4747
plugins:
4848
- monorepo-diff#v1.4.0:
49+
diff: "git diff --name-only HEAD~1"
4950
watch:
5051
- path: "**/*"
5152
skip_path: "folder/file"
@@ -101,7 +102,32 @@ steps:
101102

102103
⚠️ Warning : The user has to explictly state the paths they want to monitor or use wildcards. For instance if a user, is only watching path `app/` changes made to `app/bin` will not trigger the configuration. This is because the subfolder `/bin` was not specified.
103104

104-
### `diff` (optional)
105+
**Example**
106+
107+
```yaml
108+
steps:
109+
- label: "Triggering pipelines with plugin"
110+
plugins:
111+
- monorepo-diff#v1.4.0:
112+
watch:
113+
- path: test/.buildkite/
114+
config: # Required [trigger step configuration]
115+
trigger: test-pipeline # Required [trigger pipeline slug]
116+
- path:
117+
- app/
118+
- app/bin/service/
119+
config:
120+
trigger: "data-generator"
121+
label: ":package: Generate data"
122+
build:
123+
meta_data:
124+
release-version: "1.1"
125+
```
126+
127+
- When changes are detected in the path `test/.buildkite/` it triggers the pipeline `test-pipeline`
128+
- If the changes are made to either `app/` or `app/bin/service/` it triggers the pipeline `data-generator`
129+
130+
#### `diff` (optional)
105131

106132
This will run the script provided to determine the folder changes.
107133
Depending on your use case, you may want to determine the point where the branch occurs
@@ -140,7 +166,24 @@ LATEST_BUILT_TAG=$(git describe --tags --match foo-service-* --abbrev=0)
140166
git diff --name-only "$LATEST_TAG"
141167
```
142168

143-
### `interpolation` (optional)
169+
**Example**
170+
171+
```yaml
172+
steps:
173+
- label: "Triggering pipelines"
174+
plugins:
175+
- monorepo-diff#v1.4.0:
176+
diff: "git diff --name-only HEAD~1"
177+
watch:
178+
- path: "bar-service/"
179+
config:
180+
command: "echo deploy-bar"
181+
- path: "foo-service/"
182+
config:
183+
trigger: "deploy-foo-service"
184+
```
185+
186+
#### `interpolation` (optional)
144187

145188
This controls the pipeline interpolation on upload, and defaults to `true`.
146189
If set to `false` it adds `--no-interpolation` to the `buildkite pipeline upload`,
@@ -157,6 +200,7 @@ steps:
157200
- label: "Triggering pipelines"
158201
plugins:
159202
- monorepo-diff#v1.4.0:
203+
diff: "git diff --name-only HEAD~1"
160204
watch:
161205
- path: "bar-service/"
162206
config:
@@ -178,6 +222,7 @@ steps:
178222
- label: "Triggering pipelines"
179223
plugins:
180224
- monorepo-diff#v1.4.0:
225+
diff: "git diff --name-only HEAD~1"
181226
watch:
182227
- path: "foo-service/"
183228
config:
@@ -199,6 +244,7 @@ steps:
199244
- label: "Triggering pipelines"
200245
plugins:
201246
- monorepo-diff#v1.4.0:
247+
diff: "git diff --name-only HEAD~1"
202248
log_level: "debug" # defaults to "info"
203249
watch:
204250
- path: "foo-service/"

main_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ func TestMain(m *testing.M) {
1212
log.SetLevel(log.DebugLevel)
1313

1414
// set some env variables for using in tests
15-
os.Setenv("BUILDKITE_COMMIT", "123")
16-
os.Setenv("BUILDKITE_MESSAGE", "fix: temp file not correctly deleted")
17-
os.Setenv("BUILDKITE_BRANCH", "go-rewrite")
18-
os.Setenv("env3", "env-3")
19-
os.Setenv("env4", "env-4")
20-
os.Setenv("TEST_MODE", "true")
15+
// given this is a test we can use `_` for the return; our tests will check values
16+
_ = os.Setenv("BUILDKITE_COMMIT", "123")
17+
_ = os.Setenv("BUILDKITE_MESSAGE", "fix: temp file not correctly deleted")
18+
_ = os.Setenv("BUILDKITE_BRANCH", "go-rewrite")
19+
_ = os.Setenv("env3", "env-3")
20+
_ = os.Setenv("env4", "env-4")
21+
_ = os.Setenv("TEST_MODE", "true")
2122

2223
run := m.Run()
2324

pipeline.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ func uploadPipeline(plugin Plugin, generatePipeline PipelineGenerator) (string,
6666
}
6767

6868
pipeline, hasSteps, err := generatePipeline(steps, plugin)
69-
defer os.Remove(pipeline.Name())
70-
7169
if err != nil {
72-
log.Error(err)
7370
return "", []string{}, err
7471
}
72+
defer func() {
73+
if removeErr := os.Remove(pipeline.Name()); removeErr != nil {
74+
log.Errorf("Failed to remove temporary pipeline file: %v", removeErr)
75+
}
76+
}()
7577

7678
if !hasSteps {
7779
// Handle the case where no steps were provided
@@ -96,7 +98,7 @@ func diff(command string) ([]string, error) {
9698

9799
output, err := executeCommand(
98100
env("SHELL", "bash"),
99-
[]string{"-c", strings.Replace(command, "\n", " ", -1)},
101+
[]string{"-c", strings.ReplaceAll(command, "\n", " ")},
100102
)
101103
if err != nil {
102104
return nil, fmt.Errorf("diff command failed: %v", err)

pipeline_test.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import (
1212

1313
func mockGeneratePipeline(steps []Step, plugin Plugin) (*os.File, bool, error) {
1414
mockFile, _ := os.Create("pipeline.txt")
15-
defer mockFile.Close()
15+
defer func() {
16+
_ = mockFile.Close()
17+
}()
1618

1719
return mockFile, true, nil
1820
}
@@ -39,7 +41,9 @@ func TestUploadPipelineCallsBuildkiteAgentCommand(t *testing.T) {
3941
assert.Equal(t, []string{"pipeline", "upload", "pipeline.txt"}, args)
4042
assert.NoError(t, err)
4143

42-
agent.CheckAndClose(t)
44+
if err := agent.CheckAndClose(t); err != nil {
45+
t.Fatal(err)
46+
}
4347
}
4448

4549
func TestUploadPipelineCallsBuildkiteAgentCommandWithInterpolation(t *testing.T) {
@@ -64,7 +68,9 @@ func TestUploadPipelineCallsBuildkiteAgentCommandWithInterpolation(t *testing.T)
6468
assert.Equal(t, []string{"pipeline", "upload", "pipeline.txt", "--no-interpolation"}, args)
6569
assert.NoError(t, err)
6670

67-
agent.CheckAndClose(t)
71+
if err := agent.CheckAndClose(t); err != nil {
72+
t.Fatal(err)
73+
}
6874
}
6975

7076
func TestUploadPipelineCancelsIfThereIsNoDiffOutput(t *testing.T) {
@@ -298,7 +304,8 @@ func TestPipelinesStepsToTrigger(t *testing.T) {
298304
Paths: []string{"watch-path"},
299305
SkipPaths: []string{"watch-path/text.txt"},
300306
Step: Step{Trigger: "service-2"},
301-
}},
307+
},
308+
},
302309
Expected: []Step{
303310
{Trigger: "service-1"},
304311
},
@@ -316,7 +323,8 @@ func TestPipelinesStepsToTrigger(t *testing.T) {
316323
Paths: []string{"*.txt"},
317324
SkipPaths: []string{"*.secret.txt"},
318325
Step: Step{Trigger: "service-2"},
319-
}},
326+
},
327+
},
320328
Expected: []Step{
321329
{Trigger: "service-1"},
322330
},
@@ -334,7 +342,8 @@ func TestPipelinesStepsToTrigger(t *testing.T) {
334342
Paths: []string{"**/*.txt"},
335343
SkipPaths: []string{"docs/*.txt"},
336344
Step: Step{Trigger: "service-2"},
337-
}},
345+
},
346+
},
338347
Expected: []Step{
339348
{Trigger: "service-1"},
340349
},
@@ -353,7 +362,8 @@ func TestPipelinesStepsToTrigger(t *testing.T) {
353362
Paths: []string{"**/*.txt"},
354363
SkipPaths: []string{"docs/*.secret.txt"},
355364
Step: Step{Trigger: "service-2"},
356-
}},
365+
},
366+
},
357367
Expected: []Step{
358368
{Trigger: "service-1"},
359369
{Trigger: "service-2"},
@@ -367,7 +377,8 @@ func TestPipelinesStepsToTrigger(t *testing.T) {
367377
{
368378
SkipPaths: []string{"docs/*.secret.txt"},
369379
Step: Step{Trigger: "service-1"},
370-
}},
380+
},
381+
},
371382
Expected: []Step{},
372383
},
373384
"step is not included if except path is set": {
@@ -447,7 +458,11 @@ func TestGeneratePipeline(t *testing.T) {
447458
pipeline, _, err := generatePipeline(steps, plugin)
448459

449460
require.NoError(t, err)
450-
defer os.Remove(pipeline.Name())
461+
defer func() {
462+
if err := os.Remove(pipeline.Name()); err != nil {
463+
t.Logf("Failed to remove temporary pipeline file: %v", err)
464+
}
465+
}()
451466

452467
got, err := os.ReadFile(pipeline.Name())
453468
require.NoError(t, err)

plugin.go

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"encoding/json"
55
"errors"
6+
"fmt"
67
"net/url"
78
"path"
89
"strings"
@@ -22,6 +23,7 @@ type Plugin struct {
2223
Watch []WatchConfig
2324
RawEnv interface{} `json:"env"`
2425
Env map[string]string
26+
Metadata map[string]string `json:"meta_data"`
2527
RawNotify []map[string]interface{} `json:"notify" yaml:",omitempty"`
2628
Notify []PluginNotify `yaml:"notify,omitempty"`
2729
}
@@ -98,11 +100,12 @@ type Agent map[string]string
98100

99101
// Build is buildkite build definition
100102
type Build struct {
101-
Message string `yaml:"message,omitempty"`
102-
Branch string `yaml:"branch,omitempty"`
103-
Commit string `yaml:"commit,omitempty"`
104-
RawEnv interface{} `json:"env" yaml:",omitempty"`
105-
Env map[string]string `yaml:"env,omitempty"`
103+
Message string `yaml:"message,omitempty"`
104+
Branch string `yaml:"branch,omitempty"`
105+
Commit string `yaml:"commit,omitempty"`
106+
RawEnv interface{} `json:"env" yaml:",omitempty"`
107+
Env map[string]string `yaml:"env,omitempty"`
108+
Metadata map[string]string `json:"meta_data" yaml:"meta_data,omitempty"`
106109
// Notify []Notify `yaml:"notify,omitempty"`
107110
}
108111

@@ -129,6 +132,13 @@ func (plugin *Plugin) UnmarshalJSON(data []byte) error {
129132
plugin.Env = parseResult
130133
plugin.RawEnv = nil
131134

135+
metaDataParseResult, err := parseMetadata(plugin.Metadata)
136+
if err != nil {
137+
return errors.New("failed to parse metadata configuration")
138+
}
139+
140+
plugin.Metadata = metaDataParseResult
141+
132142
setPluginNotify(&plugin.Notify, &plugin.RawNotify)
133143

134144
for i, p := range plugin.Watch {
@@ -189,6 +199,14 @@ func (plugin *Plugin) UnmarshalJSON(data []byte) error {
189199

190200
appendEnv(&plugin.Watch[i], plugin.Env)
191201

202+
// Attempt to parse the metadata after the env's
203+
parsedMetadata, err := parseMetadata(plugin.Metadata)
204+
if err != nil {
205+
return errors.New("failed to parse metadata configuration")
206+
}
207+
208+
appendMetadata(&plugin.Watch[i], parsedMetadata)
209+
192210
p.RawPath = nil
193211
p.RawSkipPath = nil
194212
}
@@ -357,6 +375,22 @@ func appendEnv(watch *WatchConfig, env map[string]string) {
357375
watch.RawSkipPath = nil
358376
}
359377

378+
// appends build metadata
379+
func appendMetadata(watch *WatchConfig, metadata map[string]string) {
380+
if len(metadata) == 0 {
381+
return
382+
}
383+
// Only apply metadata to trigger steps, not command steps
384+
if watch.Step.Trigger != "" {
385+
if watch.Step.Build.Metadata == nil {
386+
watch.Step.Build.Metadata = make(map[string]string)
387+
}
388+
for k, v := range metadata {
389+
watch.Step.Build.Metadata[k] = v
390+
}
391+
}
392+
}
393+
360394
// parse env in format from env=env-value to map[env] = env-value
361395
func parseEnv(raw interface{}) (map[string]string, error) {
362396
if raw == nil {
@@ -385,6 +419,44 @@ func parseEnv(raw interface{}) (map[string]string, error) {
385419
return result, nil
386420
}
387421

422+
// parse metadata in format from key:value to map[key] = value
423+
func parseMetadata(raw interface{}) (map[string]string, error) {
424+
if raw == nil {
425+
return nil, nil
426+
}
427+
428+
switch v := raw.(type) {
429+
case map[string]string:
430+
return v, nil
431+
case map[string]interface{}:
432+
result := make(map[string]string)
433+
for k, val := range v {
434+
result[k] = fmt.Sprintf("%v", val)
435+
}
436+
return result, nil
437+
case []interface{}:
438+
result := make(map[string]string)
439+
for _, item := range v {
440+
str, ok := item.(string)
441+
if !ok {
442+
continue
443+
}
444+
split := strings.SplitN(str, ":", 2)
445+
key := strings.TrimSpace(split[0])
446+
value := ""
447+
if len(split) > 1 {
448+
value = strings.TrimSpace(split[1])
449+
}
450+
if key != "" {
451+
result[key] = value
452+
}
453+
}
454+
return result, nil
455+
default:
456+
return nil, errors.New("failed to parse metadata configuration: unknown type")
457+
}
458+
}
459+
388460
func getPluginName(s string) string {
389461
ref := s
390462
if strings.HasPrefix(ref, "github.com/") && !strings.Contains(ref, "://") {

plugin.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ configuration:
6969
build:
7070
type: object
7171
properties:
72+
meta_data:
73+
type: array
7274
message:
7375
type: string
7476
commit:
@@ -97,5 +99,4 @@ configuration:
9799
type: string
98100
required:
99101
- watch
100-
101102
# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite-plugins/buildkite-plugin-linter/master/lib/plugin-yaml-schema.yml

0 commit comments

Comments
 (0)