overlay/.tools/internal/steps/stepshelpers/stepshelpers.go

129 lines
4.1 KiB
Go
Raw Normal View History

// Copyright (C) 2024 Jared Allard
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package stepshelpers contains various functions used for implementing
// steps.
package stepshelpers
import (
"archive/tar"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
// CopyFileBytesToContainer copies a file into a container using the
// provided bytes as the source.
func CopyFileBytesToContainer(ctx context.Context, containerID string, src []byte, dst string) error {
tempFile, err := os.CreateTemp("", "copy-file-*."+filepath.Ext(dst))
if err != nil {
return fmt.Errorf("failed to create temporary file: %w", err)
}
defer os.Remove(tempFile.Name())
if _, err := tempFile.Write(src); err != nil {
return fmt.Errorf("failed to write to temporary file: %w", err)
}
if err := tempFile.Close(); err != nil {
return fmt.Errorf("failed to close temporary file: %w", err)
}
return CopyFileToContainer(ctx, containerID, tempFile.Name(), dst)
}
// CopyFileToContainer copies a file into a container.
func CopyFileToContainer(ctx context.Context, containerID, src, dst string) error {
cmd := exec.CommandContext(ctx, "docker", "cp", src, fmt.Sprintf("%s:%s", containerID, dst))
if b, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to copy file to container: %s", string(b))
}
return nil
}
// ReadFileInContainer reads a file from a container and returns the
// contents.
func ReadFileInContainer(ctx context.Context, containerID, path string) ([]byte, error) {
cmd := exec.CommandContext(ctx, "docker", "cp", fmt.Sprintf("%s:%s", containerID, path), "-")
b, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to read file in container: %w", err)
}
t := tar.NewReader(bytes.NewReader(b))
if _, err := t.Next(); err != nil {
return nil, fmt.Errorf("failed to read tar: %w", err)
}
b, err = io.ReadAll(t)
if err != nil {
return nil, fmt.Errorf("failed to read tar: %w", err)
}
return b, nil
}
// StreamFileFromContainer streams a file from a container to the
// provided writer. Because this function streams the file using the
// exec package, the caller must call the returned wait function to wait
// for the command to finish and clean up resources.
//
// The returned int64 is the size of the file being streamed.
func StreamFileFromContainer(ctx context.Context, containerID, path string) (io.Reader, int64, func() error, error) {
cmd := exec.CommandContext(ctx, "docker", "cp", fmt.Sprintf("%s:%s", containerID, path), "-")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to create stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return nil, 0, nil, fmt.Errorf("failed to start command: %w", err)
}
// Process the output as a tar stream.
t := tar.NewReader(stdout)
th, err := t.Next()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to read tar: %w", err)
}
return t, th.Size, cmd.Wait, nil
}
// RunCommandInContainer runs a command inside of a container.
func RunCommandInContainer(ctx context.Context, containerID string, origArgs ...string) error {
args := []string{"exec", containerID, "bash", "-eo", "pipefail"}
if len(origArgs) > 1 {
args = append(args, "-xc", strings.Join(origArgs, " "))
} else {
args = append(args, origArgs...)
}
cmd := exec.CommandContext(ctx, "docker", args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run command '%s': %w", strings.Join(origArgs, " "), err)
}
return nil
}