Skip to content

Commit 8128503

Browse files
authored
Merge pull request #151 from buildkite-plugins/SUP-6058-artifact-paths-field-support
Support both 'artifacts' and 'artifact_paths' fields
2 parents 60eb175 + 88da08b commit 8128503

6 files changed

Lines changed: 165 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
9+
### Added
10+
* Accept both `artifact_paths` (preferred) and `artifacts` (alternative) field names in plugin configuration for backward compatibility (#120)
11+
12+
### Fixed
13+
* Fix `artifact_paths` field being ignored when specified in plugin configuration. Both `artifact_paths` and `artifacts` now work correctly (#120)
14+
* Fix typo in test case: "artifiact" → "artifact"
15+
* Fix BATS test failures caused by unbound `BUILDKITE_PLUGINS_PATH` variable in hooks/command
16+
717
## [v1.6.0]
818

919
### Added

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,26 @@ steps:
449449
wait: true
450450
```
451451

452+
**Note:** This plugin accepts both `artifact_paths` and `artifacts` field names for backward compatibility:
453+
454+
```yaml
455+
# Preferred - use "artifact_paths":
456+
- path: "app/"
457+
config:
458+
command: "npm test"
459+
artifact_paths:
460+
- "logs/**/*"
461+
462+
# Also supported - "artifacts":
463+
- path: "app/"
464+
config:
465+
command: "npm test"
466+
artifacts:
467+
- "logs/**/*"
468+
```
469+
470+
Both field names are supported by Buildkite. The generated pipeline YAML will use `artifact_paths`.
471+
452472
### `binary_folder` (optional)
453473

454474
Default: `BUILDKITE_PLUGINS_PATH`

hooks/command

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ err() {
1717
exit 1
1818
}
1919

20-
executable_dir="${BUILDKITE_PLUGIN_MONOREPO_DIFF_BINARY_FOLDER:-${BUILDKITE_PLUGINS_PATH}}"
20+
executable_dir="${BUILDKITE_PLUGIN_MONOREPO_DIFF_BINARY_FOLDER:-${BUILDKITE_PLUGINS_PATH:-}}"
2121

2222
# Default to current directory if not set
2323
if [[ -z "${executable_dir}" || ${BUILDKITE_PLUGIN_MONOREPO_DIFF_BUILDKITE_PLUGIN_TEST_MODE:-false} == "true" ]]; then

plugin.go

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,66 @@ type StepNotify struct {
7676

7777
// Step is buildkite pipeline definition
7878
type Step struct {
79-
Group string `yaml:"group,omitempty"`
80-
Trigger string `yaml:"trigger,omitempty"`
81-
Label string `yaml:"label,omitempty"`
82-
Branches string `yaml:"branches,omitempty"`
83-
Condition string `json:"if,omitempty" yaml:"if,omitempty"`
84-
Build Build `yaml:"build,omitempty"`
85-
Command interface{} `yaml:"command,omitempty"`
86-
Commands interface{} `yaml:"commands,omitempty"`
87-
Agents Agent `yaml:"agents,omitempty"`
88-
Artifacts []string `yaml:"artifacts,omitempty"`
89-
RawEnv interface{} `json:"env" yaml:",omitempty"`
90-
Plugins []map[string]interface{} `json:"plugins,omitempty" yaml:"plugins,omitempty"`
91-
Env map[string]string `yaml:"env,omitempty"`
92-
Async bool `yaml:"async,omitempty"`
93-
SoftFail interface{} `json:"soft_fail" yaml:"soft_fail,omitempty"`
94-
RawNotify []map[string]interface{} `json:"notify" yaml:",omitempty"`
95-
Notify []StepNotify `yaml:"notify,omitempty"`
96-
DependsOn interface{} `json:"depends_on" yaml:"depends_on,omitempty"`
97-
Key string `yaml:"key,omitempty"`
98-
Secrets interface{} `json:"secrets,omitempty" yaml:"secrets,omitempty"`
99-
Steps []Step `yaml:"steps,omitempty"`
79+
Group string `yaml:"group,omitempty"`
80+
Trigger string `yaml:"trigger,omitempty"`
81+
Label string `yaml:"label,omitempty"`
82+
Branches string `yaml:"branches,omitempty"`
83+
Condition string `json:"if,omitempty" yaml:"if,omitempty"`
84+
Build Build `yaml:"build,omitempty"`
85+
Command interface{} `yaml:"command,omitempty"`
86+
Commands interface{} `yaml:"commands,omitempty"`
87+
Agents Agent `yaml:"agents,omitempty"`
88+
ArtifactPaths []string `json:"artifact_paths" yaml:"artifact_paths,omitempty"`
89+
RawEnv interface{} `json:"env" yaml:",omitempty"`
90+
Plugins []map[string]interface{} `json:"plugins,omitempty" yaml:"plugins,omitempty"`
91+
Env map[string]string `yaml:"env,omitempty"`
92+
Async bool `yaml:"async,omitempty"`
93+
SoftFail interface{} `json:"soft_fail" yaml:"soft_fail,omitempty"`
94+
RawNotify []map[string]interface{} `json:"notify" yaml:",omitempty"`
95+
Notify []StepNotify `yaml:"notify,omitempty"`
96+
DependsOn interface{} `json:"depends_on" yaml:"depends_on,omitempty"`
97+
Key string `yaml:"key,omitempty"`
98+
Secrets interface{} `json:"secrets,omitempty" yaml:"secrets,omitempty"`
99+
Steps []Step `yaml:"steps,omitempty"`
100+
}
101+
102+
// UnmarshalJSON handles both "artifacts" and "artifact_paths" field names for backward compatibility
103+
// Both fields are supported by the Buildkite API; "artifact_paths" is preferred per documentation
104+
func (step *Step) UnmarshalJSON(data []byte) error {
105+
// Check which fields are present without full unmarshaling
106+
var fieldCheck map[string]json.RawMessage
107+
if err := json.Unmarshal(data, &fieldCheck); err != nil {
108+
return err
109+
}
110+
111+
_, hasArtifacts := fieldCheck["artifacts"]
112+
_, hasArtifactPaths := fieldCheck["artifact_paths"]
113+
114+
// Validate that both fields are not specified
115+
if hasArtifacts && hasArtifactPaths {
116+
return errors.New("cannot specify both 'artifacts' and 'artifact_paths'; please use 'artifact_paths'")
117+
}
118+
119+
// Use a type alias to avoid infinite recursion
120+
type stepAlias Step
121+
122+
// Unmarshal the main struct (this will populate artifact_paths if present)
123+
if err := json.Unmarshal(data, (*stepAlias)(step)); err != nil {
124+
return err
125+
}
126+
127+
// If only "artifacts" was specified, manually extract and use it
128+
if hasArtifacts && !hasArtifactPaths {
129+
var temp struct {
130+
Artifacts []string `json:"artifacts"`
131+
}
132+
if err := json.Unmarshal(data, &temp); err != nil {
133+
return err
134+
}
135+
step.ArtifactPaths = temp.Artifacts
136+
}
137+
138+
return nil
100139
}
101140

102141
// Agent is Buildkite agent definition
@@ -124,7 +163,9 @@ func (plugin *Plugin) UnmarshalJSON(data []byte) error {
124163
Interpolation: true,
125164
}
126165

127-
_ = json.Unmarshal(data, def)
166+
if err := json.Unmarshal(data, def); err != nil {
167+
return err
168+
}
128169

129170
*plugin = Plugin(*def)
130171

@@ -235,7 +276,7 @@ func initializePlugin(data string) (Plugin, error) {
235276

236277
if err := json.Unmarshal(pluginConfig, &plugin); err != nil {
237278
log.Debug(err)
238-
return Plugin{}, errors.New("failed to parse plugin configuration")
279+
return Plugin{}, err
239280
}
240281

241282
return plugin, nil

plugin.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ configuration:
9494
type: string
9595
artifacts:
9696
type: array
97+
description: Artifact paths to upload (alternative field name)
98+
artifact_paths:
99+
type: array
100+
description: Artifact paths to upload (preferred field name)
97101
env:
98102
type: array
99103
wait:

plugin_test.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func TestPluginShouldUnmarshallCorrectly(t *testing.T) {
154154
"queue": "queue-1",
155155
"database": "postgres"
156156
},
157-
"artifacts": [ "artifiact-1" ],
157+
"artifacts": [ "artifact-1" ],
158158
"soft_fail": [{
159159
"exit_status": 127
160160
}]
@@ -266,9 +266,9 @@ func TestPluginShouldUnmarshallCorrectly(t *testing.T) {
266266
"env3": "env-3",
267267
},
268268
},
269-
Async: true,
270-
Agents: map[string]string{"queue": "queue-1", "database": "postgres"},
271-
Artifacts: []string{"artifiact-1"},
269+
Async: true,
270+
Agents: map[string]string{"queue": "queue-1", "database": "postgres"},
271+
ArtifactPaths: []string{"artifact-1"},
272272
SoftFail: []interface{}{map[string]interface{}{
273273
"exit_status": float64(127),
274274
}},
@@ -1221,3 +1221,65 @@ func TestPluginShouldPreserveSecretsInNestedSteps(t *testing.T) {
12211221
assert.True(t, ok, "second step secrets should be an array")
12221222
assert.Equal(t, []interface{}{"PROD_DB_HOST", "PROD_DB_PASS"}, secretsArray)
12231223
}
1224+
1225+
func TestPluginAcceptsArtifactPathsFieldName(t *testing.T) {
1226+
// Test that artifact_paths field name works
1227+
param := `[{
1228+
"github.com/buildkite-plugins/monorepo-diff-buildkite-plugin#commit": {
1229+
"watch": [
1230+
{
1231+
"path": "service/**/*",
1232+
"config": {
1233+
"command": "echo test",
1234+
"artifact_paths": ["logs/**/*", "coverage/**/*"]
1235+
}
1236+
}
1237+
]
1238+
}
1239+
}]`
1240+
1241+
got, err := initializePlugin(param)
1242+
assert.NoError(t, err)
1243+
1244+
expected := Plugin{
1245+
Diff: "git diff --name-only HEAD~1",
1246+
Wait: false,
1247+
LogLevel: "info",
1248+
Interpolation: true,
1249+
Watch: []WatchConfig{
1250+
{
1251+
Paths: []string{"service/**/*"},
1252+
Step: Step{
1253+
Command: "echo test",
1254+
ArtifactPaths: []string{"logs/**/*", "coverage/**/*"},
1255+
},
1256+
},
1257+
},
1258+
}
1259+
1260+
if diff := cmp.Diff(expected, got); diff != "" {
1261+
t.Fatalf("plugin diff (-want +got):\n%s", diff)
1262+
}
1263+
}
1264+
1265+
func TestPluginRejectsBothArtifactsFields(t *testing.T) {
1266+
// Test that specifying both fields returns an error
1267+
param := `[{
1268+
"github.com/buildkite-plugins/monorepo-diff-buildkite-plugin#commit": {
1269+
"watch": [
1270+
{
1271+
"path": "service/**/*",
1272+
"config": {
1273+
"command": "echo test",
1274+
"artifacts": ["old.log"],
1275+
"artifact_paths": ["new.log"]
1276+
}
1277+
}
1278+
]
1279+
}
1280+
}]`
1281+
1282+
_, err := initializePlugin(param)
1283+
assert.Error(t, err)
1284+
assert.Contains(t, err.Error(), "cannot specify both 'artifacts' and 'artifact_paths'")
1285+
}

0 commit comments

Comments
 (0)