From 67d7fbcca8eee8e2a42c8aa373ef7fdc419ddf3c Mon Sep 17 00:00:00 2001 From: Amartya Sinha Date: Thu, 9 Apr 2026 18:44:31 +0530 Subject: [PATCH] util: Add MultiTemplateDir to support multi-segment path in template A new parameter MultiTemplateDir is introduced in Template struct. It takes the multi-segment path which we have inside templates dir and then continues to execute the templates. Earlier, only single- segment path (kind) was supported through InstanceType. Signed-off-by: Amartya Sinha --- modules/common/util/errors.go | 4 + modules/common/util/template_util.go | 32 ++++++-- modules/common/util/template_util_test.go | 79 +++++++++++++++++-- .../templates/test/service/config/bar.conf | 18 +++++ .../templates/test/service/config/config.json | 3 + .../templates/test/service/config/foo.conf | 4 + 6 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 modules/common/util/testdata/templates/test/service/config/bar.conf create mode 100644 modules/common/util/testdata/templates/test/service/config/config.json create mode 100644 modules/common/util/testdata/templates/test/service/config/foo.conf diff --git a/modules/common/util/errors.go b/modules/common/util/errors.go index 63894f64..33f9b2d2 100644 --- a/modules/common/util/errors.go +++ b/modules/common/util/errors.go @@ -25,4 +25,8 @@ var ( ErrNoPodSubdomain = errors.New("no subdomain or hostname") // ErrPodsInterfaces indicates that pod interfaces aren't configured ErrPodsInterfaces = errors.New("not all pods have interfaces") + // ErrTemplateSubdirUnset indicates no template subdir for a non-none template type + ErrTemplateSubdirUnset = errors.New("template subdir not set") + // ErrInstanceTypeUnsetWithMultiTemplateDir indicates InstanceType is empty while MultiTemplateDir is set + ErrInstanceTypeUnsetWithMultiTemplateDir = errors.New("instance type not set") ) diff --git a/modules/common/util/template_util.go b/modules/common/util/template_util.go index 72f24fd1..b763a46f 100644 --- a/modules/common/util/template_util.go +++ b/modules/common/util/template_util.go @@ -59,6 +59,7 @@ type Template struct { ConfigOptions map[string]interface{} // map of parameters as input data to render the templates SkipSetOwner bool // skip setting ownership on the associated configmap Version string // optional version string to separate templates inside the InstanceType/Type directory. E.g. placementapi/config/18.0 + MultiTemplateDir string // templates dir for multi-group operators, e.g. nova/api; requires InstanceType to be set } // GetTemplatesPath get path to templates, either running local or deployed as container @@ -83,20 +84,21 @@ func GetTemplatesPath() (string, error) { // GetAllTemplates - get all template files // -// The structur of the folder is, base path, the kind (CRD in lower case), +// The structure of the folder is, base path, subdir, templateType, version // - path - base path of the templates folder -// - kind - sub folder for the CRDs templates +// - subdir - directory under that root: one segment (legacy InstanceType, e.g. NovaAPI) or +// multi-segment (e.g. nova/api from MultiTemplateDir) // - templateType - TType of the templates. When the templates got rendered and added to a CM // this information is e.g. used for the permissions they get mounted into the pod // - version - if there need to be templates for different versions, they can be stored in a version subdir // // Sub directories inside the specified directory with the above parameters get ignored. -func GetAllTemplates(path string, kind string, templateType string, version string) []string { +func GetAllTemplates(path string, subdir string, templateType string, version string) []string { - templatePath := filepath.Join(path, strings.ToLower(kind), templateType, "*") + templatePath := filepath.Join(path, subdir, templateType, "*") if version != "" { - templatePath = filepath.Join(path, strings.ToLower(kind), templateType, version, "*") + templatePath = filepath.Join(path, subdir, templateType, version, "*") } templatesFiles, err := filepath.Glob(templatePath) @@ -293,8 +295,24 @@ func GetTemplateData(t Template) (map[string]string, error) { data := make(map[string]string) if t.Type != TemplateTypeNone { - // get all scripts templates which are in ../templesPath/cr.Kind/CMType/ - templatesFiles := GetAllTemplates(templatesPath, t.InstanceType, string(t.Type), string(t.Version)) + // If MultiTemplateDir is set but InstanceType is not, return an error + // though we do not use InstanceType here, but it is used in secret.go + if t.MultiTemplateDir != "" && t.InstanceType == "" { + return nil, ErrInstanceTypeUnsetWithMultiTemplateDir + } + + var templateSubdir string + // if MultiTemplateDir is set, it will take precedence over InstanceType + // otherwise, InstanceType is used to create the template subdir based on CRD name + if t.MultiTemplateDir != "" { + templateSubdir = t.MultiTemplateDir + } else { + templateSubdir = strings.ToLower(t.InstanceType) + } + if templateSubdir == "" { + return nil, fmt.Errorf("%w: type is %q", ErrTemplateSubdirUnset, t.Type) + } + templatesFiles := GetAllTemplates(templatesPath, templateSubdir, string(t.Type), string(t.Version)) // render all template files for _, file := range templatesFiles { diff --git a/modules/common/util/template_util_test.go b/modules/common/util/template_util_test.go index 41dc40eb..75d3c505 100644 --- a/modules/common/util/template_util_test.go +++ b/modules/common/util/template_util_test.go @@ -300,14 +300,14 @@ func TestGetAllTemplates(t *testing.T) { tests := []struct { name string - kind string + subdir string tmplType TType version string want []string }{ { name: "Get TemplateTypeConfig templates with no version", - kind: "testservice", + subdir: "testservice", tmplType: TemplateTypeConfig, version: "", want: []string{ @@ -318,7 +318,7 @@ func TestGetAllTemplates(t *testing.T) { }, { name: "Get TemplateTypeScripts templates with version", - kind: "testservice", + subdir: "testservice", tmplType: TemplateTypeScripts, version: "1.0", want: []string{ @@ -334,7 +334,7 @@ func TestGetAllTemplates(t *testing.T) { p, _ := GetTemplatesPath() g.Expect(p).To(BeADirectory()) - templatesFiles := GetAllTemplates(p, tt.kind, string(tt.tmplType), tt.version) + templatesFiles := GetAllTemplates(p, tt.subdir, string(tt.tmplType), tt.version) g.Expect(templatesFiles).To(HaveLen(len(tt.want))) g.Expect(templatesFiles).Should(HaveEach(BeARegularFile())) @@ -366,7 +366,7 @@ func TestGetTemplateData(t *testing.T) { Name: "testservice", Namespace: "somenamespace", Type: TemplateTypeConfig, - InstanceType: "testservice", + InstanceType: "TestService", Version: "", ConfigOptions: map[string]interface{}{ "ServiceUser": "foo", @@ -382,6 +382,75 @@ func TestGetTemplateData(t *testing.T) { }, error: false, }, + { + name: "Render TemplateTypeConfig using MultiTemplateDir with InstanceType", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + InstanceType: "wrong-unused", + MultiTemplateDir: "testservice", + Version: "", + ConfigOptions: map[string]interface{}{ + "ServiceUser": "foo", + "Count": 1, + "Upper": "BAR", + }, + AdditionalTemplate: map[string]string{}, + }, + want: map[string]string{ + "bar.conf": "[DEFAULT]\nstate_path = /var/lib/nova\ndebug=true\nsome_parameter_with_brackets=[test]\ncompute_driver = libvirt.LibvirtDriver\n\n[oslo_concurrency]\nlock_path = /var/lib/nova/tmp\n", + "config.json": "{\n \"command\": \"/usr/sbin/httpd -DFOREGROUND\",\n}\n", + "foo.conf": "username = foo\ncount = 1\nadd = 3\nlower = bar\n", + }, + error: false, + }, + { + name: "Render TemplateTypeConfig using MultiTemplateDir test/service with InstanceType", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + InstanceType: "wrong-unused", + MultiTemplateDir: "test/service", + Version: "", + ConfigOptions: map[string]interface{}{ + "ServiceUser": "foo", + "Count": 1, + "Upper": "BAR", + }, + AdditionalTemplate: map[string]string{}, + }, + want: map[string]string{ + "bar.conf": "[DEFAULT]\nstate_path = /var/lib/nova\ndebug=true\nsome_parameter_with_brackets=[test]\ncompute_driver = libvirt.LibvirtDriver\n\n[oslo_concurrency]\nlock_path = /var/lib/nova/tmp\n", + "config.json": "{\n \"command\": \"/usr/sbin/httpd -DFOREGROUND\",\n}\n", + "foo.conf": "username = foo\ncount = 1\nadd = 3\nlower = bar\n", + }, + error: false, + }, + { + name: "TemplateTypeConfig with MultiTemplateDir and empty InstanceType errors", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + MultiTemplateDir: "testservice", + ConfigOptions: map[string]interface{}{"ServiceUser": "foo"}, + }, + want: map[string]string{}, + error: true, + }, + { + name: "TemplateTypeConfig with empty InstanceType and MultiTemplateDir errors", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + ConfigOptions: map[string]interface{}{"ServiceUser": "foo"}, + }, + want: map[string]string{}, + error: true, + }, { name: "Render TemplateTypeScripts templates with version", tmpl: Template{ diff --git a/modules/common/util/testdata/templates/test/service/config/bar.conf b/modules/common/util/testdata/templates/test/service/config/bar.conf new file mode 100644 index 00000000..5bfc2e11 --- /dev/null +++ b/modules/common/util/testdata/templates/test/service/config/bar.conf @@ -0,0 +1,18 @@ +{{define "bar-template"}} +[DEFAULT] +state_path = /var/lib/nova + + +debug=true + +some_parameter_with_brackets=[test] +compute_driver = libvirt.LibvirtDriver + + + + +[oslo_concurrency] +lock_path = /var/lib/nova/tmp +{{end}} +{{- $var := execTempl "bar-template" . | removeNewLinesInSections -}} +{{$var -}} diff --git a/modules/common/util/testdata/templates/test/service/config/config.json b/modules/common/util/testdata/templates/test/service/config/config.json new file mode 100644 index 00000000..ff57ee8b --- /dev/null +++ b/modules/common/util/testdata/templates/test/service/config/config.json @@ -0,0 +1,3 @@ +{ + "command": "/usr/sbin/httpd -DFOREGROUND", +} diff --git a/modules/common/util/testdata/templates/test/service/config/foo.conf b/modules/common/util/testdata/templates/test/service/config/foo.conf new file mode 100644 index 00000000..0c223d3d --- /dev/null +++ b/modules/common/util/testdata/templates/test/service/config/foo.conf @@ -0,0 +1,4 @@ +username = {{ .ServiceUser }} +count = {{ .Count }} +add = {{ (add 1 2) }} +lower = {{ (lower .Upper) }}