Skip to content

Commit b4ba461

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 c01dd54 commit b4ba461

File tree

3 files changed

+166
-103
lines changed

3 files changed

+166
-103
lines changed

cmd/timoni/mod_show_config.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,7 @@ func runConfigShowModCmd(cmd *cobra.Command, args []string) error {
120120
return fmt.Errorf("build failed: %w", err)
121121
}
122122

123-
buildResult, err := builder.Build()
124-
if err != nil {
125-
return describeErr(fetcher.GetModuleRoot(), "validation failed", err)
126-
}
127-
128-
rows, err := builder.GetConfigDoc(buildResult)
123+
rows, err := builder.GetConfigDoc()
129124
if err != nil {
130125
return describeErr(fetcher.GetModuleRoot(), "failed to get config structure", err)
131126
}

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"
@@ -307,98 +305,3 @@ func (b *ModuleBuilder) GetContainerImages(value cue.Value) ([]string, error) {
307305

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

0 commit comments

Comments
 (0)