From d9780ce7490418b3b414c6ec2e77729f3dfd9d9a Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 14 Nov 2023 17:37:30 +0200 Subject: [PATCH] Bootstrap fileio package (#40) * Drop receiver from writeFile Signed-off-by: Atanas Dinov * Extract writeFile to fileio package Signed-off-by: Atanas Dinov * Restructure file writing Signed-off-by: Atanas Dinov * Test file writing Signed-off-by: Atanas Dinov * Extract copyFile to fileio package Signed-off-by: Atanas Dinov * Test file copying Signed-off-by: Atanas Dinov * Drop unused copy function Signed-off-by: Atanas Dinov * Use temporary dir in tests Signed-off-by: Atanas Dinov --------- Signed-off-by: Atanas Dinov --- pkg/build/build.go | 76 +------------------ pkg/build/build_test.go | 30 -------- pkg/build/rpm.go | 4 +- pkg/build/scripts.go | 4 +- pkg/fileio/file_io.go | 61 +++++++++++++++ pkg/fileio/file_io_test.go | 147 +++++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 105 deletions(-) create mode 100644 pkg/fileio/file_io.go create mode 100644 pkg/fileio/file_io_test.go diff --git a/pkg/build/build.go b/pkg/build/build.go index f8232f62..474208d8 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -3,18 +3,12 @@ package build import ( _ "embed" "fmt" - "io" "os" "path/filepath" "slices" - "text/template" "github.com/suse-edge/edge-image-builder/pkg/config" -) - -const ( - // nolint: unused - embeddedScriptsBaseDir = "scripts" + "github.com/suse-edge/edge-image-builder/pkg/fileio" ) //go:embed scripts/script_base.sh @@ -149,55 +143,12 @@ func (b *Builder) generateCombustionScript() error { func (b *Builder) writeBuildDirFile(filename string, contents string, templateData any) (string, error) { destFilename := filepath.Join(b.eibBuildDir, filename) - return destFilename, b.writeFile(destFilename, contents, templateData) + return destFilename, fileio.WriteFile(destFilename, contents, templateData) } func (b *Builder) writeCombustionFile(filename string, contents string, templateData any) (string, error) { destFilename := filepath.Join(b.combustionDir, filename) - return destFilename, b.writeFile(destFilename, contents, templateData) -} - -func (b *Builder) writeFile(filename string, contents string, templateData any) error { - if templateData != nil { - tmpl, err := template.New(filename).Parse(contents) - if err != nil { - return fmt.Errorf("creating template for file %s: %w", filename, err) - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("creating file %s: %w", filename, err) - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return fmt.Errorf("applying the template at %s: %w", filename, err) - } - } else { - err := os.WriteFile(filename, []byte(contents), os.ModePerm) - if err != nil { - return fmt.Errorf("writing file %s: %w", filename, err) - } - } - return nil -} - -// nolint: unused -func (b *Builder) copyCombustionFile(scriptSubDir string, scriptName string) error { - sourcePath := filepath.Join(embeddedScriptsBaseDir, scriptSubDir, scriptName) - src, err := os.ReadFile(sourcePath) - if err != nil { - return fmt.Errorf("reading file: %w", err) - } - - destFilename := filepath.Join(b.combustionDir, filepath.Base(sourcePath)) - err = os.WriteFile(destFilename, src, os.ModePerm) - if err != nil { - return fmt.Errorf("writing file: %w", err) - } - - return nil + return destFilename, fileio.WriteFile(destFilename, contents, templateData) } func (b *Builder) registerCombustionScript(scriptName string) { @@ -217,24 +168,3 @@ func (b *Builder) generateBaseImageFilename() string { filename := filepath.Join(b.buildConfig.ImageConfigDir, "images", b.imageConfig.Image.BaseImage) return filename } - -func copyFile(sourcePath string, destPath string) error { - sourceFile, err := os.Open(sourcePath) - if err != nil { - return fmt.Errorf("opening file from source path: %w", err) - } - defer sourceFile.Close() - - destFile, err := os.Create(destPath) - if err != nil { - return fmt.Errorf("creating file at dest path: %w", err) - } - defer destFile.Close() - - _, err = io.Copy(destFile, sourceFile) - if err != nil { - return fmt.Errorf("copying file from source path to dest path: %w", err) - } - - return nil -} diff --git a/pkg/build/build_test.go b/pkg/build/build_test.go index bc17fb19..b4b81602 100644 --- a/pkg/build/build_test.go +++ b/pkg/build/build_test.go @@ -164,33 +164,3 @@ func TestWriteBuildDirFile(t *testing.T) { require.NoError(t, err) assert.Equal(t, testData, string(foundData)) } - -func TestWriteFileWithTemplate(t *testing.T) { - // Setup - builder := New(nil, &config.BuildConfig{}) - - tmpDir, err := os.MkdirTemp("", "eib-test-") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - testData := "{{.Foo}} and {{.Bar}}" - values := struct { - Foo string - Bar string - }{ - Foo: "ooF", - Bar: "raB", - } - testFilename := filepath.Join(tmpDir, "write-file-with-template.sh") - - // Test - err = builder.writeFile(testFilename, testData, &values) - - // Verify - require.NoError(t, err) - - expectedFilename := filepath.Join(builder.eibBuildDir, testFilename) - foundData, err := os.ReadFile(expectedFilename) - require.NoError(t, err) - assert.Equal(t, "ooF and raB", string(foundData)) -} diff --git a/pkg/build/rpm.go b/pkg/build/rpm.go index 48e37d44..ece27326 100644 --- a/pkg/build/rpm.go +++ b/pkg/build/rpm.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/suse-edge/edge-image-builder/pkg/fileio" ) func (b *Builder) getRPMFileNames(rpmSourceDir string) ([]string, error) { @@ -48,7 +50,7 @@ func (b *Builder) copyRPMs() error { sourcePath := filepath.Join(rpmSourceDir, rpm) destPath := filepath.Join(rpmDestDir, rpm) - err = copyFile(sourcePath, destPath) + err = fileio.CopyFile(sourcePath, destPath) if err != nil { return fmt.Errorf("copying file %s: %w", sourcePath, err) } diff --git a/pkg/build/scripts.go b/pkg/build/scripts.go index 14cbbecb..364b819d 100644 --- a/pkg/build/scripts.go +++ b/pkg/build/scripts.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/suse-edge/edge-image-builder/pkg/fileio" ) const ( @@ -37,7 +39,7 @@ func (b *Builder) configureScripts() error { copyMe := filepath.Join(fullScriptsDir, scriptEntry.Name()) copyTo := filepath.Join(b.combustionDir, scriptEntry.Name()) - err = copyFile(copyMe, copyTo) + err = fileio.CopyFile(copyMe, copyTo) if err != nil { return fmt.Errorf("copying script to %s: %w", copyTo, err) } diff --git a/pkg/fileio/file_io.go b/pkg/fileio/file_io.go new file mode 100644 index 00000000..f68569a1 --- /dev/null +++ b/pkg/fileio/file_io.go @@ -0,0 +1,61 @@ +package fileio + +import ( + "fmt" + "io" + "os" + "text/template" +) + +func WriteFile(filename string, contents string, templateData any) error { + if templateData == nil { + if err := os.WriteFile(filename, []byte(contents), os.ModePerm); err != nil { + return fmt.Errorf("writing file: %w", err) + } + + return nil + } + + 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 = tmpl.Execute(file, templateData); err != nil { + return fmt.Errorf("applying template: %w", err) + } + + return nil +} + +func CopyFile(src string, dest string) error { + sourceFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("opening source file: %w", err) + } + defer func() { + _ = sourceFile.Close() + }() + + destFile, err := os.Create(dest) + if err != nil { + return fmt.Errorf("creating destination file: %w", err) + } + defer func() { + _ = destFile.Close() + }() + + if _, err = io.Copy(destFile, sourceFile); err != nil { + return fmt.Errorf("copying file: %w", err) + } + + return nil +} diff --git a/pkg/fileio/file_io_test.go b/pkg/fileio/file_io_test.go new file mode 100644 index 00000000..71009db8 --- /dev/null +++ b/pkg/fileio/file_io_test.go @@ -0,0 +1,147 @@ +package fileio + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteFile(t *testing.T) { + const tmpDirPrefix = "eib-write-file-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: "Standard file is successfully written", + filename: "standard", + contents: "this is a non-templated file", + expectedContents: "this is a non-templated file", + }, + { + 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 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 := WriteFile(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)) + } + }) + } +} + +func TestCopyFile(t *testing.T) { + const ( + source = "file_io.go" // use the source code file as a valid input + destDirPrefix = "eib-copy-file-test-" + ) + + tmpDir, err := os.MkdirTemp("", destDirPrefix) + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + tests := []struct { + name string + source string + destination string + expectedErr string + }{ + { + name: "Source file does not exist", + source: "", + expectedErr: "opening source file: open : no such file or directory", + }, + { + name: "Destination is an empty file", + source: source, + destination: "", + expectedErr: "creating destination file: open : no such file or directory", + }, + { + name: "Destination is a directory", + source: source, + destination: tmpDir, + expectedErr: fmt.Sprintf("creating destination file: open %s: is a directory", tmpDir), + }, + { + name: "File is successfully copied", + source: source, + destination: fmt.Sprintf("%s/copy.go", tmpDir), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := CopyFile(test.source, test.destination) + + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + } else { + require.Nil(t, err) + + src, err := os.ReadFile(test.source) + require.NoError(t, err) + + dest, err := os.ReadFile(test.destination) + require.NoError(t, err) + + assert.Equal(t, src, dest) + } + }) + } +}