Implement YAML Map Merge, Overrides, and Sequence Merge Support (#1720)

close  #1192
This commit is contained in:
6543 2023-04-29 14:49:41 +02:00 committed by GitHub
parent cfdb32ae45
commit 204d05f447
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 109 additions and 31 deletions

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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
} }

View file

@ -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) {

View file

@ -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

View 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

View file

@ -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)

View file

@ -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",