Skip to content

Commit

Permalink
Merge pull request #17 from ubergesundheit/add-flags-omit-title-descr…
Browse files Browse the repository at this point in the history
…iption-and-default

Add flag to skip default creation of schema fields
  • Loading branch information
dadav authored Mar 6, 2024
2 parents 04737a7 + 58bdb1e commit cddcf5d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 43 deletions.
61 changes: 31 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ file, you can do the following:
1. Install [`pre-commit`](https://pre-commit.com/#install)
2. Copy the [`.pre-commit-config.yaml`](./.pre-commit-config.yaml) to your helm chart repository.
3. Then run these commands:

```sh
pre-commit install
pre-commit install-hooks
Expand Down Expand Up @@ -73,6 +74,7 @@ Flags:
-n, --no-dependencies "don't analyze dependencies"
-o, --output-file string "jsonschema file path relative to each chart directory to which jsonschema will be written (default 'values.schema.json')"
-f, --value-files strings "filenames to check for chart values (default [values.yaml])"
-k, --skip-auto-generation strings "skip the auto generation for these fields (default [])"
-v, --version "version for helm-schema"
```

Expand All @@ -92,32 +94,32 @@ The `jsonschema` must be between two entries of : `# @schema`, example below :

### Available annotations

|Key|Description|Values|
|-|-|-|
|`type`|Defines the [jsonschema-type](https://json-schema.org/understanding-json-schema/reference/type.html) of the object|`object`, `array`, `string`, `number`, `integer`, `boolean` or `null`|
|`title`|Defines the [title field](https://json-schema.org/understanding-json-schema/reference/generic.html?highlight=title) of the object|Defaults to the key itself|
|`description`|Defines the [description field](https://json-schema.org/understanding-json-schema/reference/generic.html?highlight=description) of the object.|Defaults to the comment which has no `# @schema` prefix|
|`default`|Sets the default value and will be displayed first on the users IDE||
|`properties`|Contains a map with keys as property names and values as schema|Takes an `object`|
|`pattern`|Regex pattern to test the value|Takes an `string`|
|`format`|The [format keyword](https://json-schema.org/understanding-json-schema/reference/string.html#format) allows for basic semantic identification of certain kinds of string values||
|`required`|Adds the key to the required items|`true` or `false`|
|`deprecated`|Marks the option as deprecated|`true` or `false`|
|`items`|Contains the schema that describes the possible array items||
|`enum`|Multiple allowed values||
|`const`|Single allowed value||
|`examples`|Some examples you can provide for the end user|Takes an `array`|
|`minimum`|Minimum value. Can't be used with `exclusiveMinimum`|Must be smaller than `maximum` or `exclusiveMaximum` (if used)|
|`exclusiveMinimum`|Exclusive minimum. Can't be used with `minimum`|Must be smaller than `maximum` or `exclusiveMaximum` (if used)|
|`maximum`|Maximum value. Can't be used with `exclusiveMaximum`|Must be bigger than `minimum` or `exclusiveMinimum` (if used)|
|`exclusiveMaximum`|Exclusive maximum value. Can't be used with `maximum`|Must be bigger than `minimum` or `exclusiveMinimum` (if used)|
|`multipleOf`|The yaml-value must be a multiple of. For example: If you set this to 10, allowed values would be 0, 10, 20, 30...|Takes an `int`|
|`additionalProperties`|Allow additional keys in maps. Useful if you want to use for example `additionalAnnotations`, which will be filled with keys that the `jsonschema` can't know|Defaults to `false` if the map is not an empty map. Takes a schema or boolean value|
|`patternProperties`|Contains a map which maps schemas to pattern. If properties match the patterns, the given schema is applied|Takes an `object`|
|`anyOf`|Accepts an array of schemas. None or one must apply|Takes an `array`|
|`oneOf`|Accepts an array of schemas. One or more must apply|Takes an `array`|
|`allOf`|Accepts an array of schemas. All must apply|Takes an `array`|
|`if/then/else`|`if` the given schema applies, `then` also apply the given schema or `else` the other schema||
| Key | Description | Values |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `type` | Defines the [jsonschema-type](https://json-schema.org/understanding-json-schema/reference/type.html) of the object | `object`, `array`, `string`, `number`, `integer`, `boolean` or `null` |
| `title` | Defines the [title field](https://json-schema.org/understanding-json-schema/reference/generic.html?highlight=title) of the object | Defaults to the key itself |
| `description` | Defines the [description field](https://json-schema.org/understanding-json-schema/reference/generic.html?highlight=description) of the object. | Defaults to the comment which has no `# @schema` prefix |
| `default` | Sets the default value and will be displayed first on the users IDE | |
| `properties` | Contains a map with keys as property names and values as schema | Takes an `object` |
| `pattern` | Regex pattern to test the value | Takes an `string` |
| `format` | The [format keyword](https://json-schema.org/understanding-json-schema/reference/string.html#format) allows for basic semantic identification of certain kinds of string values | |
| `required` | Adds the key to the required items | `true` or `false` |
| `deprecated` | Marks the option as deprecated | `true` or `false` |
| `items` | Contains the schema that describes the possible array items | |
| `enum` | Multiple allowed values | |
| `const` | Single allowed value | |
| `examples` | Some examples you can provide for the end user | Takes an `array` |
| `minimum` | Minimum value. Can't be used with `exclusiveMinimum` | Must be smaller than `maximum` or `exclusiveMaximum` (if used) |
| `exclusiveMinimum` | Exclusive minimum. Can't be used with `minimum` | Must be smaller than `maximum` or `exclusiveMaximum` (if used) |
| `maximum` | Maximum value. Can't be used with `exclusiveMaximum` | Must be bigger than `minimum` or `exclusiveMinimum` (if used) |
| `exclusiveMaximum` | Exclusive maximum value. Can't be used with `maximum` | Must be bigger than `minimum` or `exclusiveMinimum` (if used) |
| `multipleOf` | The yaml-value must be a multiple of. For example: If you set this to 10, allowed values would be 0, 10, 20, 30... | Takes an `int` |
| `additionalProperties` | Allow additional keys in maps. Useful if you want to use for example `additionalAnnotations`, which will be filled with keys that the `jsonschema` can't know | Defaults to `false` if the map is not an empty map. Takes a schema or boolean value |
| `patternProperties` | Contains a map which maps schemas to pattern. If properties match the patterns, the given schema is applied | Takes an `object` |
| `anyOf` | Accepts an array of schemas. None or one must apply | Takes an `array` |
| `oneOf` | Accepts an array of schemas. One or more must apply | Takes an `array` |
| `allOf` | Accepts an array of schemas. All must apply | Takes an `array` |
| `if/then/else` | `if` the given schema applies, `then` also apply the given schema or `else` the other schema | |

## Validation & completion

Expand All @@ -129,7 +131,6 @@ You'll have to place this line at the top of your `values.yaml` (`$schema=<path-
# yaml-language-server: $schema=values.schema.json

foo: bar
...
```
## Example
Expand All @@ -143,7 +144,7 @@ This `values.yaml`:
# This text will be used as description.
# @schema
# type: integer
# type: integer
# minimum: 0
# @schema
foo: 1
Expand Down Expand Up @@ -212,7 +213,7 @@ If you're using [`helm-docs`](https://github.com/norwoodj/helm-docs), then you c
foo: []
```

💡 Make sure to place the `@schema` annotations **before** the actual field description to avoid having it in your `helm-docs` generated table
💡 Make sure to place the `@schema` annotations **before** the actual field description to avoid having it in your `helm-docs` generated table

## Dependencies

Expand All @@ -226,7 +227,7 @@ If you don't want to generate jsonschemas for dependencies, you can use the `-n`
## Limitations

You can't change the jsonschema for dependencies by using `@schema` annotations on dependency
config values. For example:
config values. For example:

```yaml
# foo is a dependency chart
Expand Down
2 changes: 2 additions & 0 deletions cmd/helm-schema/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func newCommand(run func(cmd *cobra.Command, args []string) error) (*cobra.Comma
StringSliceP("value-files", "f", []string{"values.yaml"}, "filenames to check for chart values")
cmd.PersistentFlags().
StringP("output-file", "o", "values.schema.json", "jsonschema file path relative to each chart directory to which jsonschema will be written")
cmd.PersistentFlags().
StringSliceP("skip-auto-generation", "k", []string{}, "do not auto-create the given fields (e.g. title,description,...)")

viper.AutomaticEnv()
viper.SetEnvPrefix("HELM_SCHEMA")
Expand Down
13 changes: 11 additions & 2 deletions cmd/helm-schema/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Result struct {
func worker(
dryRun, keepFullComment, dontRemoveHelmDocsPrefix bool,
valueFileNames []string,
skipAutoGeneration []string,
outFile string,
queue <-chan string,
results chan<- Result,
Expand Down Expand Up @@ -120,7 +121,7 @@ func worker(
continue
}

result.Schema = schema.YamlToSchema(&values, keepFullComment, dontRemoveHelmDocsPrefix, nil)
result.Schema = schema.YamlToSchema(&values, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGeneration, nil)

results <- result
}
Expand All @@ -129,13 +130,20 @@ func worker(
func exec(cmd *cobra.Command, _ []string) error {
configureLogging()

var skipAutoGeneration, valueFileNames []string

chartSearchRoot := viper.GetString("chart-search-root")
dryRun := viper.GetBool("dry-run")
noDeps := viper.GetBool("no-dependencies")
keepFullComment := viper.GetBool("keep-full-comment")
outFile := viper.GetString("output-file")
valueFileNames := viper.GetStringSlice("value-files")
dontRemoveHelmDocsPrefix := viper.GetBool("dont-strip-helm-docs-prefix")
if err := viper.UnmarshalKey("value-files", &valueFileNames); err != nil {
return err
}
if err := viper.UnmarshalKey("skip-auto-generation", &skipAutoGeneration); err != nil {
return err
}
workersCount := runtime.NumCPU() * 2

// 1. Start a producer that searches Chart.yaml and values.yaml files
Expand Down Expand Up @@ -164,6 +172,7 @@ func exec(cmd *cobra.Command, _ []string) error {
keepFullComment,
dontRemoveHelmDocsPrefix,
valueFileNames,
skipAutoGeneration,
outFile,
queue,
resultsChan,
Expand Down
32 changes: 21 additions & 11 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strings"

jsonschema "github.com/santhosh-tekuri/jsonschema/v5"
Expand Down Expand Up @@ -354,6 +355,7 @@ func YamlToSchema(
node *yaml.Node,
keepFullComment bool,
dontRemoveHelmDocsPrefix bool,
skipAutoGeneration []string,
parentRequiredProperties *[]string,
) Schema {
var schema Schema
Expand All @@ -372,6 +374,7 @@ func YamlToSchema(
node.Content[0],
keepFullComment,
dontRemoveHelmDocsPrefix,
skipAutoGeneration,
&requiredProperties,
).Properties

Expand All @@ -381,17 +384,23 @@ func YamlToSchema(
schema.Properties = make(map[string]*Schema)
}
schema.Properties["global"] = &Schema{
Type: []string{"object"},
Title: "global",
Description: "Global values are values that can be accessed from any chart or subchart by exactly the same name.",
Type: []string{"object"},
}
if !slices.Contains(skipAutoGeneration, "title") {
schema.Properties["global"].Title = "global"
}
if !slices.Contains(skipAutoGeneration, "description") {
schema.Properties["global"].Description = "Global values are values that can be accessed from any chart or subchart by exactly the same name."
}
}

if len(requiredProperties) > 0 {
schema.RequiredProperties = requiredProperties
}
// always disable on top level
schema.AdditionalProperties = new(bool)
if !slices.Contains(skipAutoGeneration, "additionalProperties") {
schema.AdditionalProperties = new(bool)
}
case yaml.MappingNode:
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
Expand Down Expand Up @@ -429,27 +438,27 @@ func YamlToSchema(
}

// Add key to required array of parent
if keyNodeSchema.Required || !keyNodeSchema.HasData {
if keyNodeSchema.Required || (!slices.Contains(skipAutoGeneration, "required") && !keyNodeSchema.HasData) {
*parentRequiredProperties = append(*parentRequiredProperties, keyNode.Value)
}

if valueNode.Kind == yaml.MappingNode &&
if !slices.Contains(skipAutoGeneration, "additionalProperties") && valueNode.Kind == yaml.MappingNode &&
(!keyNodeSchema.HasData || keyNodeSchema.AdditionalProperties == nil) {
keyNodeSchema.AdditionalProperties = new(bool)
}

// If no title was set, use the key value
if keyNodeSchema.Title == "" {
if keyNodeSchema.Title == "" && !slices.Contains(skipAutoGeneration, "title") {
keyNodeSchema.Title = keyNode.Value
}

// If no description was set, use the rest of the comment as description
if keyNodeSchema.Description == "" {
if keyNodeSchema.Description == "" && !slices.Contains(skipAutoGeneration, "description") {
keyNodeSchema.Description = description
}

// If no default value was set, use the values node value as default
if keyNodeSchema.Default == nil && valueNode.Kind == yaml.ScalarNode {
if !slices.Contains(skipAutoGeneration, "default") && keyNodeSchema.Default == nil && valueNode.Kind == yaml.ScalarNode {
keyNodeSchema.Default = valueNode.Value
}

Expand All @@ -460,6 +469,7 @@ func YamlToSchema(
valueNode,
keepFullComment,
dontRemoveHelmDocsPrefix,
skipAutoGeneration,
&requiredProperties,
).Properties
if len(requiredProperties) > 0 {
Expand All @@ -478,13 +488,13 @@ func YamlToSchema(
seqSchema.AnyOf = append(seqSchema.AnyOf, &Schema{Type: itemNodeType})
} else {
itemRequiredProperties := []string{}
itemSchema := YamlToSchema(itemNode, keepFullComment, dontRemoveHelmDocsPrefix, &itemRequiredProperties)
itemSchema := YamlToSchema(itemNode, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGeneration, &itemRequiredProperties)

if len(itemRequiredProperties) > 0 {
itemSchema.RequiredProperties = itemRequiredProperties
}

if itemNode.Kind == yaml.MappingNode && (!itemSchema.HasData || itemSchema.AdditionalProperties == nil) {
if !slices.Contains(skipAutoGeneration, "additionalProperties") && itemNode.Kind == yaml.MappingNode && (!itemSchema.HasData || itemSchema.AdditionalProperties == nil) {
itemSchema.AdditionalProperties = new(bool)
}

Expand Down

0 comments on commit cddcf5d

Please sign in to comment.