mirror of
https://github.com/outbackdingo/debos.git
synced 2026-01-27 18:18:45 +00:00
If the overlay action fails to copy a file into the target filesystem
(e.g. destination path inside the filesystem doesn't exist), the current
behaviour is to panic, which causes a debos call inside the fakemachine
to panic, which isn't detected by the outer debos call.
This causes the execution of the inner debos call to stop (i.e. the
remaining recipe actions are no longer ran which is expected), but the
postexec commands still run and the overall exection is marked as
successful!
Rework the panics to instead bubble up errors so that any errors when
overlaying files causes the recipe to error out correctly rather than
this unexpected behaviour.
Before this commit is applied, the panic causes the debos call inside the
fakemachine to fail without being trapped by the outer debos call and the
outer debos call to still run the postprocess commands and exit with no
error as if the overlay action was successful:
$ debos tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml
2023/07/05 11:07:24 ==== Overlay file to a non-existent destination ====
2023/07/05 11:07:24 Overlaying tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml on /scratch/root/this/path/does/not/exist
2023/07/05 11:07:24 Failed to copy file tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml: open /scratch/root/this/path/does/not/3277940894: no such file or directory
2023/07/05 12:07:24 ==== run ====
2023/07/05 12:07:24 echo Test | Test
2023/07/05 12:07:24 ==== Recipe done ====
$ echo $?
0
With this commit applied, the execution of the outer debos call stops when
the overlay action fails and the error is correctly bubbled up to the user:
$ debos tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml
2023/07/05 11:08:15 ==== Overlay file to a non-existent destination ====
2023/07/05 11:08:15 Overlaying tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml on /scratch/root/this/path/does/not/exist
2023/07/05 11:08:15 Action `Overlay file to a non-existent destination` failed at stage Run, error: Failed to copy file tests/overlay-non-existent-destination/overlay-non-existent-destination.yaml: open /scratch/root/this/path/does/not/1742738134: no such file or directory
$ echo $?
1
Fixes: #401
Signed-off-by: Christopher Obbard <chris.obbard@collabora.com>
113 lines
2.2 KiB
Go
113 lines
2.2 KiB
Go
package debos
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func CleanPathAt(path, at string) string {
|
|
if filepath.IsAbs(path) {
|
|
return filepath.Clean(path)
|
|
}
|
|
|
|
return filepath.Join(at, path)
|
|
}
|
|
|
|
func CleanPath(path string) string {
|
|
cwd, _ := os.Getwd()
|
|
return CleanPathAt(path, cwd)
|
|
}
|
|
|
|
func CopyFile(src, dst string, mode os.FileMode) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
tmp, err := ioutil.TempFile(filepath.Dir(dst), "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(tmp, in)
|
|
if err != nil {
|
|
tmp.Close()
|
|
os.Remove(tmp.Name())
|
|
return err
|
|
}
|
|
if err = tmp.Close(); err != nil {
|
|
os.Remove(tmp.Name())
|
|
return err
|
|
}
|
|
if err = os.Chmod(tmp.Name(), mode); err != nil {
|
|
os.Remove(tmp.Name())
|
|
return err
|
|
}
|
|
|
|
if err = os.Rename(tmp.Name(), dst); err != nil {
|
|
os.Remove(tmp.Name())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CopyTree(sourcetree, desttree string) error {
|
|
walker := func(p string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
suffix, _ := filepath.Rel(sourcetree, p)
|
|
target := path.Join(desttree, suffix)
|
|
switch info.Mode() & os.ModeType {
|
|
case 0:
|
|
err := CopyFile(p, target, info.Mode())
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to copy file %s: %w", p, err)
|
|
}
|
|
case os.ModeDir:
|
|
os.Mkdir(target, info.Mode())
|
|
case os.ModeSymlink:
|
|
link, err := os.Readlink(p)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to read symlink %s: %w", suffix, err)
|
|
}
|
|
os.Symlink(link, target)
|
|
default:
|
|
return fmt.Errorf("File %s with mode %v not handled", p, info.Mode())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return filepath.Walk(sourcetree, walker)
|
|
}
|
|
|
|
func RealPath(path string) (string, error) {
|
|
p, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return filepath.Abs(p)
|
|
}
|
|
|
|
func RestrictedPath(prefix, dest string) (string, error) {
|
|
var err error
|
|
destination := path.Join(prefix, dest)
|
|
destination, err = filepath.Abs(destination)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !strings.HasPrefix(destination, prefix) {
|
|
return "", fmt.Errorf("The resulting path points outside of prefix '%s': '%s'\n", prefix, destination)
|
|
}
|
|
return destination, nil
|
|
}
|