Files
podman/utils/utils.go
Matt Heon 98876454cb Refactor volume import to support the remote client
As with `volume export`, this was coded up exclusively in cmd/
instead of in libpod. Move it into Libpod, add a REST endpoint,
add bindings, and now everything talks using the ContainerEngine
wiring.

Also similar to `volume export` this also makes things work much
better with volumes that require mounting - we can now guarantee
they're actually mounted, instead of just hoping.

Includes some refactoring of `volume export` as well, to simplify
its implementation and ensure both Import and Export work with
readers/writers, as opposed to just files.

Fixes #26409

Signed-off-by: Matt Heon <mheon@redhat.com>
2025-06-23 13:42:22 -04:00

133 lines
3.4 KiB
Go

package utils
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chrootarchive"
"github.com/sirupsen/logrus"
"github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor"
)
// ExecCmd executes a command with args and returns its output as a string along
// with an error, if any.
func ExecCmd(name string, args ...string) (string, error) {
cmd := exec.Command(name, args...)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("`%v %v` failed: %v %v (%v)", name, strings.Join(args, " "), stderr.String(), stdout.String(), err)
}
return stdout.String(), nil
}
// ExecCmdWithStdStreams execute a command with the specified standard streams.
func ExecCmdWithStdStreams(stdin io.Reader, stdout, stderr io.Writer, env []string, name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Env = env
err := cmd.Run()
if err != nil {
return fmt.Errorf("`%v %v` failed: %v", name, strings.Join(args, " "), err)
}
return nil
}
// TarToFilesystem creates a tarball from source and writes to an os.file
// provided
func TarToFilesystem(source string, tarball *os.File) error {
tb, err := Tar(source)
if err != nil {
return err
}
defer tb.Close()
_, err = io.Copy(tarball, tb)
if err != nil {
return err
}
logrus.Debugf("wrote tarball file %s", tarball.Name())
return nil
}
// Tar creates a tarball from source and returns a readcloser of it
func Tar(source string) (io.ReadCloser, error) {
logrus.Debugf("creating tarball of %s", source)
return archive.Tar(source, archive.Uncompressed)
}
// TarWithChroot creates a tarball from source and returns a readcloser of it
// while chrooted to the source.
func TarWithChroot(source string) (io.ReadCloser, error) {
logrus.Debugf("creating tarball of %s", source)
return chrootarchive.Tar(source, nil, source)
}
// GuardedRemoveAll functions much like os.RemoveAll but
// will not delete certain catastrophic paths.
func GuardedRemoveAll(path string) error {
if path == "" || path == "/" {
return fmt.Errorf("refusing to recursively delete `%s`", path)
}
return os.RemoveAll(path)
}
// RemoveFilesExcept removes all files in a directory except for the one specified
// by excludeFile and will not delete certain catastrophic paths.
func RemoveFilesExcept(path string, excludeFile string) error {
if path == "" || path == "/" {
return fmt.Errorf("refusing to recursively delete `%s`", path)
}
files, err := os.ReadDir(path)
if err != nil {
return err
}
for _, file := range files {
if file.Name() != excludeFile {
if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil {
return err
}
}
}
return nil
}
func ProgressBar(prefix string, size int64, onComplete string) (*mpb.Progress, *mpb.Bar) {
p := mpb.New(
mpb.WithWidth(80), // Do not go below 80, see bug #17718
mpb.WithRefreshRate(180*time.Millisecond),
)
bar := p.AddBar(size,
mpb.BarFillerClearOnComplete(),
mpb.PrependDecorators(
decor.OnComplete(decor.Name(prefix), onComplete),
),
mpb.AppendDecorators(
decor.OnComplete(decor.CountersKibiByte("%.1f / %.1f"), ""),
),
)
if size == 0 {
bar.SetTotal(0, true)
}
return p, bar
}