Add 'environment' package

This commit adds a new environment package that manages all calls to the
underlying operating system.
This commit is contained in:
Tom Wiesing 2022-09-18 14:24:22 +02:00
parent 822c70cd69
commit f19619ef9f
No known key found for this signature in database
60 changed files with 539 additions and 308 deletions

View file

@ -0,0 +1,58 @@
package environment
import (
"io"
"io/fs"
"net"
"time"
"github.com/tkw1536/goprogram/stream"
)
// Environment represents an environment that a program can run it.
// It mostly mimics the interfaces of the [os] package.
type Environment interface {
isEnv()
GetEnv(name string) string
Stat(path string) (fs.FileInfo, error)
Lstat(path string) (fs.FileInfo, error)
Readlink(path string) (string, error)
Symlink(oldname, newname string) error
ReadDir(name string) ([]fs.DirEntry, error)
Open(path string) (fs.File, error)
Chtimes(name string, atime time.Time, mtime time.Time) error
SameFile(f1, f2 fs.FileInfo) bool
Create(path string, mode fs.FileMode) (WritableFile, error)
Mkdir(path string, mode fs.FileMode) error
MkdirAll(path string, mode fs.FileMode) error
Remove(path string) error
RemoveAll(path string) error
WalkDir(root string, fn fs.WalkDirFunc) error
Abs(path string) (string, error)
Listen(network, address string) (net.Listener, error)
Dial(network, address string) (net.Conn, error)
Executable() (string, error)
Exec(io stream.IOStream, workdir string, exe string, argv ...string) int
LookPathAbs(name string) (string, error)
}
type WritableFile interface {
fs.File
io.Writer
}
func init() {
var _ Environment = Native{}
}

View file

@ -0,0 +1,69 @@
package environment
import (
"bytes"
"io"
"io/fs"
"os"
"github.com/tkw1536/goprogram/stream"
)
// ExecCommandError is returned by Exec when a command could not be executed.
// This typically hints that the executable cannot be found, but may have other causes.
const ExecCommandError = 127
// DefaultFilePerm is the default mode to use for files
const DefaultFilePerm fs.FileMode = 0666
// DefaultDirPerm is the default mode to use for directories
const DefaultDirPerm fs.FileMode = os.ModeDir & fs.ModePerm
// IsExist checks if the provided error represents a 'does not exist' errror
func IsExist(err error) bool {
return os.IsExist(err)
}
// IsNotExist checks if the provided error represents a 'does exist' error
func IsNotExist(err error) bool {
return os.IsNotExist(err)
}
// WriteFile is like [os.WriteFile].
func WriteFile(env Environment, path string, data []byte, mode fs.FileMode) error {
handle, err := env.Create(path, mode)
if err != nil {
return err
}
defer handle.Close()
if _, err := handle.Write(data); err != nil {
return err
}
return nil
}
// ReadFile is like [os.ReadFile]
func ReadFile(env Environment, path string) ([]byte, error) {
// open the file!
file, err := env.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// copy everything into a buffer!
var buffer bytes.Buffer
if _, err := io.Copy(&buffer, file); err != nil {
return nil, err
}
// return the buffer contents!
return buffer.Bytes(), nil
}
// MustExec is like Exec, except that it returns true if the command exited successfully, and else false.
func MustExec(env Environment, io stream.IOStream, workdir string, exe string, argv ...string) bool {
return env.Exec(io, workdir, exe, argv...) == 0
}

View file

@ -1,5 +1,4 @@
// Package execx defines extensions to the "os/exec" package
package execx
package environment
import (
"os/exec"
@ -7,15 +6,11 @@ import (
"github.com/tkw1536/goprogram/stream"
)
// ExecCommandError is returned by Exec when a command could not be executed.
// This typically hints that the executable cannot be found, but may have other causes.
const ExecCommandError = 127
// Exec executes a system command with the specified input/output streams, working directory, and arguments.
//
// If the command executes, it's exit code will be returned.
// If the command can not be executed, returns [ExecCommandError].
func Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
func (Native) Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
// setup the command
cmd := exec.Command(exe, argv...)
cmd.Dir = workdir
@ -40,7 +35,10 @@ func Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
return 0
}
// MustExec is like Exec, except that it returns true if the command exited successfully, and else false.
func MustExec(io stream.IOStream, workdir string, exe string, argv ...string) bool {
return Exec(io, workdir, exe, argv...) == 0
func (n Native) LookPathAbs(file string) (string, error) {
path, err := exec.LookPath(file)
if err != nil {
return "", err
}
return n.Abs(path)
}

View file

@ -0,0 +1,80 @@
package environment
import (
"io/fs"
"os"
"path/filepath"
"time"
)
type Native struct{}
func (Native) isEnv() {}
func (Native) GetEnv(name string) string {
return os.Getenv(name)
}
func (Native) Stat(path string) (fs.FileInfo, error) {
return os.Stat(path)
}
func (Native) Lstat(path string) (fs.FileInfo, error) {
return os.Lstat(path)
}
func (Native) Readlink(path string) (string, error) {
return os.Readlink(path)
}
func (Native) Symlink(oldname, newname string) error {
return os.Symlink(oldname, newname)
}
func (Native) ReadDir(name string) ([]fs.DirEntry, error) {
return os.ReadDir(name)
}
func (Native) SameFile(f1, f2 fs.FileInfo) bool {
return os.SameFile(f1, f2)
}
func (Native) WalkDir(path string, f fs.WalkDirFunc) error {
return filepath.WalkDir(path, f)
}
func (Native) Executable() (string, error) {
return os.Executable() // TODO: not sure this works with the remote concepts
}
func (Native) Open(path string) (fs.File, error) {
return os.Open(path)
}
func (Native) Create(path string, mode fs.FileMode) (WritableFile, error) {
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
}
func (Native) Chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
func (Native) Mkdir(path string, mode fs.FileMode) error {
return os.Mkdir(path, mode)
}
func (Native) MkdirAll(path string, mode fs.FileMode) error {
return os.MkdirAll(path, mode)
}
func (Native) Remove(path string) error {
return os.Remove(path)
}
func (Native) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (Native) Abs(path string) (string, error) {
return filepath.Abs(path)
}

View file

@ -0,0 +1,11 @@
package environment
import "net"
func (Native) Listen(network, address string) (net.Listener, error) {
return net.Listen(network, address)
}
func (Native) Dial(network, address string) (net.Conn, error) {
return net.Dial(network, address)
}

View file

@ -1,15 +0,0 @@
package execx
import (
"os/exec"
"path/filepath"
)
// LookPathAbs is like [exec.LookPath], but always returns an absolute path
func LookPathAbs(file string) (string, error) {
path, err := exec.LookPath(file)
if err != nil {
return "", err
}
return filepath.Abs(path)
}

View file

@ -4,8 +4,9 @@ import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
)
var ErrCopySameFile = errors.New("src and dst must be different")
@ -14,13 +15,13 @@ var ErrCopySameFile = errors.New("src and dst must be different")
// When src points to a symbolic link, will copy the symbolic link.
//
// When dst and src are the same file, returns ErrCopySameFile.
func CopyFile(dst, src string) error {
if SameFile(src, dst) {
func CopyFile(env environment.Environment, dst, src string) error {
if SameFile(env, src, dst) {
return ErrCopySameFile
}
// open the source
srcFile, err := os.Open(src)
srcFile, err := env.Open(src)
if err != nil {
return err
}
@ -33,7 +34,7 @@ func CopyFile(dst, src string) error {
}
// open or create the destination
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, srcStat.Mode())
dstFile, err := env.Create(dst, srcStat.Mode())
if err != nil {
return err
}
@ -46,27 +47,27 @@ func CopyFile(dst, src string) error {
// CopyLink copies a link from src to dst.
// If dst already exists, it is deleted and then re-created.
func CopyLink(dst, src string) error {
func CopyLink(env environment.Environment, dst, src string) error {
// if they're the same file that is an error
if SameFile(dst, src) {
if SameFile(env, dst, src) {
return ErrCopySameFile
}
// read the link target
target, err := os.Readlink(src)
target, err := env.Readlink(src)
if err != nil {
return err
}
// delete it if it already exists
if Exists(dst) {
if err := os.Remove(dst); err != nil {
if Exists(env, dst) {
if err := env.Remove(dst); err != nil {
return err
}
}
// make the symbolic link!
return os.Symlink(target, dst)
return env.Symlink(target, dst)
}
var ErrDstFile = errors.New("dst is a file")
@ -77,16 +78,16 @@ var ErrDstFile = errors.New("dst is a file")
// When a directory already exists, additional files are not deleted.
//
// onCopy, when not nil, is called for each file or directory being copied.
func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
func CopyDirectory(env environment.Environment, dst, src string, onCopy func(dst, src string)) error {
// sanity checks
if SameFile(src, dst) {
if SameFile(env, src, dst) {
return ErrCopySameFile
}
if IsFile(dst) {
if IsFile(env, dst) {
return ErrDstFile
}
return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
return env.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
@ -111,19 +112,19 @@ func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
}
// if we have a symbolic link, copy the link!
if info.Mode()&os.ModeSymlink != 0 {
return CopyLink(dst, path)
if info.Mode()&fs.ModeSymlink != 0 {
return CopyLink(env, dst, path)
}
// if we got a file, we should copy it normally
if !d.IsDir() {
return CopyFile(dst, path)
return CopyFile(env, dst, path)
}
// create the directory, but ignore an error if the directory already exists.
// this is so that we can copy one tree into another tree.
err = os.Mkdir(dst, info.Mode())
if os.IsExist(err) && IsDirectory(dst) {
err = env.Mkdir(dst, info.Mode())
if environment.IsExist(err) && IsDirectory(env, dst) {
err = nil
}

View file

@ -1,17 +1,18 @@
package fsx
import (
"os"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
)
// SameFile checks if path1 and path2 refer to the same file.
// If both files exist, they are compared using [os.SameFile].
// If both files exist, they are compared using [env.SameFile].
// If both files do not exist, the paths are first compared syntactically and then via recursion on [filepath.Dir].
func SameFile(path1, path2 string) bool {
func SameFile(env environment.Environment, path1, path2 string) bool {
// initial attempt: check if directly
same, certain := couldBeSameFile(path1, path2)
same, certain := couldBeSameFile(env, path1, path2)
if certain {
return same
}
@ -28,28 +29,28 @@ func SameFile(path1, path2 string) bool {
// compare the base names!
{
same, _ := couldBeSameFile(d1, d2)
same, _ := couldBeSameFile(env, d1, d2)
return same
}
}
// couldBeSameFile checks if path1 might be the same as path2.
//
// If both files exist, compares using [os.SameFile].
// If both files exist, compares using [env.SameFile].
// Otherwise compares absolute paths using string comparison.
//
// same indicates if they might be the same file.
// authorative indiciates if the result is authorative.
func couldBeSameFile(path1, path2 string) (same, authorative bool) {
func couldBeSameFile(env environment.Environment, path1, path2 string) (same, authorative bool) {
{
// stat both files
info1, err1 := os.Stat(path1)
info2, err2 := os.Stat(path2)
info1, err1 := env.Stat(path1)
info2, err2 := env.Stat(path2)
// both files exist => check using os.SameFile
// both files exist => check using env.SameFile
// the result is always authorative
if err1 == nil && err2 == nil {
same = os.SameFile(info1, info2)
same = env.SameFile(info1, info2)
authorative = true
return
}
@ -60,15 +61,15 @@ func couldBeSameFile(path1, path2 string) (same, authorative bool) {
}
// only 1 file does not exist => they could be different
if os.IsNotExist(err1) != os.IsNotExist(err2) {
if environment.IsNotExist(err1) != environment.IsNotExist(err2) {
return
}
}
{
// resolve paths absolutely
rpath1, err1 := filepath.Abs(path1)
rpath2, err2 := filepath.Abs(path2)
rpath1, err1 := env.Abs(path1)
rpath2, err2 := env.Abs(path2)
// if either path could not be resolved absolutely
// fallback to just using clean!

View file

@ -1,20 +1,21 @@
package fsx
import (
"os"
"time"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
)
// Touch touches a file.
// It is similar to the unix 'touch' command.
//
// If the file does not exist exists, it is created using [os.Create].
// If the file does not exist exists, it is created using [env.Create].
// If the file does exist, it's access and modification times are updated to the current time.
func Touch(path string) error {
_, err := os.Stat(path)
func Touch(env environment.Environment, path string) error {
_, err := env.Stat(path)
switch {
case os.IsNotExist(err):
f, err := os.Create(path)
case environment.IsNotExist(err):
f, err := env.Create(path, environment.DefaultFilePerm)
if err != nil {
return err
}
@ -24,6 +25,6 @@ func Touch(path string) error {
return err
default:
now := time.Now().Local()
return os.Chtimes(path, now, now)
return env.Chtimes(path, now, now)
}
}

View file

@ -2,29 +2,31 @@
package fsx
import (
"os"
"io/fs"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
)
// Exists checks if the given path exists
func Exists(path string) bool {
_, err := os.Lstat(path)
func Exists(env environment.Environment, path string) bool {
_, err := env.Lstat(path)
return err == nil
}
// IsDirectory checks if the provided path exists and is a directory
func IsDirectory(path string) bool {
info, err := os.Stat(path)
func IsDirectory(env environment.Environment, path string) bool {
info, err := env.Stat(path)
return err == nil && info.Mode().IsDir()
}
// IsFile checks if the provided path exists and is a regular file
func IsFile(path string) bool {
info, err := os.Stat(path)
func IsFile(env environment.Environment, path string) bool {
info, err := env.Stat(path)
return err == nil && info.Mode().IsRegular()
}
// IsLink checks if the provided path exists and is a symlink
func IsLink(path string) bool {
info, err := os.Lstat(path)
return err == nil && info.Mode()&os.ModeSymlink != 0
func IsLink(env environment.Environment, path string) bool {
info, err := env.Lstat(path)
return err == nil && info.Mode()&fs.ModeSymlink != 0
}

View file

@ -4,12 +4,14 @@ package hostname
import (
"os"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/Showmax/go-fqdn"
)
// FQDN attempts to return the fully qualified domain name of the host system.
// If an error occurs, may fall back to the empty string.
func FQDN() string {
func FQDN(env environment.Environment) string {
// TODO: Pass this through!
// try the hostname function
{

View file

@ -4,11 +4,12 @@ import (
"reflect"
"strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/pkg/errors"
)
// Parse parses the provided value with the parser.
func Parse(name, value string, vField reflect.Value) error {
func Parse(env environment.Environment, name, value string, vField reflect.Value) error {
// use the validator
parser, ok := knownParsers[strings.ToLower(name)]
@ -17,7 +18,7 @@ func Parse(name, value string, vField reflect.Value) error {
}
// get the parsed value
checked, err := parser(value)
checked, err := parser(env, value)
if err != nil {
return errors.Wrapf(err, "parser %s returned error", name)
}
@ -53,8 +54,8 @@ var knownParsers map[string]Parser[any] = map[string]Parser[any]{
}
func asGenericParser[T any](parser Parser[T]) Parser[any] {
return func(s string) (value any, err error) {
value, err = parser(s)
return func(env environment.Environment, s string) (value any, err error) {
value, err = parser(env, s)
return
}
}

View file

@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/pkg/errors"
)
@ -17,19 +18,19 @@ import (
// Parsers can be found in this package as functions called Parse*.
// They are refered to by their name, e.g. ParseNonempty can be refered to by the name 'Nonempty'.
// See [Parse].
type Parser[T any] func(s string) (T, error)
type Parser[T any] func(env environment.Environment, s string) (T, error)
// ParseAbspath checks that s is an absolute path and returns it as-is
func ParseAbspath(s string) (string, error) {
if !fsx.IsDirectory(s) {
func ParseAbspath(env environment.Environment, s string) (string, error) {
if !fsx.IsDirectory(env, s) {
return "", errors.Errorf("%q does not exist or is not a directory", s)
}
return s, nil
}
// ParseFile checks that s is a valid file and returns it as-is
func ParseFile(s string) (string, error) {
if !fsx.IsFile(s) {
func ParseFile(env environment.Environment, s string) (string, error) {
if !fsx.IsFile(env, s) {
return "", errors.Errorf("%q does not exist or is not a regular file", s)
}
return s, nil
@ -38,7 +39,7 @@ func ParseFile(s string) (string, error) {
var errEmptyString = errors.New("value is empty")
// ParseNonEmpty checks that s is a non-empty string and returns it as-is
func ParseNonEmpty(s string) (string, error) {
func ParseNonEmpty(env environment.Environment, s string) (string, error) {
if s == "" {
return "", errEmptyString
}
@ -48,7 +49,7 @@ func ParseNonEmpty(s string) (string, error) {
var regexpDomain = regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseValidDomain checks that s is a valid domain and returns it in lowercase
func ParseValidDomain(s string) (string, error) {
func ParseValidDomain(env environment.Environment, s string) (string, error) {
if !regexpDomain.MatchString(s) {
return "", errors.Errorf("%q is not a valid domain", s)
}
@ -56,7 +57,7 @@ func ParseValidDomain(s string) (string, error) {
}
// ParseValidDomains checks that s is a comma-seperated list of valid domains and returns them in lower case
func ParseValidDomains(s string) ([]string, error) {
func ParseValidDomains(env environment.Environment, s string) ([]string, error) {
if len(s) == 0 {
return []string{}, nil
}
@ -70,13 +71,13 @@ func ParseValidDomains(s string) ([]string, error) {
}
// ParseNumber parses s as a decimal integer
func ParseNumber(s string) (int, error) {
func ParseNumber(env environment.Environment, s string) (int, error) {
value, err := strconv.ParseInt(s, 10, 64)
return int(value), err
}
// ParseHttpsURL parses a string into a url that starts with 'https://'
func ParseHttpsURL(s string) (*url.URL, error) {
func ParseHttpsURL(env environment.Environment, s string) (*url.URL, error) {
url, err := url.Parse(s)
if err != nil {
return nil, errors.Wrapf(err, "%q is not a valid URL", s)
@ -90,7 +91,7 @@ func ParseHttpsURL(s string) (*url.URL, error) {
var regexpEmail = regexp.MustCompile(`^([-a-zA-Z0-9]+)\@([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseEmail checks that s represents an email, and then returns it as is.
func ParseEmail(s string) (string, error) {
func ParseEmail(env environment.Environment, s string) (string, error) {
if s == "" { // no email provided
return "", nil
}
@ -103,7 +104,7 @@ func ParseEmail(s string) (string, error) {
var regexpSlug = regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseSlug parses s as a slug and returns it as is.
func ParseSlug(s string) (string, error) {
func ParseSlug(env environment.Environment, s string) (string, error) {
if !regexpSlug.MatchString(s) {
return "", errors.Errorf("%q is not a valid slug", s)
}

View file

@ -6,17 +6,18 @@ import (
"compress/gzip"
"io"
"io/fs"
"os"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
)
// 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) {
func Package(env environment.Environment, dst, src string, onCopy func(rel string, src string)) (count int64, err error) {
// create the target archive
archive, err := os.Create(dst)
archive, err := env.Create(dst, environment.DefaultFilePerm)
if err != nil {
return 0, err
}
@ -31,7 +32,7 @@ func Package(dst, src string, onCopy func(rel string, src string)) (count int64,
defer tarHandle.Close()
// and walk through it!
err = filepath.WalkDir(src, func(path string, entry fs.DirEntry, err error) error {
err = env.WalkDir(src, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
@ -74,7 +75,7 @@ func Package(dst, src string, onCopy func(rel string, src string)) (count int64,
}
// open the file
handle, err := os.Open(path)
handle, err := env.Open(path)
if err != nil {
return err
}

View file

@ -3,9 +3,9 @@ package unpack
import (
"io"
"io/fs"
"os"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/pkg/errors"
)
@ -16,9 +16,9 @@ var errExpectedDirectoryButGotFile = errors.New("expected a directory, but got a
//
// onInstallFile is called for each file or directory being installed.
//
// If the destination path does not exist, it is created using [os.MakeDirs]
// If the destination path does not exist, it is created using [environment.MakeDirs]
// The directory is installed recursively.
func InstallDir(dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
func InstallDir(env environment.Environment, dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
// open the source file
srcFile, err := fsys.Open(src)
if err != nil {
@ -43,14 +43,14 @@ func InstallDir(dst string, src string, fsys fs.FS, onInstallFile func(dst, src
// do the installation of the directory.
// the type cast should be safe.
return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile)
return installDir(env, 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 {
func installResource(env environment.Environment, dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
// open the srcFile
srcFile, err := fsys.Open(src)
if err != nil {
@ -71,19 +71,19 @@ func installResource(dst string, src string, fsys fs.FS, onInstallFile func(dst,
// this is a directory, so the cast is safe!
if srcInfo.IsDir() {
return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile)
return installDir(env, dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile)
}
// this is a regular file!
return installFile(dst, srcInfo, srcFile)
return installFile(env, dst, srcInfo, srcFile)
}
func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
func installDir(env environment.Environment, 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)
dstStat, dstErr := env.Stat(dst)
switch {
case os.IsNotExist(dstErr):
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
case environment.IsNotExist(dstErr):
if err := env.MkdirAll(dst, srcInfo.Mode()); err != nil {
return errors.Wrapf(err, "Error creating destination directory %s", dst)
}
case dstErr != nil:
@ -105,6 +105,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(
env,
filepath.Join(dst, entry.Name()),
filepath.Join(src, entry.Name()),
fsys,
@ -117,9 +118,9 @@ 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 {
func installFile(env environment.Environment, 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())
file, err := env.Create(dst, srcInfo.Mode())
if err != nil {
return err
}

View file

@ -6,9 +6,9 @@ import (
"fmt"
"io"
"io/fs"
"os"
"strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
@ -201,7 +201,7 @@ parseloop:
// 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 {
func InstallTemplate(env environment.Environment, dst string, context map[string]string, src string, fsys fs.FS) error {
// open the srcFile
srcFile, err := fsys.Open(src)
@ -222,7 +222,7 @@ func InstallTemplate(dst string, context map[string]string, src string, fsys fs.
}
// open the destination file
file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode())
file, err := env.Create(dst, srcInfo.Mode())
if err != nil {
return err
}