Skip to content

Commit 17bdfc5

Browse files
committed
Partial multi-resource template refactoring and tests
1 parent da9f902 commit 17bdfc5

File tree

4 files changed

+177
-35
lines changed

4 files changed

+177
-35
lines changed

controllers/gateway_controller.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,13 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
186186
if tmpl, errs := parseSingleTemplate("status", tmplStr); errs != nil {
187187
logger.Info("unable to parse status template", "temporary error", errs)
188188
} else {
189-
if statusMap, errs := template2map(tmpl, &templateValues); errs != nil {
189+
if statusMap, errs := template2maps(tmpl, &templateValues); errs != nil {
190190
logger.Info("unable to render status template", "temporary error", errs, "template", tmplStr, "values", templateValues)
191191
} else {
192192
gw.Status.Addresses = []gatewayapi.GatewayAddress{}
193-
_, found := statusMap["addresses"]
193+
_, found := statusMap[0]["addresses"] // FIXME, more addresses?
194194
if found {
195-
addresses := statusMap["addresses"]
195+
addresses := statusMap[0]["addresses"]
196196
if errs := mapstructure.Decode(addresses, &gw.Status.Addresses); errs != nil {
197197
// This is probably not a temporary error
198198
logger.Error(errs, "unable to decode status data")

controllers/statuscheck.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,27 @@ limitations under the License.
3232
package controllers
3333

3434
import (
35+
"fmt"
36+
3537
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
3638
)
3739

3840
// Given a slice of template states, compute the overall
3941
// health/readiness status. The general approach is to test for a
4042
// `Ready` status condition, which is implemented through kstatus.
4143
func statusIsReady(templates []*ResourceTemplateState) (bool, error) {
42-
for _, tmplRes := range templates {
43-
if tmplRes.Resource.Current == nil {
44-
return false, nil
45-
}
46-
res, err := status.Compute(tmplRes.Resource.Current)
47-
if err != nil {
48-
return false, err
49-
}
50-
if res.Status != status.CurrentStatus {
51-
return false, nil
44+
for _, tmpl := range templates {
45+
for _, res := range tmpl.NewResource {
46+
if res.Current == nil {
47+
return false, nil
48+
}
49+
res, err := status.Compute(res.Current)
50+
if err != nil {
51+
return false, err
52+
}
53+
if res.Status != status.CurrentStatus {
54+
return false, nil
55+
}
5256
}
5357
}
5458
return true, nil
@@ -57,9 +61,11 @@ func statusIsReady(templates []*ResourceTemplateState) (bool, error) {
5761
// Build a list of template names which are not yet reconciled. Useful for status reporting
5862
func statusExistingTemplates(templates []*ResourceTemplateState) []string {
5963
var missing []string
60-
for _, tmplRes := range templates {
61-
if tmplRes.Resource.Current == nil {
62-
missing = append(missing, tmplRes.TemplateName)
64+
for _, tmpl := range templates {
65+
for resIdx, res := range tmpl.NewResource {
66+
if res.Current == nil {
67+
missing = append(missing, fmt.Sprintf("%s[%d]", tmpl.TemplateName, resIdx))
68+
}
6369
}
6470
}
6571
return missing

controllers/templating.go

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"context"
3737
"fmt"
3838
"io"
39+
"sort"
3940
"strings"
4041
"text/template"
4142

@@ -52,7 +53,7 @@ import (
5253
)
5354

5455
// Information about a resource, rendered format as well as actual in API server
55-
type Composite struct {
56+
type ResourceComposite struct {
5657
// The rendered resource
5758
Rendered *unstructured.Unstructured
5859

@@ -67,19 +68,19 @@ type Composite struct {
6768
}
6869

6970
// Rendering and applying templates is a multi-stage process. This
70-
// structure holds information about a rendered template between
71-
// stages
71+
// structure holds information about a template between stages
7272
type ResourceTemplateState struct {
7373
// Compiled template
7474
Template *template.Template
7575

7676
// Resource information, rendered and current
77-
Resource Composite
77+
Resource ResourceComposite // FIXME, refactoring - delete and replace with below
78+
NewResource []ResourceComposite // FIXME, refactoring temp name
7879

79-
// Name of rendered resource (from template key in GatewayClassBlueprint, not Kubernetes resource name)
80+
// Name of template (from template key in GatewayClassBlueprint, not Kubernetes resource name)
8081
TemplateName string
8182

82-
// Raw template for resource
83+
// Raw template
8384
StringTemplate string
8485
}
8586

@@ -133,9 +134,13 @@ func parseTemplates(resourceTemplates map[string]string) ([]*ResourceTemplateSta
133134
if err != nil {
134135
return nil, fmt.Errorf("cannot parse template %q: %w", tmplKey, err)
135136
}
137+
r.NewResource = make([]ResourceComposite, 1)
136138
templates = append(templates, &r)
137139
}
138140

141+
// Sort to increase predictability
142+
sort.SliceStable(templates, func(i, j int) bool { return templates[i].TemplateName < templates[j].TemplateName })
143+
139144
return templates, nil
140145
}
141146

@@ -153,7 +158,7 @@ func renderTemplates(ctx context.Context, r ControllerDynClient, parent metav1.O
153158

154159
for _, tmplRes := range templates {
155160
if tmplRes.Resource.Rendered == nil {
156-
tmplRes.Resource.Rendered, err = template2Unstructured(tmplRes.Template, values)
161+
//tmplRes.Resource.Rendered, err = template2Unstructured(tmplRes.Template, values) // FIXME
157162
if err != nil {
158163
if isFinalAttempt {
159164
logger.Error(err, "cannot render template", "templateName", tmplRes.TemplateName)
@@ -199,9 +204,9 @@ func renderTemplates(ctx context.Context, r ControllerDynClient, parent metav1.O
199204
func buildResourceValues(templates []*ResourceTemplateState) map[string]any {
200205
resources := map[string]any{}
201206

202-
for _, tmplRes := range templates {
203-
if tmplRes.Resource.Current != nil {
204-
resources[tmplRes.TemplateName] = tmplRes.Resource.Current.UnstructuredContent()
207+
for _, tmpl := range templates {
208+
if tmpl.Resource.Current != nil {
209+
resources[tmpl.TemplateName] = tmpl.Resource.Current.UnstructuredContent()
205210
}
206211
}
207212
return resources
@@ -272,26 +277,38 @@ func templateRender(tmpl *template.Template, templateValues *TemplateValues) (*b
272277
return &buffer, nil
273278
}
274279

275-
func template2map(tmpl *template.Template, tmplValues *TemplateValues) (map[string]any, error) {
280+
func template2maps(tmpl *template.Template, tmplValues *TemplateValues) ([]map[string]any, error) {
276281
renderBuffer, err := templateRender(tmpl, tmplValues)
277282
if err != nil {
278283
return nil, err
279284
}
280285

281-
rawResource := map[string]any{}
282-
err = yaml.Unmarshal(renderBuffer.Bytes(), &rawResource)
283-
if err != nil {
284-
return nil, err
286+
rawSlice := bytes.SplitN(renderBuffer.Bytes(), []byte("---"), -1)
287+
resources := make([]map[string]any, 0, len(rawSlice))
288+
for _, raw := range rawSlice {
289+
r := map[string]any{}
290+
err = yaml.Unmarshal(raw, &r)
291+
if err != nil {
292+
return nil, err
293+
}
294+
if len(r) == 0 {
295+
continue // Empty resource
296+
}
297+
resources = append(resources, r)
285298
}
286-
return rawResource, nil
299+
return resources, nil
287300
}
288301

289-
func template2Unstructured(tmpl *template.Template, tmplValues *TemplateValues) (*unstructured.Unstructured, error) {
290-
rawResource, err := template2map(tmpl, tmplValues)
302+
func template2Unstructured(tmpl *template.Template, tmplValues *TemplateValues) ([]unstructured.Unstructured, error) {
303+
rawResources, err := template2maps(tmpl, tmplValues)
291304
if err != nil {
292305
return nil, err
293306
}
294-
return &unstructured.Unstructured{Object: rawResource}, nil
307+
uu := make([]unstructured.Unstructured, 0, len(rawResources))
308+
for _, r := range rawResources {
309+
uu = append(uu, unstructured.Unstructured{Object: r})
310+
}
311+
return uu, nil
295312
}
296313

297314
// Prepare a resource like Gateway or HTTPRoute for use in templates

controllers/templating_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package controllers
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/util/yaml"
5+
"testing"
6+
)
7+
8+
func TestParseSingleTemplate(t *testing.T) {
9+
template := "foo"
10+
tmpl, err := parseSingleTemplate("foo", template)
11+
if tmpl == nil || err != nil {
12+
t.Fatalf("Error parsing template %v", err)
13+
}
14+
}
15+
16+
var textTemplate = `
17+
t1: |
18+
name: {{ .Values.name1 }}
19+
t2: |
20+
{{ if .Values.t2enable }}
21+
name: {{ .Values.name2 }}
22+
{{ end }}
23+
t3: |
24+
{{ range .Values.t3data }}
25+
name: {{ $.Values.name3 }}-{{ . }}
26+
---
27+
{{ end }}
28+
`
29+
30+
var textValues = `
31+
name1: t1name
32+
name2: t2name
33+
name3: t3name
34+
t2enable: false
35+
t3data:
36+
- foo1
37+
- foo2
38+
- foo3
39+
`
40+
41+
func helperGetResourceState() ([]*ResourceTemplateState, error) {
42+
templates := map[string]string{}
43+
_ = yaml.Unmarshal([]byte(textTemplate), &templates)
44+
return parseTemplates(templates)
45+
}
46+
47+
func helperGetValues() *TemplateValues {
48+
values := map[string]any{}
49+
_ = yaml.Unmarshal([]byte(textValues), &values)
50+
templateValues := TemplateValues{
51+
Values: values,
52+
}
53+
return &templateValues
54+
}
55+
56+
func TestParseTemplate(t *testing.T) {
57+
tmpl, err := helperGetResourceState()
58+
if tmpl == nil || err != nil {
59+
t.Fatalf("Error parsing templates %v", err)
60+
}
61+
if len(tmpl) != 2 {
62+
t.Fatalf("Template slice lenght mismatch, got %v, expected 2", len(tmpl))
63+
}
64+
if tmpl[0].TemplateName != "t1" {
65+
t.Fatalf("Template[0] name, got %v, expected t1", tmpl[0].TemplateName)
66+
}
67+
}
68+
69+
func TestTemplate2map(t *testing.T) {
70+
tmpl, err := helperGetResourceState()
71+
tmplValues := helperGetValues()
72+
rawResources, err := template2maps(tmpl[0].Template, tmplValues)
73+
if rawResources == nil {
74+
t.Fatalf("Cannot render template to map: %v", err)
75+
}
76+
if len(rawResources) != 1 {
77+
t.Fatalf("Error rendering resource, got len %v, expected 1", len(rawResources))
78+
}
79+
if rawResources[0]["name"] != "t1name" {
80+
t.Fatalf("Rendered template error, got %v, expected 't1name'", rawResources[0]["name"])
81+
}
82+
rawResources, err = template2maps(tmpl[1].Template, tmplValues)
83+
if err != nil {
84+
t.Fatalf("Error rendering empty resource, got err %v", err)
85+
}
86+
if len(rawResources) != 0 {
87+
t.Fatalf("Error rendering empty resource, got len %v, expected 0", len(rawResources))
88+
}
89+
rawResources, err = template2maps(tmpl[2].Template, tmplValues)
90+
if err != nil {
91+
t.Fatalf("Error rendering multi-resource, got err %v", err)
92+
}
93+
if len(rawResources) != 3 {
94+
t.Fatalf("Error rendering multi-resource, got len %v, expected 3", len(rawResources))
95+
}
96+
}
97+
98+
func TestTemplate2Unstructured(t *testing.T) {
99+
tmpl, err := helperGetResourceState()
100+
tmplValues := helperGetValues()
101+
u, err := template2Unstructured(tmpl[0].Template, tmplValues)
102+
if u == nil {
103+
t.Fatalf("Cannot render template to map: %v", err)
104+
}
105+
u, err = template2Unstructured(tmpl[1].Template, tmplValues)
106+
if err != nil {
107+
t.Fatalf("Error rendering empty resource, got err %v", err)
108+
}
109+
if len(u) != 0 {
110+
t.Fatalf("Error rendering empty resource, got %v, expected 0", len(u))
111+
}
112+
u, err = template2Unstructured(tmpl[2].Template, tmplValues)
113+
if err != nil {
114+
t.Fatalf("Error rendering multi-resource, got err %v", err)
115+
}
116+
if len(u) != 3 {
117+
t.Fatalf("Error rendering multi-resource, got len %v, expected 3", len(u))
118+
}
119+
}

0 commit comments

Comments
 (0)