diff --git a/cmd/system_update.go b/cmd/system_update.go index 6609e21..304846f 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -142,7 +142,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { } if err := logging.LogOperation(func() error { - return unpack.InstallResource(dis.RuntimeDir(), "runtime", core.Runtime, func(dst, src string) { + return unpack.InstallDir(dis.RuntimeDir(), "runtime", core.Runtime, func(dst, src string) { context.Printf("[copy] %s\n", dst) }) }, context.IOStream, "Unpacking Runtime Components"); err != nil { diff --git a/component/installable.go b/component/installable.go index bcf2eb2..bcca104 100644 --- a/component/installable.go +++ b/component/installable.go @@ -45,7 +45,7 @@ type InstallationContext map[string]string func (is Installable) Install(io stream.IOStream, context InstallationContext) error { if is.ContextPath != "" { // setup the base files - if err := unpack.InstallResource( + if err := unpack.InstallDir( is.Dir, is.ContextPath, is.Resources, diff --git a/internal/targz/targz.go b/internal/targz/targz.go index 8e54603..7aa0c9f 100644 --- a/internal/targz/targz.go +++ b/internal/targz/targz.go @@ -1,3 +1,4 @@ +// Package targz provides facilities for packaging tar.gz files package targz import ( @@ -9,7 +10,9 @@ import ( "path/filepath" ) -// Package packages the directory src into dst. +// Package packages the source directory into a 'tar.gz' file into destination. +// If the destination already exists, it is truncated. +// // onCopy, when not nil, is called for each file being copied into the archive. func Package(dst, src string, onCopy func(rel string, src string)) (count int64, err error) { // create the target archive diff --git a/internal/unpack/file.go b/internal/unpack/file.go deleted file mode 100644 index beef8a0..0000000 --- a/internal/unpack/file.go +++ /dev/null @@ -1,76 +0,0 @@ -package unpack - -import ( - "io" - "io/fs" - "os" - - "github.com/pkg/errors" -) - -// InstallFile installs the file from src into dst. -// -// If the destination path does not exist, it is created. -func InstallFile(dst string, src fs.File) error { - // stat it! - srcInfo, err := src.Stat() - if err != nil { - return err - } - - // if this is a directory, something went wrong! - if srcInfo.IsDir() { - return errExpectedFileButGotDirectory - } - - // and store it there! - return installFile(dst, srcInfo, src) -} - -func installFile(dst string, srcInfo fs.FileInfo, src fs.File) error { - // create the file using the right mode! - file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) - if err != nil { - return err - } - defer file.Close() - - // copy over the content! - _, err = io.Copy(file, src) - return errors.Wrapf(err, "Error writing to destination %s", dst) -} - -// InstallTemplate unpacks the resource located at src in fsys, then processes it as a template, and eventually writes it to dst. -// Any existing file is truncated and overwritten. -// -// See [WriteTemplate] for possible errors. -func InstallTemplate(dst string, context map[string]string, src string, fsys fs.FS) error { - - // open the srcFile - srcFile, err := fsys.Open(src) - if err != nil { - return err - } - defer srcFile.Close() - - // stat it - srcInfo, err := srcFile.Stat() - if err != nil { - return err - } - - // check if it is a directory - if srcInfo.IsDir() { - return errExpectedFileButGotDirectory - } - - // open the destination file - file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) - if err != nil { - return err - } - defer file.Close() - - // write the file! - return WriteTemplate(file, context, srcFile) -} diff --git a/internal/unpack/dir.go b/internal/unpack/resource.go similarity index 60% rename from internal/unpack/dir.go rename to internal/unpack/resource.go index b82d0a4..58f7e80 100644 --- a/internal/unpack/dir.go +++ b/internal/unpack/resource.go @@ -1,6 +1,7 @@ package unpack import ( + "io" "io/fs" "os" "path/filepath" @@ -8,6 +9,9 @@ import ( "github.com/pkg/errors" ) +var errExpectedFileButGotDirectory = errors.New("expected a file, but got a directory") +var errExpectedDirectoryButGotFile = errors.New("expected a directory, but got a file") + // InstallDir installs the directory at src within fsys to dst. // // onInstallFile is called for each file or directory being installed. @@ -42,6 +46,38 @@ func InstallDir(dst string, src string, fsys fs.FS, onInstallFile func(dst, src return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile) } +// installResource installs the resource at src within fsys to dst. +// +// OnInstallFile is called for each source and destination file. +// OnInstallFile may be nil. +func installResource(dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { + // open the srcFile + srcFile, err := fsys.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + // stat it! + srcInfo, err := srcFile.Stat() + if err != nil { + return err + } + + // call the hook (if any) + if onInstallFile != nil { + onInstallFile(dst, src) + } + + // this is a directory, so the cast is safe! + if srcInfo.IsDir() { + return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile) + } + + // this is a regular file! + return installFile(dst, srcInfo, srcFile) +} + func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { // create the destination dstStat, dstErr := os.Stat(dst) @@ -68,7 +104,7 @@ func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src str // iterate over all the children for _, entry := range entries { - if err := InstallResource( + if err := installResource( filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name()), fsys, @@ -80,3 +116,16 @@ func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src str return nil } + +func installFile(dst string, srcInfo fs.FileInfo, src fs.File) error { + // create the file using the right mode! + file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) + if err != nil { + return err + } + defer file.Close() + + // copy over the content! + _, err = io.Copy(file, src) + return errors.Wrapf(err, "Error writing to destination %s", dst) +} diff --git a/internal/unpack/template.go b/internal/unpack/template.go index 1dcd3c8..f4dc65a 100644 --- a/internal/unpack/template.go +++ b/internal/unpack/template.go @@ -5,6 +5,8 @@ import ( "bufio" "fmt" "io" + "io/fs" + "os" "strings" "golang.org/x/exp/maps" @@ -194,3 +196,38 @@ parseloop: return nil } + +// InstallTemplate unpacks the resource located at src in fsys, then processes it as a template, and eventually writes it to dst. +// Any existing file is truncated and overwritten. +// +// See [WriteTemplate] for possible errors. +func InstallTemplate(dst string, context map[string]string, src string, fsys fs.FS) error { + + // open the srcFile + srcFile, err := fsys.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + // stat it + srcInfo, err := srcFile.Stat() + if err != nil { + return err + } + + // check if it is a directory + if srcInfo.IsDir() { + return errExpectedFileButGotDirectory + } + + // open the destination file + file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) + if err != nil { + return err + } + defer file.Close() + + // write the file! + return WriteTemplate(file, context, srcFile) +} diff --git a/internal/unpack/unpack.go b/internal/unpack/unpack.go deleted file mode 100644 index 85014e3..0000000 --- a/internal/unpack/unpack.go +++ /dev/null @@ -1,65 +0,0 @@ -// Package unpack unpacks files and templates to a target directory -package unpack - -import ( - "bytes" - "errors" - "io/fs" -) - -var errExpectedFileButGotDirectory = errors.New("expected a file, but got a directory") -var errExpectedDirectoryButGotFile = errors.New("expected a directory, but got a file") - -// InstallResource installs the resource at src within fsys to dst. -// -// OnInstallFile is called for each source and destination file. -// OnInstallFile may be nil. -// -// See [InstallDir] or [InstallFile]. -func InstallResource(dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { - // open the srcFile - srcFile, err := fsys.Open(src) - if err != nil { - return err - } - defer srcFile.Close() - - // stat it! - srcInfo, err := srcFile.Stat() - if err != nil { - return err - } - - // call the hook (if any) - if onInstallFile != nil { - onInstallFile(dst, src) - } - - // this is a directory, so the cast is safe! - if srcInfo.IsDir() { - return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile) - } - - // this is a regular file! - return installFile(dst, srcInfo, srcFile) -} - -// UnpackTemplate unpacks the given file template and template. -// See [WriteTemplate] for possible errors. -func UnpackTemplate(context map[string]string, src fs.File) ([]byte, fs.FileMode, error) { - // stat the source file to install - srcStat, srcErr := src.Stat() - if srcErr != nil { - return nil, 0, srcErr - } - - // should not be a directory - if srcStat.IsDir() { - return nil, 0, errExpectedFileButGotDirectory - } - - // read all the bytes into a buffer - var buffer bytes.Buffer - err := WriteTemplate(&buffer, context, src) - return buffer.Bytes(), srcStat.Mode(), err -}