Skip to content

Commit

Permalink
Allow fields starting with x- in the spec
Browse files Browse the repository at this point in the history
This allows clients to store extra data in the spec without having to
loosen up unmarshalling validations.

No "x-" field is explicitly added to the spec because frontends should
not be interpreting this field.
Instead the fields are stripped from the input before unmarshalling to
`Spec`.

Signed-off-by: Brian Goff <[email protected]>
  • Loading branch information
cpuguy83 committed Feb 21, 2024
1 parent 13302e8 commit e7ef9a4
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
25 changes: 25 additions & 0 deletions docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,31 @@ targets:
image: docker.io/my/custom:mariner2
```

### Metadata

You can include client-side metadata in the spec file.
This may be useful when you want to parse the spec file and do something with your own tooling.

Any field at the top-level that begins with `x-` will be ignored by the dalec parser.
Any unknown fields besides those that start with `x-` will cause the parser to fail.

```yaml
soruces:
src:
http:
url: https://example.com/foo.tar.gz
x-my-custom-field: "foo"
```

As an example use-case, you may want to use this to store the targeted image name for a CI/CD pipeline.

```yaml
x-image-name: "my-package-image:1.0.0"
```

Your CI/CD tooling can then parse the spec file and use the `x-image-name` field to tag the built image.

## Additional Reading

* Details on editor support in [editor-support.md](editor-support.md)
Expand Down
21 changes: 21 additions & 0 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ func (s *Spec) SubstituteArgs(env map[string]string) error {
// LoadSpec loads a spec from the given data.
func LoadSpec(dt []byte) (*Spec, error) {
var spec Spec

dt, err := stripXFields(dt)
if err != nil {
return nil, fmt.Errorf("error stripping x-fields: %w", err)
}

if err := yaml.UnmarshalWithOptions(dt, &spec, yaml.Strict()); err != nil {
return nil, fmt.Errorf("error unmarshalling spec: %w", err)
}
Expand All @@ -304,6 +310,21 @@ func LoadSpec(dt []byte) (*Spec, error) {
return &spec, nil
}

func stripXFields(dt []byte) ([]byte, error) {
var obj map[string]interface{}
if err := yaml.Unmarshal(dt, &obj); err != nil {
return nil, fmt.Errorf("error unmarshalling spec: %w", err)
}

for k := range obj {
if strings.HasPrefix(k, "x-") {
delete(obj, k)
}
}

return yaml.Marshal(obj)
}

func (s *BuildStep) processBuildArgs(lex *shell.Lex, args map[string]string, i int) error {
for k, v := range s.Env {
updated, err := lex.ProcessWordWithMap(v, args)
Expand Down
50 changes: 50 additions & 0 deletions load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,53 @@ func TestSourceNameWithPathSeparator(t *testing.T) {
t.Errorf("expected error to be sourceNamePathSeparatorError, got: %v", err)
}
}

func TestUnmarshal(t *testing.T) {
t.Run("x-fields are stripped from spec", func(t *testing.T) {
dt := []byte(`
sources:
test:
inline:
file:
contents: "Hello world!"
x-some-field: "some value"
x-some-other-field: "some other value"
`)

spec, err := LoadSpec(dt)
if err != nil {
t.Fatal(err)
}

src, ok := spec.Sources["test"]
if !ok {
t.Fatal("expected source to be present")
}

if src.Inline == nil {
t.Fatal("expected inline source to be present")
}

if src.Inline.File == nil {
t.Fatal("expected inline file to be present")
}

const xContents = "Hello world!"
if src.Inline.File.Contents != xContents {
t.Fatalf("expected %q, got %s", xContents, src.Inline.File.Contents)
}
})

t.Run("unknown fields cause parse error", func(t *testing.T) {
dt := []byte(`
sources:
test:
noSuchField: "some value"
`)

_, err := LoadSpec(dt)
if err == nil {
t.Fatal("expected error, but received none")
}
})
}

0 comments on commit e7ef9a4

Please sign in to comment.