Skip to content

Commit d6d05d7

Browse files
committed
Refactor code so we are not passing in values and only using the defaults from the module config
Signed-off-by: Luke Mallon (Nalum) <[email protected]>
1 parent 4e87baf commit d6d05d7

File tree

3 files changed

+166
-102
lines changed

3 files changed

+166
-102
lines changed

cmd/timoni/mod_show_config.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,8 @@ func runConfigShowModCmd(cmd *cobra.Command, args []string) error {
124124
return fmt.Errorf("build failed: %w", err)
125125
}
126126

127-
buildResult, err := builder.Build()
128-
if err != nil {
129-
return describeErr(f.GetModuleRoot(), "validation failed", err)
130-
}
127+
rows, err := builder.GetConfigDoc()
131128

132-
rows, err := builder.GetConfigDoc(buildResult)
133129
if err != nil {
134130
return describeErr(f.GetModuleRoot(), "failed to get config structure", err)
135131
}

internal/engine/get_config.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright 2023 Stefan Prodan
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package engine
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"regexp"
23+
"strings"
24+
25+
"cuelang.org/go/cue"
26+
"cuelang.org/go/cue/ast"
27+
"cuelang.org/go/cue/load"
28+
29+
apiv1 "github.com/stefanprodan/timoni/api/v1alpha1"
30+
)
31+
32+
// GetConfigDoc extracts the config structure from the module.
33+
func (b *ModuleBuilder) GetConfigDoc() ([][]string, error) {
34+
var value cue.Value
35+
36+
cfg := &load.Config{
37+
ModuleRoot: b.moduleRoot,
38+
Package: b.pkgName,
39+
Dir: b.pkgPath,
40+
DataFiles: true,
41+
Tags: []string{
42+
"name=" + b.name,
43+
"namespace=" + b.namespace,
44+
},
45+
TagVars: map[string]load.TagVar{
46+
"moduleVersion": {
47+
Func: func() (ast.Expr, error) {
48+
return ast.NewString(b.moduleVersion), nil
49+
},
50+
},
51+
"kubeVersion": {
52+
Func: func() (ast.Expr, error) {
53+
return ast.NewString(b.kubeVersion), nil
54+
},
55+
},
56+
},
57+
}
58+
59+
modInstances := load.Instances([]string{}, cfg)
60+
if len(modInstances) == 0 {
61+
return nil, errors.New("no instances found")
62+
}
63+
64+
modInstance := modInstances[0]
65+
if modInstance.Err != nil {
66+
return nil, fmt.Errorf("instance error: %w", modInstance.Err)
67+
}
68+
69+
value = b.ctx.BuildInstance(modInstance)
70+
if value.Err() != nil {
71+
return nil, value.Err()
72+
}
73+
74+
cfgValues := value.LookupPath(cue.ParsePath(apiv1.ConfigValuesSelector.String()))
75+
if cfgValues.Err() != nil {
76+
return nil, fmt.Errorf("lookup %s failed: %w", apiv1.ConfigValuesSelector, cfgValues.Err())
77+
}
78+
79+
rows, err := iterateFields(cfgValues)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
return rows, nil
85+
}
86+
87+
func iterateFields(v cue.Value) ([][]string, error) {
88+
var rows [][]string
89+
90+
fields, err := v.Fields(
91+
cue.Optional(true),
92+
cue.Concrete(true),
93+
cue.Docs(true),
94+
)
95+
if err != nil {
96+
return nil, fmt.Errorf("Cue Fields Error: %w", err)
97+
}
98+
99+
for fields.Next() {
100+
v := fields.Value()
101+
102+
// We are chekcing if the field is a struct and not optional and is concrete before we iterate through it
103+
// this allows for definition of default values as full structs without generating output for each
104+
// field in the struct where it doesn't make sense e.g.
105+
//
106+
// - annotations?: {[string]: string}
107+
// - affinity: corev1.Affinity | *{nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [...]}
108+
if v.IncompleteKind() == cue.StructKind && !fields.IsOptional() && v.IsConcrete() {
109+
iRows, err := iterateFields(v)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
rows = append(rows, iRows...)
115+
} else {
116+
rows = append(rows, getField(v))
117+
}
118+
}
119+
120+
return rows, nil
121+
}
122+
123+
func getField(v cue.Value) []string {
124+
var row []string
125+
labelDomain := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)?(".+")?$`)
126+
127+
var noDoc bool
128+
var doc string
129+
130+
for _, d := range v.Doc() {
131+
if line := len(d.List) - 1; line >= 0 {
132+
switch d.List[line].Text {
133+
case "// +nodoc":
134+
noDoc = true
135+
break
136+
}
137+
}
138+
139+
doc += d.Text()
140+
doc = strings.ReplaceAll(doc, "\n", " ")
141+
doc = strings.ReplaceAll(doc, "+required", "")
142+
doc = strings.ReplaceAll(doc, "+optional", "")
143+
}
144+
145+
if !noDoc {
146+
defaultVal, _ := v.Default()
147+
valueBytes, _ := defaultVal.MarshalJSON()
148+
valueType := strings.ReplaceAll(v.IncompleteKind().String(), "|", "\\|")
149+
150+
value := strings.ReplaceAll(string(valueBytes), "\":", "\": ")
151+
value = strings.ReplaceAll(value, "\":[", "\": [")
152+
value = strings.ReplaceAll(value, "},", "}, ")
153+
value = strings.ReplaceAll(value, "|", "\\|")
154+
155+
field := strings.Replace(v.Path().String(), "timoni.instance.config.", "", 1)
156+
match := labelDomain.FindStringSubmatch(field)
157+
158+
row = append(row, fmt.Sprintf("`%s:`", strings.ReplaceAll(match[1], ".", ": ")+match[2]))
159+
row = append(row, fmt.Sprintf("`%s`", valueType))
160+
row = append(row, fmt.Sprintf("`%s`", value))
161+
row = append(row, fmt.Sprintf("%s", doc))
162+
}
163+
164+
return row
165+
}

internal/engine/module_builder.go

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ import (
2222
"os"
2323
"path/filepath"
2424
"reflect"
25-
"regexp"
2625
"slices"
27-
"strings"
2826

2927
"cuelang.org/go/cue"
3028
"cuelang.org/go/cue/ast"
@@ -315,98 +313,3 @@ func (b *ModuleBuilder) GetContainerImages(value cue.Value) ([]string, error) {
315313

316314
return images, nil
317315
}
318-
319-
// GetConfigDoc extracts the config structure from the module.
320-
func (b *ModuleBuilder) GetConfigDoc(value cue.Value) ([][]string, error) {
321-
cfgValues := value.LookupPath(cue.ParsePath(apiv1.ConfigValuesSelector.String()))
322-
if cfgValues.Err() != nil {
323-
return nil, fmt.Errorf("lookup %s failed: %w", apiv1.ConfigValuesSelector, cfgValues.Err())
324-
}
325-
326-
rows, err := iterateFields(cfgValues)
327-
if err != nil {
328-
return nil, err
329-
}
330-
331-
return rows, nil
332-
}
333-
334-
func iterateFields(v cue.Value) ([][]string, error) {
335-
var rows [][]string
336-
337-
fields, err := v.Fields(
338-
cue.Optional(true),
339-
cue.Concrete(true),
340-
cue.Docs(true),
341-
)
342-
if err != nil {
343-
return nil, fmt.Errorf("Cue Fields Error: %w", err)
344-
}
345-
346-
for fields.Next() {
347-
v := fields.Value()
348-
349-
// We are chekcing if the field is a struct and not optional and is concrete before we iterate through it
350-
// this allows for definition of default values as full structs without generating output for each
351-
// field in the struct where it doesn't make sense e.g.
352-
//
353-
// - annotations?: {[string]: string}
354-
// - affinity: corev1.Affinity | *{nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [...]}
355-
if v.IncompleteKind() == cue.StructKind && !fields.IsOptional() && v.IsConcrete() {
356-
iRows, err := iterateFields(v)
357-
if err != nil {
358-
return nil, err
359-
}
360-
361-
rows = append(rows, iRows...)
362-
} else {
363-
rows = append(rows, getField(v))
364-
}
365-
}
366-
367-
return rows, nil
368-
}
369-
370-
func getField(v cue.Value) []string {
371-
var row []string
372-
labelDomain := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)?(".+")?$`)
373-
374-
var noDoc bool
375-
var doc string
376-
377-
for _, d := range v.Doc() {
378-
if line := len(d.List) - 1; line >= 0 {
379-
switch d.List[line].Text {
380-
case "// +nodoc":
381-
noDoc = true
382-
break
383-
}
384-
}
385-
386-
doc += d.Text()
387-
doc = strings.ReplaceAll(doc, "\n", " ")
388-
doc = strings.ReplaceAll(doc, "+required", "")
389-
doc = strings.ReplaceAll(doc, "+optional", "")
390-
}
391-
392-
if !noDoc {
393-
defaultVal, _ := v.Default()
394-
valueBytes, _ := defaultVal.MarshalJSON()
395-
valueType := strings.ReplaceAll(v.IncompleteKind().String(), "|", "\\|")
396-
397-
value := strings.ReplaceAll(string(valueBytes), "\":", "\": ")
398-
value = strings.ReplaceAll(value, "\":[", "\": [")
399-
value = strings.ReplaceAll(value, "},", "}, ")
400-
value = strings.ReplaceAll(value, "|", "\\|")
401-
402-
field := strings.Replace(v.Path().String(), "timoni.instance.config.", "", 1)
403-
match := labelDomain.FindStringSubmatch(field)
404-
405-
row = append(row, fmt.Sprintf("`%s:`", strings.ReplaceAll(match[1], ".", ": ")+match[2]))
406-
row = append(row, fmt.Sprintf("`%s`", valueType))
407-
row = append(row, fmt.Sprintf("`%s`", value))
408-
row = append(row, fmt.Sprintf("%s", doc))
409-
}
410-
411-
return row
412-
}

0 commit comments

Comments
 (0)