From e7ba299eaae8fa45e95d401929070127a5efaffe Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Fri, 17 Nov 2023 14:41:49 +0200 Subject: [PATCH] Extract template package Signed-off-by: Atanas Dinov --- pkg/build/grub.go | 15 ++----- pkg/build/raw.go | 14 ++++-- pkg/fileio/file_io.go | 32 +------------- pkg/fileio/file_io_test.go | 80 ----------------------------------- pkg/template/template.go | 25 +++++++++++ pkg/template/template_test.go | 72 +++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 126 deletions(-) create mode 100644 pkg/template/template.go create mode 100644 pkg/template/template_test.go diff --git a/pkg/build/grub.go b/pkg/build/grub.go index ae16ab52..eb857517 100644 --- a/pkg/build/grub.go +++ b/pkg/build/grub.go @@ -1,11 +1,11 @@ package build import ( - "bytes" _ "embed" "fmt" "strings" - "text/template" + + "github.com/suse-edge/edge-image-builder/pkg/template" ) //go:embed scripts/grub/guestfish-snippet.tpl @@ -19,11 +19,6 @@ func (b *Builder) generateGRUBGuestfishCommands() (string, error) { return "", nil } - tmpl, err := template.New("guestfish-snippet").Parse(guestfishSnippet) - if err != nil { - return "", fmt.Errorf("building template for GRUB guestfish snippet: %w", err) - } - argLine := strings.Join(b.imageConfig.OperatingSystem.KernelArgs, " ") values := struct { KernelArgs string @@ -31,12 +26,10 @@ func (b *Builder) generateGRUBGuestfishCommands() (string, error) { KernelArgs: argLine, } - var buff bytes.Buffer - err = tmpl.Execute(&buff, values) + snippet, err := template.Parse("guestfish-snippet", guestfishSnippet, values) if err != nil { - return "", fmt.Errorf("applying GRUB guestfish snippet: %w", err) + return "", fmt.Errorf("parsing guestfish template: %w", err) } - snippet := buff.String() return snippet, nil } diff --git a/pkg/build/raw.go b/pkg/build/raw.go index fff14dad..0ae3c338 100644 --- a/pkg/build/raw.go +++ b/pkg/build/raw.go @@ -3,10 +3,12 @@ package build import ( _ "embed" "fmt" + "os" "os/exec" "path/filepath" "github.com/suse-edge/edge-image-builder/pkg/fileio" + "github.com/suse-edge/edge-image-builder/pkg/template" ) const ( @@ -15,7 +17,7 @@ const ( ) //go:embed scripts/modify-raw-image.sh.tpl -var modifyRawImageScript string +var modifyRawImageTemplate string func (b *Builder) buildRawImage() error { cmd := b.createRawImageCopyCommand() @@ -66,10 +68,14 @@ func (b *Builder) writeModifyScript() error { ConfigureGRUB: grubConfiguration, } - filename := b.generateBuildDirFilename(modifyScriptName) + data, err := template.Parse(modifyScriptName, modifyRawImageTemplate, &values) + if err != nil { + return fmt.Errorf("parsing template: %w", err) + } - if err := fileio.WriteTemplate(filename, modifyRawImageScript, &values); err != nil { - return fmt.Errorf("writing modification script %s: %w", modifyScriptName, err) + filename := b.generateBuildDirFilename(modifyScriptName) + if err = os.WriteFile(filename, []byte(data), fileio.ExecutablePerms); err != nil { + return fmt.Errorf("writing modification script: %w", err) } return nil diff --git a/pkg/fileio/file_io.go b/pkg/fileio/file_io.go index 82035ef8..08969cd7 100644 --- a/pkg/fileio/file_io.go +++ b/pkg/fileio/file_io.go @@ -4,45 +4,15 @@ import ( "fmt" "io" "os" - "text/template" ) const ( // ExecutablePerms are Linux permissions (rwxr--r--) for executable files (scripts, binaries, etc.) ExecutablePerms os.FileMode = 0o744 - // NonExecutablePerms are Linux permissions (rw-r--r--) for non-executable files (configs, RPMs, etc.): + // NonExecutablePerms are Linux permissions (rw-r--r--) for non-executable files (configs, RPMs, etc.) NonExecutablePerms os.FileMode = 0o644 ) -func WriteTemplate(filename string, contents string, templateData any) error { - if templateData == nil { - return fmt.Errorf("template data not provided") - } - - tmpl, err := template.New(filename).Parse(contents) - if err != nil { - return fmt.Errorf("parsing template: %w", err) - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("creating file: %w", err) - } - defer func() { - _ = file.Close() - }() - - if err = file.Chmod(ExecutablePerms); err != nil { - return fmt.Errorf("applying executable permissions: %w", err) - } - - if err = tmpl.Execute(file, templateData); err != nil { - return fmt.Errorf("applying template: %w", err) - } - - return nil -} - func CopyFile(src string, dest string, perms os.FileMode) error { sourceFile, err := os.Open(src) if err != nil { diff --git a/pkg/fileio/file_io_test.go b/pkg/fileio/file_io_test.go index 76f10e9a..146c7e57 100644 --- a/pkg/fileio/file_io_test.go +++ b/pkg/fileio/file_io_test.go @@ -2,93 +2,13 @@ package fileio import ( "fmt" - "io/fs" "os" - "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestWriteTemplate(t *testing.T) { - const tmpDirPrefix = "eib-write-template-test-" - - tmpDir, err := os.MkdirTemp("", tmpDirPrefix) - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - tests := []struct { - name string - filename string - contents string - templateData any - expectedContents string - expectedErr string - }{ - { - name: "Templated file is successfully written", - filename: "template", - contents: "{{.Foo}} and {{.Bar}}", - templateData: struct { - Foo string - Bar string - }{ - Foo: "ooF", - Bar: "raB", - }, - expectedContents: "ooF and raB", - }, - { - name: "Templated file is not written due to missing data", - filename: "missing-data", - contents: "{{.Foo}} and {{.Bar}}", - expectedErr: "template data not provided", - }, - { - name: "Templated file is not written due to invalid syntax", - filename: "invalid-syntax", - contents: "{{.Foo and ", - templateData: struct{}{}, - expectedErr: fmt.Sprintf("parsing template: template: %s/invalid-syntax:1: unclosed action", tmpDir), - }, - { - name: "Templated file is not written due to missing field", - filename: "invalid-data", - contents: "{{.Foo}} and {{.Bar}}", - templateData: struct { - Foo string - }{ - Foo: "ooF", - }, - expectedErr: fmt.Sprintf("applying template: template: %[1]s/invalid-data:1:15: "+ - "executing \"%[1]s/invalid-data\" at <.Bar>: can't evaluate field Bar in type struct { Foo string }", tmpDir), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - filename := filepath.Join(tmpDir, test.filename) - - err := WriteTemplate(filename, test.contents, test.templateData) - - if test.expectedErr != "" { - assert.EqualError(t, err, test.expectedErr) - } else { - require.Nil(t, err) - - contents, err := os.ReadFile(filename) - require.NoError(t, err) - assert.Equal(t, test.expectedContents, string(contents)) - - info, err := os.Stat(filename) - require.NoError(t, err) - assert.Equal(t, fs.FileMode(0o744), info.Mode()) - } - }) - } -} - func TestCopyFile(t *testing.T) { const ( source = "file_io.go" // use the source code file as a valid input diff --git a/pkg/template/template.go b/pkg/template/template.go new file mode 100644 index 00000000..550a8571 --- /dev/null +++ b/pkg/template/template.go @@ -0,0 +1,25 @@ +package template + +import ( + "bytes" + "fmt" + "text/template" +) + +func Parse(name string, contents string, templateData any) (string, error) { + if templateData == nil { + return "", fmt.Errorf("template data not provided") + } + + tmpl, err := template.New(name).Parse(contents) + if err != nil { + return "", fmt.Errorf("parsing contents: %w", err) + } + + var buff bytes.Buffer + if err = tmpl.Execute(&buff, templateData); err != nil { + return "", fmt.Errorf("applying template: %w", err) + } + + return buff.String(), nil +} diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go new file mode 100644 index 00000000..b665cab1 --- /dev/null +++ b/pkg/template/template_test.go @@ -0,0 +1,72 @@ +package template + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + templateName string + contents string + templateData any + expectedOutput string + expectedErr string + }{ + { + name: "Template is successfully processed", + templateName: "valid-template", + contents: "{{.Foo}} and {{.Bar}}", + templateData: struct { + Foo string + Bar string + }{ + Foo: "ooF", + Bar: "raB", + }, + expectedOutput: "ooF and raB", + }, + { + name: "Templating fails due to missing data", + templateName: "missing-data", + contents: "{{.Foo}} and {{.Bar}}", + expectedErr: "template data not provided", + }, + { + name: "Templating fails due to invalid syntax", + templateName: "invalid-syntax", + contents: "{{.Foo and ", + templateData: struct{}{}, + expectedErr: "parsing contents: template: invalid-syntax:1: unclosed action", + }, + { + name: "Templating fails due to missing field", + templateName: "invalid-data", + contents: "{{.Foo}} and {{.Bar}}", + templateData: struct { + Foo string + }{ + Foo: "ooF", + }, + expectedErr: "applying template: template: invalid-data:1:15: " + + "executing \"invalid-data\" at <.Bar>: can't evaluate field Bar in type struct { Foo string }", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + data, err := Parse(test.templateName, test.contents, test.templateData) + + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + assert.Equal(t, "", data) + } else { + require.Nil(t, err) + assert.Equal(t, test.expectedOutput, data) + } + }) + } +}