Skip to content

Commit 8d037ae

Browse files
committed
feat: Add json/yaml output for the validate command
1 parent 3835ef0 commit 8d037ae

File tree

4 files changed

+443
-17
lines changed

4 files changed

+443
-17
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,18 @@ Options:
5151
Defaults to values.yaml
5252
--rules-file, -r Rules files to validate against (comma-separated or multiple flags)
5353
Defaults to values.cel.yaml
54+
--output, -o Output format: text, json, or yaml
55+
Defaults to text
5456
```
5557

5658
Example with custom files:
5759
```bash
5860
# Using single values and rules files
5961
helm cel validate ./mychart --values-file prod.values.yaml --rules-file prod.cel.yaml
6062

63+
# Using JSON output format
64+
helm cel validate ./mychart -o json
65+
6166
# Using multiple values files (later files take precedence)
6267
helm cel validate ./mychart --values-file common.yaml --values-file prod.yaml
6368

@@ -210,6 +215,61 @@ If all rules pass, you'll see a success message:
210215
✅ Values validation successful!
211216
```
212217

218+
### Structured Output Formats
219+
220+
You can output validation results in JSON or YAML format for integration with CI/CD pipelines:
221+
222+
```bash
223+
# JSON output
224+
helm cel validate ./mychart -o json
225+
226+
# YAML output
227+
helm cel validate ./mychart -o yaml
228+
```
229+
230+
JSON output example:
231+
```json
232+
{
233+
"has_errors": true,
234+
"has_warnings": true,
235+
"result": {
236+
"errors": [
237+
{
238+
"description": "replicaCount must be at least 1",
239+
"expression": "values.replicaCount >= 1",
240+
"value": 0,
241+
"path": "replicaCount"
242+
}
243+
],
244+
"warnings": [
245+
{
246+
"description": "service port should be between 1 and 65535",
247+
"expression": "values.service.port >= 1 && values.service.port <= 65535",
248+
"value": 80801,
249+
"path": "service.port"
250+
}
251+
]
252+
}
253+
}
254+
```
255+
256+
YAML output example:
257+
```yaml
258+
has_errors: true
259+
has_warnings: true
260+
result:
261+
errors:
262+
- description: replicaCount must be at least 1
263+
expression: values.replicaCount >= 1
264+
value: 0
265+
path: replicaCount
266+
warnings:
267+
- description: service port should be between 1 and 65535
268+
expression: values.service.port >= 1 && values.service.port <= 65535
269+
value: 80801
270+
path: service.port
271+
```
272+
213273
## Development
214274

215275
Requirements:

cmd/helm-cel/main.go

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package main
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"os"
67
"path/filepath"
78

89
"github.com/idsulik/helm-cel/pkg/generator"
10+
"github.com/idsulik/helm-cel/pkg/models"
911
"github.com/idsulik/helm-cel/pkg/validator"
1012
"github.com/spf13/cobra"
13+
"gopkg.in/yaml.v3"
1114
)
1215

1316
var (
@@ -17,16 +20,19 @@ var (
1720
outputFile string
1821

1922
// Flags for validate command
20-
valuesFiles []string
21-
rulesFiles []string
23+
valuesFiles []string
24+
rulesFiles []string
25+
outputFormat string
2226
)
2327

2428
const (
2529
validateShort = "Validate Helm values using CEL expressions"
2630
validateLong = `A Helm plugin to validate values.yaml using CEL expressions defined in .cel.yaml files.
2731
Example using defaults: helm cel validate ./mychart
2832
Example with specific values: helm cel validate ./mychart -v values1.yaml -v values2.yaml
29-
Example with multiple files: helm cel validate ./mychart -v prod.yaml,staging.yaml -r rules1.cel.yaml,rules2.cel.yaml`
33+
Example with multiple files: helm cel validate ./mychart -v prod.yaml,staging.yaml -r rules1.cel.yaml,rules2.cel.yaml
34+
Example with JSON output: helm cel validate ./mychart -o json
35+
Example with YAML output: helm cel validate ./mychart -o yaml`
3036

3137
generateShort = "Generate CEL validation rules from values.yaml"
3238
generateLong = `Generate values.cel.yaml file with validation rules based on the structure of values.yaml.
@@ -73,6 +79,13 @@ func init() {
7379
[]string{"values.cel.yaml"},
7480
"Rules files to validate against (comma-separated or multiple -r flags)",
7581
)
82+
validateCmd.Flags().StringVarP(
83+
&outputFormat,
84+
"output",
85+
"o",
86+
"text",
87+
"Output format: text, json, or yaml",
88+
)
7689

7790
generateCmd.Flags().BoolVarP(&forceOverwrite, "force", "f", false, "Force overwrite existing values.cel.yaml")
7891
generateCmd.Flags().StringVarP(
@@ -116,21 +129,56 @@ func runValidator(_ *cobra.Command, args []string) error {
116129
return err
117130
}
118131

119-
if result.HasErrors() {
120-
return result
132+
output := models.ValidationOutput{
133+
HasErrors: result.HasErrors(),
134+
HasWarnings: len(result.Warnings) > 0,
135+
Result: result,
121136
}
122137

123-
if len(result.Warnings) > 0 {
124-
fmt.Println(result.Error())
125-
fmt.Println("-------------------------------------------------")
126-
fmt.Println("⚠️✅ Values validation successful with warnings!")
127-
} else {
128-
fmt.Println("✅ Values validation successful!")
138+
switch outputFormat {
139+
case "json":
140+
if err := outputJson(output); err != nil {
141+
return err
142+
}
143+
case "yaml":
144+
if err := outputYaml(output); err != nil {
145+
return err
146+
}
147+
default:
148+
if result.HasErrors() {
149+
return result
150+
}
151+
152+
if len(result.Warnings) > 0 {
153+
fmt.Println(result.Error())
154+
fmt.Println("-------------------------------------------------")
155+
fmt.Println("⚠️✅ Values validation successful with warnings!")
156+
} else {
157+
fmt.Println("✅ Values validation successful!")
158+
}
129159
}
130160

131161
return nil
132162
}
133163

164+
func outputJson(output models.ValidationOutput) error {
165+
json, err := json.MarshalIndent(output, "", " ")
166+
if err != nil {
167+
return fmt.Errorf("failed to marshal output to JSON: %v", err)
168+
}
169+
fmt.Println(string(json))
170+
return nil
171+
}
172+
173+
func outputYaml(output models.ValidationOutput) error {
174+
yaml, err := yaml.Marshal(output)
175+
if err != nil {
176+
return fmt.Errorf("failed to marshal output to YAML: %v", err)
177+
}
178+
fmt.Println(string(yaml))
179+
return nil
180+
}
181+
134182
func runGenerator(_ *cobra.Command, args []string) error {
135183
if len(args) != 1 {
136184
return fmt.Errorf("chart path is required")

pkg/models/models.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,23 @@ type ValidationRules struct {
2020

2121
// ValidationResult represents the outcome of validation
2222
type ValidationResult struct {
23-
Errors []*ValidationError
24-
Warnings []*ValidationError
23+
Errors []*ValidationError `json:"errors" yaml:"errors"`
24+
Warnings []*ValidationError `json:"warnings" yaml:"warnings"`
2525
}
2626

2727
// ValidationError represents a validation failure
2828
type ValidationError struct {
29-
Description string
30-
Expression string
31-
Value any
32-
Path string
29+
Description string `json:"description" yaml:"description"`
30+
Expression string `json:"expression" yaml:"expression"`
31+
Value any `json:"value" yaml:"value"`
32+
Path string `json:"path,omitempty" yaml:"path,omitempty"`
33+
}
34+
35+
// ValidationOutput is used for structured output in JSON/YAML format
36+
type ValidationOutput struct {
37+
HasErrors bool `json:"has_errors" yaml:"has_errors"`
38+
HasWarnings bool `json:"has_warnings" yaml:"has_warnings"`
39+
Result *ValidationResult `json:"result" yaml:"result"`
3340
}
3441

3542
func (vr *ValidationResult) HasErrors() bool {

0 commit comments

Comments
 (0)