master
go 124 lines 3.09 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package collecttest
4
5 import (
6 "encoding/json"
7 "fmt"
8 "sync"
9 "testing"
10
11 "github.com/netdata/netdata/go/plugins/plugin/framework/charttpl"
12 "github.com/santhosh-tekuri/jsonschema/v6"
13 "gopkg.in/yaml.v2"
14 )
15
16 var (
17 chartTemplateSchemaOnce sync.Once
18 chartTemplateSchema *jsonschema.Schema
19 chartTemplateSchemaErr error
20 )
21
22 // AssertChartTemplateSchema validates chart-template YAML against
23 // charttpl JSON schema and fails the test on error.
24 func AssertChartTemplateSchema(t testing.TB, templateYAML string) {
25 t.Helper()
26 if err := ValidateChartTemplateSchema(templateYAML); err != nil {
27 t.Fatalf("collecttest: chart template schema validation failed: %v", err)
28 }
29 }
30
31 // ValidateChartTemplateSchema validates chart-template YAML against charttpl JSON schema.
32 func ValidateChartTemplateSchema(templateYAML string) error {
33 schema, err := loadChartTemplateSchema()
34 if err != nil {
35 return err
36 }
37
38 doc, err := decodeYAMLToGenericJSONValue([]byte(templateYAML))
39 if err != nil {
40 return err
41 }
42
43 if err := schema.Validate(doc); err != nil {
44 return fmt.Errorf("schema validate: %w", err)
45 }
46 return nil
47 }
48
49 func loadChartTemplateSchema() (*jsonschema.Schema, error) {
50 chartTemplateSchemaOnce.Do(func() {
51 var doc any
52 if err := json.Unmarshal([]byte(charttpl.ConfigSchemaJSON), &doc); err != nil {
53 chartTemplateSchemaErr = fmt.Errorf("decode embedded charttpl schema JSON: %w", err)
54 return
55 }
56
57 compiler := jsonschema.NewCompiler()
58 if err := compiler.AddResource("charttpl.schema.json", doc); err != nil {
59 chartTemplateSchemaErr = fmt.Errorf("add schema resource: %w", err)
60 return
61 }
62
63 chartTemplateSchema, chartTemplateSchemaErr = compiler.Compile("charttpl.schema.json")
64 if chartTemplateSchemaErr != nil {
65 chartTemplateSchemaErr = fmt.Errorf("compile schema: %w", chartTemplateSchemaErr)
66 return
67 }
68 })
69
70 return chartTemplateSchema, chartTemplateSchemaErr
71 }
72
73 func decodeYAMLToGenericJSONValue(rawYAML []byte) (any, error) {
74 var doc any
75 if err := yaml.UnmarshalStrict(rawYAML, &doc); err != nil {
76 return nil, fmt.Errorf("decode template YAML: %w", err)
77 }
78 normalized, err := normalizeYAML(doc, "$")
79 if err != nil {
80 return nil, err
81 }
82 return normalized, nil
83 }
84
85 func normalizeYAML(v any, path string) (any, error) {
86 switch tv := v.(type) {
87 case map[string]any:
88 out := make(map[string]any, len(tv))
89 for key, val := range tv {
90 norm, err := normalizeYAML(val, path+"."+key)
91 if err != nil {
92 return nil, err
93 }
94 out[key] = norm
95 }
96 return out, nil
97 case map[any]any:
98 out := make(map[string]any, len(tv))
99 for key, val := range tv {
100 ks, ok := key.(string)
101 if !ok {
102 return nil, fmt.Errorf("decode template YAML: non-string key at %s (key type %T)", path, key)
103 }
104 norm, err := normalizeYAML(val, path+"."+ks)
105 if err != nil {
106 return nil, err
107 }
108 out[ks] = norm
109 }
110 return out, nil
111 case []any:
112 out := make([]any, len(tv))
113 for i, val := range tv {
114 norm, err := normalizeYAML(val, fmt.Sprintf("%s[%d]", path, i))
115 if err != nil {
116 return nil, err
117 }
118 out[i] = norm
119 }
120 return out, nil
121 default:
122 return v, nil
123 }
124 }