Skip to content

Commit a11291c

Browse files
committed
add support for meta parameters in templating with prefix "meta."
1 parent 0d6636c commit a11291c

File tree

3 files changed

+310
-100
lines changed

3 files changed

+310
-100
lines changed

internal/templating/applier.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package templating
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
7+
"github.com/jumpstarter-dev/jumpstarter-lab-config/internal/config"
8+
"github.com/jumpstarter-dev/jumpstarter-lab-config/internal/vars"
9+
)
10+
11+
type TemplateApplier struct {
12+
variables *vars.Variables
13+
parameters *Parameters
14+
}
15+
16+
func NewTemplateApplier(cfg *config.Config, parameters *Parameters) (*TemplateApplier, error) {
17+
if cfg == nil {
18+
return nil, fmt.Errorf("config cannot be nil")
19+
}
20+
if cfg.Loaded == nil {
21+
return nil, fmt.Errorf("loaded config cannot be nil")
22+
}
23+
return &TemplateApplier{
24+
variables: cfg.Loaded.Variables,
25+
parameters: parameters,
26+
}, nil
27+
}
28+
29+
// ApplyTemplatesRecursively walks through all fields of the given object recursively,
30+
// and applies ProcessTemplate to every string field.
31+
func (t *TemplateApplier) Apply(obj interface{}) error {
32+
meta := createMetadataParameters(obj)
33+
return t.applyTemplates(reflect.ValueOf(obj), meta)
34+
}
35+
36+
func createMetadataParameters(obj interface{}) *Parameters {
37+
meta := NewParameters("meta")
38+
39+
if obj != nil {
40+
val := reflect.ValueOf(obj)
41+
42+
// If obj is a pointer, get the element it points to
43+
if val.Kind() == reflect.Ptr {
44+
if val.IsNil() { // Check if the pointer is nil
45+
obj = nil // Treat as nil object if pointer is nil
46+
} else {
47+
val = val.Elem()
48+
}
49+
}
50+
51+
if obj != nil && val.Kind() == reflect.Struct {
52+
// Try to get obj.Name
53+
nameField := val.FieldByName("Name")
54+
if nameField.IsValid() && nameField.Kind() == reflect.String && nameField.CanInterface() {
55+
meta.Set("name", nameField.String())
56+
} else {
57+
// If obj.Name was not found or not a string, try obj.Metadata.Name
58+
metadataField := val.FieldByName("Metadata")
59+
if metadataField.IsValid() && metadataField.CanInterface() {
60+
metadataVal := metadataField
61+
// Get the actual value of Metadata, handling if it's a pointer
62+
if metadataVal.Kind() == reflect.Ptr {
63+
if metadataVal.IsNil() {
64+
return meta
65+
}
66+
metadataVal = metadataVal.Elem()
67+
}
68+
69+
if metadataVal.Kind() == reflect.Struct {
70+
nameFromMetadataField := metadataVal.FieldByName("Name")
71+
if nameFromMetadataField.IsValid() && nameFromMetadataField.Kind() == reflect.String && nameFromMetadataField.CanInterface() {
72+
meta.Set("name", nameFromMetadataField.String())
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
return meta
80+
}
81+
82+
func (t *TemplateApplier) applyTemplates(v reflect.Value, meta *Parameters) error {
83+
if !v.IsValid() {
84+
return nil
85+
}
86+
87+
switch v.Kind() {
88+
case reflect.Ptr:
89+
if v.IsNil() {
90+
return nil
91+
}
92+
return t.applyTemplates(v.Elem(), meta)
93+
case reflect.Interface:
94+
if v.IsNil() {
95+
return nil
96+
}
97+
return t.applyTemplates(v.Elem(), meta)
98+
case reflect.Struct:
99+
for i := 0; i < v.NumField(); i++ {
100+
// Only process exported fields
101+
if v.Type().Field(i).PkgPath != "" {
102+
continue
103+
}
104+
if err := t.applyTemplates(v.Field(i), meta); err != nil {
105+
return err
106+
}
107+
}
108+
case reflect.Slice, reflect.Array:
109+
for i := 0; i < v.Len(); i++ {
110+
if err := t.applyTemplates(v.Index(i), meta); err != nil {
111+
return err
112+
}
113+
}
114+
case reflect.Map:
115+
for _, key := range v.MapKeys() {
116+
val := v.MapIndex(key)
117+
// For map[string]string, apply directly
118+
if val.Kind() == reflect.String {
119+
str, err := ProcessTemplate(val.String(), t.variables, t.parameters, meta)
120+
if err != nil {
121+
return fmt.Errorf("template error for map key %v: %w", key, err)
122+
}
123+
v.SetMapIndex(key, reflect.ValueOf(str))
124+
} else {
125+
// For other map value types, recurse
126+
if err := t.applyTemplates(val, meta); err != nil {
127+
return err
128+
}
129+
}
130+
}
131+
case reflect.String:
132+
if v.CanSet() {
133+
str, err := ProcessTemplate(v.String(), t.variables, t.parameters, meta)
134+
if err != nil {
135+
return err
136+
}
137+
v.SetString(str)
138+
}
139+
}
140+
return nil
141+
}

internal/templating/templating.go

Lines changed: 10 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,104 +2,17 @@ package templating
22

33
import (
44
"fmt"
5-
"reflect"
65
"regexp"
76

8-
"github.com/jumpstarter-dev/jumpstarter-lab-config/internal/config"
97
"github.com/jumpstarter-dev/jumpstarter-lab-config/internal/vars"
108
)
119

12-
type TemplateApplier struct {
13-
variables *vars.Variables
14-
parameters *Parameters
15-
}
16-
17-
func NewTemplateApplier(cfg *config.Config, parameters *Parameters) (*TemplateApplier, error) {
18-
if cfg == nil {
19-
return nil, fmt.Errorf("config cannot be nil")
20-
}
21-
if cfg.Loaded == nil {
22-
return nil, fmt.Errorf("loaded config cannot be nil")
23-
}
24-
return &TemplateApplier{
25-
variables: cfg.Loaded.Variables,
26-
parameters: parameters,
27-
}, nil
28-
}
29-
30-
// ApplyTemplatesRecursively walks through all fields of the given object recursively,
31-
// and applies ProcessTemplate to every string field.
32-
func (t *TemplateApplier) Apply(obj interface{}) error {
33-
return t.applyTemplates(reflect.ValueOf(obj))
34-
}
35-
36-
func (t *TemplateApplier) applyTemplates(v reflect.Value) error {
37-
if !v.IsValid() {
38-
return nil
39-
}
40-
41-
switch v.Kind() {
42-
case reflect.Ptr:
43-
if v.IsNil() {
44-
return nil
45-
}
46-
return t.applyTemplates(v.Elem())
47-
case reflect.Interface:
48-
if v.IsNil() {
49-
return nil
50-
}
51-
return t.applyTemplates(v.Elem())
52-
case reflect.Struct:
53-
for i := 0; i < v.NumField(); i++ {
54-
// Only process exported fields
55-
if v.Type().Field(i).PkgPath != "" {
56-
continue
57-
}
58-
if err := t.applyTemplates(v.Field(i)); err != nil {
59-
return err
60-
}
61-
}
62-
case reflect.Slice, reflect.Array:
63-
for i := 0; i < v.Len(); i++ {
64-
if err := t.applyTemplates(v.Index(i)); err != nil {
65-
return err
66-
}
67-
}
68-
case reflect.Map:
69-
for _, key := range v.MapKeys() {
70-
val := v.MapIndex(key)
71-
// For map[string]string, apply directly
72-
if val.Kind() == reflect.String {
73-
str, err := ProcessTemplate(val.String(), t.variables, t.parameters)
74-
if err != nil {
75-
return fmt.Errorf("template error for map key %v: %w", key, err)
76-
}
77-
v.SetMapIndex(key, reflect.ValueOf(str))
78-
} else {
79-
// For other map value types, recurse
80-
if err := t.applyTemplates(val); err != nil {
81-
return err
82-
}
83-
}
84-
}
85-
case reflect.String:
86-
if v.CanSet() {
87-
str, err := ProcessTemplate(v.String(), t.variables, t.parameters)
88-
if err != nil {
89-
return err
90-
}
91-
v.SetString(str)
92-
}
93-
}
94-
return nil
95-
}
96-
97-
func ProcessTemplate(data string, variables *vars.Variables, parameters *Parameters) (string, error) {
10+
func ProcessTemplate(data string, variables *vars.Variables, parameters *Parameters, meta *Parameters) (string, error) {
9811
// This function would process the template using the provided variables.
9912
// For now, we will just return the template as-is for demonstration purposes.
10013
// In a real implementation, you would use a templating engine like text/template or html/template.
10114
if needsReplacements(data) {
102-
replacements, err := constructReplacementMap(variables, parameters)
15+
replacements, err := constructReplacementMap(variables, parameters, meta)
10316
if err != nil {
10417
return "", err
10518
}
@@ -116,7 +29,7 @@ func needsReplacements(data string) bool {
11629

11730
}
11831

119-
func constructReplacementMap(variables *vars.Variables, parameters *Parameters) (map[string]string, error) {
32+
func constructReplacementMap(variables *vars.Variables, parameters *Parameters, meta *Parameters) (map[string]string, error) {
12033
replacements := make(map[string]string)
12134

12235
// Add variables to the replacement map
@@ -133,6 +46,13 @@ func constructReplacementMap(variables *vars.Variables, parameters *Parameters)
13346
replacements["param."+key] = value
13447
}
13548

49+
// Add meta parameters to the replacement map
50+
if meta != nil {
51+
for key, value := range meta.parameters {
52+
replacements[key] = value
53+
}
54+
}
55+
13656
return replacements, nil
13757
}
13858

0 commit comments

Comments
 (0)