Skip to content

Commit

Permalink
feat: Support relative files in ref
Browse files Browse the repository at this point in the history
  • Loading branch information
dadav committed Sep 21, 2024
1 parent f033847 commit fe17fcd
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 24 deletions.
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ foo: bar
| [`allOf`](#allof) | Accepts an array of schemas. All must apply| Takes an `array` |
| [`not`](#not) | A schema that must not be matched. | Takes an `object` |
| [`if/then/else`](#ifthenelse) | `if` the given schema applies, `then` also apply the given schema or `else` the other schema| Takes an `object` |
| `$ref` | Accepts a URL to a valid `jsonschema`. Extend the schema for the current key | Takes an URL |
| [`$ref`](#ref) | Accepts a URI to a valid `jsonschema`. Extend the schema for the current key | Takes an URI (or relative file) |
| [`minLength`](#minlength) | Minimum string length. | Takes an `integer`. Must be smaller or equal than `maxLength` (if used) |
| [`maxLength`](#maxlength) | Maximum string length. | Takes an `integer`. Must be greater or equal than `minLength` (if used) |

Expand Down Expand Up @@ -710,6 +710,41 @@ The value must be an integer greater than zero and defines the maximum length of
namespace: foo
```

#### `$ref`

The value must be an URI or relative file.

Relative files are imported on creation time. If you update the referenced file, you need
to run helm-schema again.

**foo.json:**

```json
{
"foo": {
"type": "string",
"minLength": 10
}
}
```

```yaml
# @schema
# $ref: foo.json#/foo
# @schema
namespace: foo
```

is the same as

```yaml
# @schema
# type: string
# minLength: 10
# @schema
namespace: foo
```

## License

[MIT](https://github.com/dadav/helm-schema/blob/main/LICENSE)
16 changes: 9 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module github.com/dadav/helm-schema

go 1.21.0
go 1.23.1

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/semver/v3 v3.3.0
github.com/dadav/go-jsonpointer v0.0.0-20240918181927-335cbee8c279
github.com/deckarep/golang-set/v2 v2.6.0
github.com/magiconair/properties v1.8.7
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
Expand All @@ -19,16 +20,17 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
33 changes: 19 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dadav/go-jsonpointer v0.0.0-20240918181927-335cbee8c279 h1:GxxxrAMM6YJK4Tf7c6APwkhXzzpJm2hGeWKpD3OKpHY=
github.com/dadav/go-jsonpointer v0.0.0-20240918181927-335cbee8c279/go.mod h1:JoQhlwhuWFnmxzDNgByW0ONYJZjQGnUnRu9H7+C0lTU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand All @@ -11,8 +13,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand All @@ -33,8 +35,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
Expand All @@ -57,6 +59,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
Expand All @@ -65,15 +68,17 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
64 changes: 63 additions & 1 deletion pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"regexp"
"slices"
"strconv"
"strings"

"github.com/dadav/go-jsonpointer"
"github.com/dadav/helm-schema/pkg/util"
"github.com/santhosh-tekuri/jsonschema/v5"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -112,6 +116,18 @@ func (s *StringOrArrayOfString) UnmarshalYAML(value *yaml.Node) error {
return nil
}

func (s *StringOrArrayOfString) UnmarshalJSON(value []byte) error {
var multi []string
var single string

if err := json.Unmarshal(value, &multi); err == nil {
*s = multi
} else if err := json.Unmarshal(value, &single); err == nil {
*s = []string{single}
}
return nil
}

func (s *StringOrArrayOfString) MarshalJSON() ([]byte, error) {
if len(*s) == 1 {
return json.Marshal([]string(*s)[0])
Expand Down Expand Up @@ -607,6 +623,7 @@ func GetSchemaFromComment(comment string) (Schema, string, error) {

// YamlToSchema recursevly parses the given yaml.Node and creates a jsonschema from it
func YamlToSchema(
valuesPath string,
node *yaml.Node,
keepFullComment bool,
dontRemoveHelmDocsPrefix bool,
Expand All @@ -623,6 +640,7 @@ func YamlToSchema(

schema.Schema = "http://json-schema.org/draft-07/schema#"
schema.Properties = YamlToSchema(
valuesPath,
node.Content[0],
keepFullComment,
dontRemoveHelmDocsPrefix,
Expand Down Expand Up @@ -675,6 +693,48 @@ func YamlToSchema(
description = prefixRemover.ReplaceAllString(description, "")
}

if keyNodeSchema.Ref != "" {
// Check if Ref is a relative file to the values file
refParts := strings.Split(keyNodeSchema.Ref, "#")
if relFilePath, err := util.IsRelativeFile(valuesPath, refParts[0]); err == nil {
var relSchema Schema
file, err := os.Open(relFilePath)
if err == nil {
byteValue, _ := io.ReadAll(file)

if len(refParts) > 1 {
// Found json-pointer
var obj interface{}
json.Unmarshal(byteValue, &obj)
jsonPointerResultRaw, err := jsonpointer.Get(obj, refParts[1])
if err != nil {
log.Fatal(err)
}
jsonPointerResultMarshaled, err := json.Marshal(jsonPointerResultRaw)
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(jsonPointerResultMarshaled, &relSchema)
if err != nil {
log.Fatal(err)
}
} else {
// No json-pointer
err = json.Unmarshal(byteValue, &relSchema)
if err != nil {
log.Fatal(err)
}
}
keyNodeSchema = relSchema
keyNodeSchema.HasData = true
} else {
log.Fatal(err)
}
} else {
log.Debug(err)
}
}

if keyNodeSchema.HasData {
if err := keyNodeSchema.Validate(); err != nil {
log.Fatalf(
Expand Down Expand Up @@ -724,6 +784,7 @@ func YamlToSchema(
// If the value is another map and no properties are set, get them from default values
if valueNode.Kind == yaml.MappingNode && keyNodeSchema.Properties == nil {
keyNodeSchema.Properties = YamlToSchema(
valuesPath,
valueNode,
keepFullComment,
dontRemoveHelmDocsPrefix,
Expand All @@ -743,7 +804,7 @@ func YamlToSchema(
seqSchema.AnyOf = append(seqSchema.AnyOf, NewSchema(itemNodeType[0]))
} else {
itemRequiredProperties := []string{}
itemSchema := YamlToSchema(itemNode, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGeneration, &itemRequiredProperties)
itemSchema := YamlToSchema(valuesPath, itemNode, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGeneration, &itemRequiredProperties)

for _, req := range itemRequiredProperties {
itemSchema.Required.Strings = append(itemSchema.Required.Strings, req)
Expand All @@ -763,6 +824,7 @@ func YamlToSchema(
FixRequiredProperties(&keyNodeSchema)
}
}

if schema.Properties == nil {
schema.Properties = make(map[string]*Schema)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/schema/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func Worker(
continue
}

result.Schema = *YamlToSchema(&values, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGenerationConfig, nil)
result.Schema = *YamlToSchema(valuesPath, &values, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGenerationConfig, nil)

results <- result
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/util/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package util

import (
"bufio"
"errors"
"io"
"os"
"path"
"regexp"
"strings"

Expand Down Expand Up @@ -145,3 +147,13 @@ func RemoveCommentsFromYaml(reader io.Reader) ([]byte, error) {

return result, nil
}

// IsRelativeFile checks if the given string is a relative path to a file
func IsRelativeFile(root, relPath string) (string, error) {
if !path.IsAbs(relPath) {
foo := path.Join(path.Dir(root), relPath)
_, err := os.Stat(foo)
return foo, err
}
return "", errors.New("Is absolute file")
}
6 changes: 6 additions & 0 deletions tests/ref_input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"foo": {
"type": "string",
"description": "from ref"
}
}
6 changes: 6 additions & 0 deletions tests/test_ref.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
# @schema
# $ref: ref_input.json#/foo
# @schema
# Not from ref
foo: bar
21 changes: 21 additions & 0 deletions tests/test_ref_expected.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"foo": {
"default": "bar",
"description": "from ref",
"required": [],
"title": "foo",
"type": "string"
},
"global": {
"description": "Global values are values that can be accessed from any chart or subchart by exactly the same name.",
"required": [],
"title": "global",
"type": "object"
}
},
"required": [],
"type": "object"
}

0 comments on commit fe17fcd

Please sign in to comment.