mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 03:41:01 +00:00
Implement YAML Map Merge, Overrides, and Sequence Merge Support (#1720)
close #1192
This commit is contained in:
parent
cfdb32ae45
commit
204d05f447
9 changed files with 109 additions and 31 deletions
|
@ -32,34 +32,59 @@ Just add a new section called **variables** like this:
|
||||||
commands: build
|
commands: build
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--
|
## Map merges and overwrites
|
||||||
TODO(1192): Support YAML override and extension
|
|
||||||
|
|
||||||
## Example of YAML override and extension
|
```yaml
|
||||||
|
|
||||||
```yml
|
|
||||||
variables:
|
variables:
|
||||||
&some-plugin-settings
|
- &base-plugin-settings
|
||||||
target: dist
|
target: dist
|
||||||
recursive: false
|
recursive: false
|
||||||
try: true
|
try: true
|
||||||
|
- &special-setting
|
||||||
|
special: true
|
||||||
|
- &some-plugin codeberg.org/6543/docker-images/print_env
|
||||||
|
|
||||||
pipelines:
|
pipeline:
|
||||||
develop:
|
develop:
|
||||||
name: Build and test
|
image: *some-plugin
|
||||||
image: some-plugin
|
settings:
|
||||||
settings: *some-plugin-settings
|
<<: [*base-plugin-settings, *special-setting] # merge two maps into an empty map
|
||||||
when:
|
when:
|
||||||
branch: develop
|
branch: develop
|
||||||
|
|
||||||
main:
|
main:
|
||||||
name: Build and test
|
image: *some-plugin
|
||||||
image: some-plugin
|
|
||||||
settings:
|
settings:
|
||||||
<<: *some-plugin-settings
|
<<: *base-plugin-settings # merge one map and ...
|
||||||
try: false # replacing original value from `some-plugin-settings`
|
try: false # ... overwrite original value
|
||||||
ongoing: false # adding a new value to `some-plugin-settings`
|
ongoing: false # ... adding a new value
|
||||||
when:
|
when:
|
||||||
branch: main
|
branch: main
|
||||||
```
|
```
|
||||||
-->
|
|
||||||
|
## Sequence merges
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
variables:
|
||||||
|
pre_cmds: &pre_cmds
|
||||||
|
- echo start
|
||||||
|
- whoami
|
||||||
|
post_cmds: &post_cmds
|
||||||
|
- echo stop
|
||||||
|
hello_cmd: &hello_cmd
|
||||||
|
- echo hello
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
step1:
|
||||||
|
image: debian
|
||||||
|
commands:
|
||||||
|
- <<: *pre_cmds # prepend a sequence
|
||||||
|
- echo exec step now do dedicated things
|
||||||
|
- <<: *post_cmds # append a sequence
|
||||||
|
step2:
|
||||||
|
image: debian
|
||||||
|
commands:
|
||||||
|
- <<: [*pre_cmds, *hello_cmd] # prepend two sequences
|
||||||
|
- echo echo from second step
|
||||||
|
- <<: *post_cmds
|
||||||
|
```
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -4,7 +4,8 @@ go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0
|
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0
|
||||||
codeberg.org/6543/go-yaml2json v0.3.0
|
codeberg.org/6543/go-yaml2json v1.0.0
|
||||||
|
codeberg.org/6543/xyaml v1.1.0
|
||||||
github.com/alessio/shellescape v1.4.1
|
github.com/alessio/shellescape v1.4.1
|
||||||
github.com/antonmedv/expr v1.12.3
|
github.com/antonmedv/expr v1.12.3
|
||||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -32,8 +32,10 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0 h1:AKpsCoOtVrWWBtANM9319pwCB5ihx1Sdvr1HSbAwr54=
|
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0 h1:AKpsCoOtVrWWBtANM9319pwCB5ihx1Sdvr1HSbAwr54=
|
||||||
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
code.gitea.io/sdk/gitea v0.15.1-0.20221016183512-2d9ee57af1e0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
||||||
codeberg.org/6543/go-yaml2json v0.3.0 h1:BlvjmY0Gous8P+rr8aBdgPYnIfUAqFepF8q7Tp0R5t8=
|
codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig=
|
||||||
codeberg.org/6543/go-yaml2json v0.3.0/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ=
|
codeberg.org/6543/go-yaml2json v1.0.0/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ=
|
||||||
|
codeberg.org/6543/xyaml v1.1.0 h1:0PWTy8OUqshshjrrnAXFWXSPUEa8R49DIh2ah07SxFc=
|
||||||
|
codeberg.org/6543/xyaml v1.1.0/go.mod h1:jI7afXLZUxeL4rNNsG1SlHh78L+gma9lK1bIebyFZwA=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package yaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v3"
|
"codeberg.org/6543/xyaml"
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/constraint"
|
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/constraint"
|
||||||
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/types"
|
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/types"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +36,7 @@ type (
|
||||||
// ParseBytes parses the configuration from bytes b.
|
// ParseBytes parses the configuration from bytes b.
|
||||||
func ParseBytes(b []byte) (*Config, error) {
|
func ParseBytes(b []byte) (*Config, error) {
|
||||||
out := new(Config)
|
out := new(Config)
|
||||||
err := yaml.Unmarshal(b, out)
|
err := xyaml.Unmarshal(b, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,22 @@ pipeline:
|
||||||
repo: foo/bar
|
repo: foo/bar
|
||||||
settings:
|
settings:
|
||||||
foo: bar
|
foo: bar
|
||||||
`}}
|
`}, {
|
||||||
|
Title: "merge maps", Data: `
|
||||||
|
variables:
|
||||||
|
step_template: &base-step
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- go version
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
test base step:
|
||||||
|
<<: *base-step
|
||||||
|
test base step with latest image:
|
||||||
|
<<: *base-step
|
||||||
|
image: golang:latest
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
|
||||||
for _, testd := range testdatas {
|
for _, testd := range testdatas {
|
||||||
t.Run(testd.Title, func(t *testing.T) {
|
t.Run(testd.Title, func(t *testing.T) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
|
|
||||||
pipeline "github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml"
|
pipeline "github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"codeberg.org/6543/xyaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -115,7 +115,7 @@ func parse(raw []byte) (Matrix, error) {
|
||||||
data := struct {
|
data := struct {
|
||||||
Matrix map[string][]string
|
Matrix map[string][]string
|
||||||
}{}
|
}{}
|
||||||
if err := yaml.Unmarshal(raw, &data); err != nil {
|
if err := xyaml.Unmarshal(raw, &data); err != nil {
|
||||||
return nil, &pipeline.PipelineParseError{Err: err}
|
return nil, &pipeline.PipelineParseError{Err: err}
|
||||||
}
|
}
|
||||||
return data.Matrix, nil
|
return data.Matrix, nil
|
||||||
|
@ -128,7 +128,7 @@ func parseList(raw []byte) ([]Axis, error) {
|
||||||
}
|
}
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(raw, &data); err != nil {
|
if err := xyaml.Unmarshal(raw, &data); err != nil {
|
||||||
return nil, &pipeline.PipelineParseError{Err: err}
|
return nil, &pipeline.PipelineParseError{Err: err}
|
||||||
}
|
}
|
||||||
return data.Matrix.Include, nil
|
return data.Matrix.Include, nil
|
||||||
|
|
18
pipeline/schema/.woodpecker/test-merge-map-and-sequence.yml
Normal file
18
pipeline/schema/.woodpecker/test-merge-map-and-sequence.yml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
variables:
|
||||||
|
step_template: &base-step
|
||||||
|
image: golang:1.19
|
||||||
|
commands: &base-cmds
|
||||||
|
- go version
|
||||||
|
- whoami
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
test-base-step:
|
||||||
|
<<: *base-step
|
||||||
|
test base step with latest image:
|
||||||
|
<<: *base-step
|
||||||
|
image: golang:latest
|
||||||
|
test list overwrite:
|
||||||
|
<<: *base-step
|
||||||
|
commands:
|
||||||
|
- <<: *base-cmds
|
||||||
|
- hostname
|
|
@ -1,13 +1,14 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"codeberg.org/6543/go-yaml2json"
|
"codeberg.org/6543/go-yaml2json"
|
||||||
|
"codeberg.org/6543/xyaml"
|
||||||
"github.com/xeipuuv/gojsonschema"
|
"github.com/xeipuuv/gojsonschema"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed schema.json
|
//go:embed schema.json
|
||||||
|
@ -16,13 +17,26 @@ var schemaDefinition []byte
|
||||||
// Lint lints an io.Reader against the Woodpecker schema.json
|
// Lint lints an io.Reader against the Woodpecker schema.json
|
||||||
func Lint(r io.Reader) ([]gojsonschema.ResultError, error) {
|
func Lint(r io.Reader) ([]gojsonschema.ResultError, error) {
|
||||||
schemaLoader := gojsonschema.NewBytesLoader(schemaDefinition)
|
schemaLoader := gojsonschema.NewBytesLoader(schemaDefinition)
|
||||||
buff := new(bytes.Buffer)
|
|
||||||
err := yaml2json.StreamConvert(r, buff)
|
// read yaml config
|
||||||
|
rBytes, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to load yml file %w", err)
|
return nil, fmt.Errorf("Failed to load yml file %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
documentLoader := gojsonschema.NewBytesLoader(buff.Bytes())
|
// resolve sequence merges
|
||||||
|
yamlDoc := new(yaml.Node)
|
||||||
|
if err := xyaml.Unmarshal(rBytes, yamlDoc); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to parse yml file %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to json
|
||||||
|
jsonDoc, err := yaml2json.ConvertNode(yamlDoc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to convert yaml %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
documentLoader := gojsonschema.NewBytesLoader(jsonDoc)
|
||||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Validation failed %w", err)
|
return nil, fmt.Errorf("Validation failed %w", err)
|
||||||
|
|
|
@ -78,6 +78,10 @@ func TestSchema(t *testing.T) {
|
||||||
name: "Labels",
|
name: "Labels",
|
||||||
testFile: ".woodpecker/test-labels.yml",
|
testFile: ".woodpecker/test-labels.yml",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Map and Sequence Merge", // https://woodpecker-ci.org/docs/next/usage/advanced-yaml-syntax
|
||||||
|
testFile: ".woodpecker/test-merge-map-and-sequence.yml",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Broken Config",
|
name: "Broken Config",
|
||||||
testFile: ".woodpecker/test-broken.yml",
|
testFile: ".woodpecker/test-broken.yml",
|
||||||
|
|
Loading…
Reference in a new issue