Implement initial 'wdcli backup_instance' command

This commit performs an initial implementation of the 'backup_instance'
command.
This commit is contained in:
Tom Wiesing 2022-09-05 14:43:50 +02:00
parent a64c02cd78
commit 2a14d93d3c
No known key found for this signature in database
17 changed files with 437 additions and 135 deletions

View file

@ -4,6 +4,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
)
var ErrCopySameFile = errors.New("src and dst must be different files")
@ -39,3 +40,70 @@ func CopyFile(dst, src string) error {
_, err = io.Copy(dstFile, srcFile)
return err
}
var ErrCopyNoDirectory = errors.New("dst is not a directory")
// CopyDirectory copies the directory src to dst recursively.
// The destination directory must exist, or an error is returned.
//
// onCopy, when not nil, is called for each file or directory being copied.
func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
// TODO: Allow copying in parallel? Maybe with a mutex?
// sanity checks
if src == dst {
return ErrCopySameFile
}
if !IsDirectory(dst) {
return ErrCopyNoDirectory
}
// call onCopy for this directory!
if onCopy != nil {
onCopy(dst, src)
}
// iterate over the entries or bail out
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
eDest := filepath.Join(dst, name)
eSrc := filepath.Join(src, name)
// it is not a directory => Use CopyFile
if !entry.IsDir() {
if onCopy != nil {
onCopy(eDest, eSrc)
}
// do the copy!
if err := CopyFile(eDest, eSrc); err != nil {
return err
}
continue
}
// find out the mode of the entry
eInfo, err := entry.Info()
if err != nil {
return err
}
// make the target directory
if err := os.Mkdir(eDest, eInfo.Mode()); err != nil {
return err
}
// do the copy!
if err := CopyDirectory(eDest, eSrc, onCopy); err != nil {
return err
}
}
return nil
}

View file

@ -76,6 +76,7 @@ func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...str
if io.StdinIsATerminal() {
compose = append(compose, "-ti")
}
compose = append(compose, service)
compose = append(compose, executable)
compose = append(compose, args...)
return ds.compose(io, compose...)

69
internal/targz/targz.go Normal file
View file

@ -0,0 +1,69 @@
package targz
import (
"archive/tar"
"compress/gzip"
"io"
"io/fs"
"os"
"path/filepath"
)
// Package packages the directory src into dst.
// onCopy, when not nil, is called for each file being copied into the archive.
func Package(dst, src string, onCopy func(src string)) error {
// create the target archive
archive, err := os.Create(dst)
if err != nil {
return err
}
defer archive.Close()
// create a gzip writer
zipHandle := gzip.NewWriter(archive)
defer zipHandle.Close()
// create a tar writer
tarHandle := tar.NewWriter(zipHandle)
defer tarHandle.Close()
// and walk through it!
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
if onCopy != nil {
onCopy(path)
}
if err != nil {
return err
}
// create a file info header!
tInfo, err := tar.FileInfoHeader(info, path)
if err != nil {
return err
}
tInfo.Name = filepath.ToSlash(path)
// write it!
if err := tarHandle.WriteHeader(tInfo); err != nil {
return err
}
// a directory => no more writing required
if info.IsDir() {
return nil
}
// open the file
handle, err := os.Open(path)
if err != nil {
return err
}
defer handle.Close()
// and copy it into the archive
_, err = io.Copy(tarHandle, handle)
return err
})
}