pkg/buildah/native_linux.go

Summary

Maintainability
F
6 days
Test Coverage
F
0%
//go:build linux
// +build linux

package buildah

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "io/ioutil"
    "math/rand"
    "os"
    "path/filepath"
    "strconv"
    "strings"
    "syscall"
    "time"

    "github.com/containerd/containerd/platforms"
    "github.com/containers/buildah"
    "github.com/containers/buildah/define"
    "github.com/containers/buildah/docker"
    "github.com/containers/buildah/imagebuildah"
    "github.com/containers/buildah/pkg/parse"
    "github.com/containers/common/libimage"
    "github.com/containers/image/v5/manifest"
    imgstor "github.com/containers/image/v5/storage"
    storageTransport "github.com/containers/image/v5/storage"
    "github.com/containers/image/v5/transports/alltransports"
    "github.com/containers/image/v5/types"
    imgtypes "github.com/containers/image/v5/types"
    "github.com/containers/storage"
    "github.com/containers/storage/drivers/overlay"
    "github.com/containers/storage/pkg/homedir"
    "github.com/containers/storage/pkg/reexec"
    "github.com/containers/storage/pkg/unshare"
    "github.com/hashicorp/go-multierror"
    "github.com/moby/buildkit/frontend/dockerfile/instructions"
    "github.com/opencontainers/runtime-spec/specs-go"
    "github.com/sirupsen/logrus"
    "gopkg.in/errgo.v2/fmt/errors"

    "github.com/werf/werf/pkg/buildah/thirdparty"
    "github.com/werf/werf/pkg/image"
)

const (
    MaxPullPushRetries = 3
    PullPushRetryDelay = 2 * time.Second
)

var DefaultShell = []string{"/bin/sh", "-c"}

func NativeProcessStartupHook() bool {
    if reexec.Init() {
        return true
    }

    if debug() {
        logrus.SetLevel(logrus.TraceLevel)
    }
    unshare.MaybeReexecUsingUserNamespace(false)

    return false
}

type NativeBuildah struct {
    Isolation               thirdparty.Isolation
    TmpDir                  string
    InstanceTmpDir          string
    ConfigTmpDir            string
    SignaturePolicyPath     string
    ContainersConfigPath    string
    RegistriesConfigPath    string
    RegistriesConfigDirPath string
    Insecure                bool

    Store storage.Store

    defaultCommonBuildOptions    define.CommonBuildOptions
    defaultSystemContext         imgtypes.SystemContext
    defaultPlatform              string
    defaultPlatformOverrideSpecs []struct{ OS, Arch, Variant string }
    runtimePlatform              string
}

func NewNativeBuildah(commonOpts CommonBuildahOpts, opts NativeModeOpts) (*NativeBuildah, error) {
    b := &NativeBuildah{
        Isolation: *commonOpts.Isolation,
        TmpDir:    commonOpts.TmpDir,
        Insecure:  commonOpts.Insecure,
    }

    if err := os.MkdirAll(b.TmpDir, os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to create dir %q: %w", b.TmpDir, err)
    }

    var err error
    b.InstanceTmpDir, err = ioutil.TempDir(b.TmpDir, "instance")
    if err != nil {
        return nil, fmt.Errorf("unable to create instance tmp dir: %w", err)
    }

    b.ConfigTmpDir = filepath.Join(b.InstanceTmpDir, "config")
    if err := os.MkdirAll(b.ConfigTmpDir, os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to create dir %q: %w", b.ConfigTmpDir, err)
    }

    b.SignaturePolicyPath = filepath.Join(b.ConfigTmpDir, "policy.json")
    if err := ioutil.WriteFile(b.SignaturePolicyPath, []byte(DefaultSignaturePolicy), os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to write file %q: %w", b.SignaturePolicyPath, err)
    }

    b.ContainersConfigPath = filepath.Join(b.ConfigTmpDir, "containers.conf")
    if err := ioutil.WriteFile(b.ContainersConfigPath, []byte(DefaultContainersConfig), os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to write file %q: %w", b.ContainersConfigPath, err)
    }

    if err := os.Setenv("CONTAINERS_CONF", b.ContainersConfigPath); err != nil {
        return nil, fmt.Errorf("unable to set env var CONTAINERS_CONF: %w", err)
    }

    b.RegistriesConfigPath = filepath.Join(b.ConfigTmpDir, "registries.conf")
    if err := ioutil.WriteFile(b.RegistriesConfigPath, []byte(DefaultRegistriesConfig), os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to write file %q: %w", b.RegistriesConfigPath, err)
    }

    b.RegistriesConfigDirPath = filepath.Join(b.ConfigTmpDir, "registries.conf.d")
    if err := os.MkdirAll(b.RegistriesConfigDirPath, os.ModePerm); err != nil {
        return nil, fmt.Errorf("unable to create dir %q: %w", b.RegistriesConfigDirPath, err)
    }

    storeOpts, err := NewNativeStoreOptions(unshare.GetRootlessUID(), *commonOpts.StorageDriver)
    if err != nil {
        return nil, fmt.Errorf("unable to initialize storage opts: %w", err)
    }

    b.Store, err = storage.GetStore(storage.StoreOptions(*storeOpts))
    if err != nil {
        return nil, fmt.Errorf("unable to get storage: %w", err)
    }

    b.defaultSystemContext = imgtypes.SystemContext{
        SignaturePolicyPath:               b.SignaturePolicyPath,
        SystemRegistriesConfPath:          b.RegistriesConfigPath,
        SystemRegistriesConfDirPath:       b.RegistriesConfigDirPath,
        OCIInsecureSkipTLSVerify:          b.Insecure,
        DockerInsecureSkipTLSVerify:       imgtypes.NewOptionalBool(b.Insecure),
        DockerDaemonInsecureSkipTLSVerify: b.Insecure,
    }

    b.runtimePlatform = platforms.Format(platforms.DefaultSpec())

    if opts.DefaultPlatform != "" {
        b.defaultPlatform = opts.DefaultPlatform

        os, arch, variant, err := parse.Platform(opts.DefaultPlatform)
        if err != nil {
            return nil, fmt.Errorf("unable to parse platform %q: %w", opts.DefaultPlatform, err)
        }

        b.defaultSystemContext.OSChoice = os
        b.defaultSystemContext.ArchitectureChoice = arch
        b.defaultSystemContext.VariantChoice = variant

        b.defaultPlatformOverrideSpecs = []struct{ OS, Arch, Variant string }{
            {os, arch, variant},
        }
    } else {
        b.defaultPlatform = b.runtimePlatform
    }

    var ulimit []string
    if ulmt := os.Getenv("WERF_BUILDAH_ULIMIT"); ulmt != "" {
        ulimit = strings.Split(ulmt, ",")
    } else {
        rlimits, err := currentRlimits()
        if err != nil {
            return nil, fmt.Errorf("error getting current rlimits: %w", err)
        }
        ulimit = rlimitsToBuildahUlimits(rlimits)
    }

    b.defaultCommonBuildOptions = define.CommonBuildOptions{
        ShmSize: DefaultShmSize,
        Ulimit:  ulimit,
    }

    imgstor.Transport.SetStore(b.Store)

    return b, nil
}

func (b *NativeBuildah) getSystemContext(targetPlatform string) (*imgtypes.SystemContext, error) {
    systemContext := new(imgtypes.SystemContext)
    *systemContext = b.defaultSystemContext

    if targetPlatform != "" {
        os, arch, variant, err := parse.Platform(targetPlatform)
        if err != nil {
            return nil, fmt.Errorf("unable to parse platform %q: %w", targetPlatform, err)
        }

        systemContext.OSChoice = os
        systemContext.ArchitectureChoice = arch
        systemContext.VariantChoice = variant
    }

    return systemContext, nil
}

func (b *NativeBuildah) getRuntime(systemContext *imgtypes.SystemContext) (*libimage.Runtime, error) {
    return libimage.RuntimeFromStore(b.Store, &libimage.RuntimeOptions{
        SystemContext: systemContext,
    })
}

func (b *NativeBuildah) GetRuntimePlatform() string {
    return b.runtimePlatform
}

func (b *NativeBuildah) GetDefaultPlatform() string {
    return b.defaultPlatform
}

// Inspect returns nil, nil if image not found.
func (b *NativeBuildah) Inspect(ctx context.Context, ref string) (*thirdparty.BuilderInfo, error) {
    builder, err := b.getBuilderFromImage(ctx, ref, CommonOpts{})
    if err != nil {
        return nil, fmt.Errorf("error doing inspect: %w", err)
    }
    if builder == nil {
        return nil, nil
    }

    buildInfo := thirdparty.BuilderInfo(buildah.GetBuildInfo(builder))

    return &buildInfo, nil
}

func (b *NativeBuildah) Tag(_ context.Context, ref, newRef string, opts TagOpts) error {
    image, err := b.getImage(ref, CommonOpts(opts))
    if err != nil {
        return err
    }

    if err := image.Tag(newRef); err != nil {
        return fmt.Errorf("error tagging image: %w", err)
    }

    return nil
}

func (b *NativeBuildah) Push(ctx context.Context, ref string, opts PushOpts) error {
    // NOTICE: targetPlatform specified for push causes buildah to fail for some unknown reason
    sysCtx, err := b.getSystemContext("")
    if err != nil {
        return err
    }

    pushOpts := buildah.PushOptions{
        Compression:         define.Gzip,
        SignaturePolicyPath: b.SignaturePolicyPath,
        ReportWriter:        opts.LogWriter,
        Store:               b.Store,
        SystemContext:       sysCtx,
        ManifestType:        manifest.DockerV2Schema2MediaType,
        MaxRetries:          MaxPullPushRetries,
        RetryDelay:          PullPushRetryDelay,
    }

    imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", ref))
    if err != nil {
        return fmt.Errorf("error parsing image ref from %q: %w", ref, err)
    }

    if _, _, err = buildah.Push(ctx, ref, imageRef, pushOpts); err != nil {
        return fmt.Errorf("error pushing image %q: %w", ref, err)
    }

    return nil
}

func (b *NativeBuildah) BuildFromDockerfile(ctx context.Context, dockerfile string, opts BuildFromDockerfileOpts) (string, error) {
    var targetPlatform string
    var targetPlatforms []struct{ OS, Arch, Variant string }
    if opts.TargetPlatform != "" {
        os, arch, variant, err := parse.Platform(opts.TargetPlatform)
        if err != nil {
            return "", fmt.Errorf("unable to parse target platform %q: %w", opts.TargetPlatform, err)
        }

        targetPlatform = opts.TargetPlatform
        targetPlatforms = []struct{ OS, Arch, Variant string }{
            {os, arch, variant},
        }
    } else {
        targetPlatform = b.defaultPlatform
        targetPlatforms = b.defaultPlatformOverrideSpecs
    }

    // FIXME(multiarch): Fix warning in the build log:
    // FIXME(multiarch):  [Warning] one or more build args were not consumed: [TARGETARCH TARGETOS TARGETPLATFORM]

    sysCtx, err := b.getSystemContext(targetPlatform)
    if err != nil {
        return "", err
    }

    buildOpts := define.BuildOptions{
        Isolation:               define.Isolation(b.Isolation),
        Args:                    opts.BuildArgs,
        SignaturePolicyPath:     b.SignaturePolicyPath,
        ReportWriter:            opts.LogWriter,
        OutputFormat:            buildah.Dockerv2ImageManifest,
        SystemContext:           sysCtx,
        ConfigureNetwork:        define.NetworkEnabled,
        CommonBuildOpts:         &b.defaultCommonBuildOptions,
        Target:                  opts.Target,
        Platforms:               targetPlatforms,
        MaxPullPushRetries:      MaxPullPushRetries,
        PullPushRetryDelay:      PullPushRetryDelay,
        Layers:                  true,
        RemoveIntermediateCtrs:  true,
        ForceRmIntermediateCtrs: false,
        NoCache:                 false,
        Labels:                  opts.Labels,
    }

    if targetPlatform != b.GetRuntimePlatform() {
        // Prevent local cache collisions in multiplatform build mode:
        //   allow local cache only for the current runtime platform.
        buildOpts.NoCache = true
    }

    errLog := &bytes.Buffer{}
    if opts.LogWriter != nil {
        buildOpts.Out = opts.LogWriter
        buildOpts.Err = io.MultiWriter(opts.LogWriter, errLog)
    } else {
        buildOpts.Err = errLog
    }

    buildOpts.ContextDirectory = opts.ContextDir

    imageId, _, err := imagebuildah.BuildDockerfiles(ctx, b.Store, buildOpts, dockerfile)
    if err != nil {
        return "", fmt.Errorf("unable to build Dockerfile %q:\n%s\n%w", dockerfile, errLog.String(), err)
    }

    return imageId, nil
}

func (b *NativeBuildah) Mount(ctx context.Context, container string, opts MountOpts) (string, error) {
    builder, err := b.openContainerBuilder(ctx, container)
    if err != nil {
        return "", fmt.Errorf("unable to open container %q builder: %w", container, err)
    }

    return builder.Mount("")
}

func (b *NativeBuildah) Umount(ctx context.Context, container string, opts UmountOpts) error {
    builder, err := b.openContainerBuilder(ctx, container)
    if err != nil {
        return fmt.Errorf("unable to open container %q builder: %w", container, err)
    }

    return builder.Unmount()
}

func (b *NativeBuildah) RunCommand(ctx context.Context, container string, command []string, opts RunCommandOpts) error {
    builder, err := b.openContainerBuilder(ctx, container)
    if err != nil {
        return fmt.Errorf("unable to open container %q builder: %w", container, err)
    }

    contextDir := generateContextDir(opts.ContextDir, opts.RunMounts)
    nsOpts, netPolicy := generateNamespaceOptionsAndNetworkPolicy(opts.NetworkType)
    globalMounts := generateGlobalMounts(opts.GlobalMounts)
    runMounts := generateRunMounts(opts.RunMounts)
    stdout, stderr, stderrBuf := generateStdoutStderr(opts.LogWriter)
    command = prependShellToCommand(opts.PrependShell, opts.Shell, command, builder)

    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return err
    }

    runOpts := buildah.RunOptions{
        Env:              opts.Envs,
        ContextDir:       contextDir,
        AddCapabilities:  opts.AddCapabilities,
        DropCapabilities: opts.DropCapabilities,
        Stdout:           stdout,
        Stderr:           stderr,
        NamespaceOptions: nsOpts,
        ConfigureNetwork: netPolicy,
        Isolation:        define.Isolation(b.Isolation),
        SystemContext:    sysCtx,
        WorkingDir:       opts.WorkingDir,
        User:             opts.User,
        Entrypoint:       []string{},
        Cmd:              []string{},
        Mounts:           globalMounts,
        RunMounts:        runMounts,
        // TODO(ilya-lesikov):
        Secrets: nil,
        // TODO(ilya-lesikov):
        SSHSources: nil,
    }

    if err := builder.Run(command, runOpts); err != nil {
        return fmt.Errorf("RunCommand failed:\n%s\n%w", stderrBuf.String(), err)
    }

    return nil
}

func (b *NativeBuildah) FromCommand(ctx context.Context, container, image string, opts FromCommandOpts) (string, error) {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return "", err
    }

    builder, err := buildah.NewBuilder(ctx, b.Store, buildah.BuilderOptions{
        FromImage:           image,
        Container:           container,
        SignaturePolicyPath: b.SignaturePolicyPath,
        ReportWriter:        opts.LogWriter,
        SystemContext:       sysCtx,
        Isolation:           define.Isolation(b.Isolation),
        ConfigureNetwork:    define.NetworkEnabled,
        CommonBuildOpts:     &b.defaultCommonBuildOptions,
        Format:              buildah.Dockerv2ImageManifest,
        MaxPullRetries:      MaxPullPushRetries,
        PullRetryDelay:      PullPushRetryDelay,
        Capabilities:        define.DefaultCapabilities,
    })
    if err != nil {
        return "", fmt.Errorf("unable to create builder: %w", err)
    }

    return builder.Container, builder.Save()
}

func (b *NativeBuildah) Pull(ctx context.Context, ref string, opts PullOpts) error {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return err
    }

    pullOpts := buildah.PullOptions{
        SignaturePolicyPath: b.SignaturePolicyPath,
        ReportWriter:        opts.LogWriter,
        Store:               b.Store,
        SystemContext:       sysCtx,
        MaxRetries:          MaxPullPushRetries,
        RetryDelay:          PullPushRetryDelay,
        PullPolicy:          define.PullIfNewer,
    }

    imageID, err := buildah.Pull(ctx, ref, pullOpts)
    if err != nil {
        return fmt.Errorf("error pulling image %q: %w", ref, err)
    }

    imageInspect, err := b.Inspect(ctx, imageID)
    if err != nil {
        return fmt.Errorf("unable to inspect pulled image %q: %w", imageID, err)
    }

    platformMismatch := false
    if sysCtx.OSChoice != "" && sysCtx.OSChoice != imageInspect.OCIv1.OS {
        platformMismatch = true
    }
    if sysCtx.ArchitectureChoice != "" && sysCtx.ArchitectureChoice != imageInspect.OCIv1.Architecture {
        platformMismatch = true
    }

    if platformMismatch {
        imagePlatform := fmt.Sprintf("%s/%s", imageInspect.OCIv1.OS, imageInspect.OCIv1.Architecture)
        if imageInspect.OCIv1.Variant != "" {
            imagePlatform = fmt.Sprintf("%s/%s", imagePlatform, imageInspect.OCIv1.Variant)
        }
        expectedPlatform := fmt.Sprintf("%s/%s", sysCtx.OSChoice, sysCtx.ArchitectureChoice)
        if sysCtx.VariantChoice != "" {
            expectedPlatform = fmt.Sprintf("%s/%s", expectedPlatform, sysCtx.VariantChoice)
        }
        return fmt.Errorf("image platform mismatch: image uses %s, expecting %s platform", imagePlatform, expectedPlatform)
    }

    return nil
}

func (b *NativeBuildah) Rm(ctx context.Context, ref string, opts RmOpts) error {
    builder, err := b.getBuilderFromContainer(ctx, ref)
    if err != nil {
        return fmt.Errorf("error getting builder: %w", err)
    }

    return builder.Delete()
}

func (b *NativeBuildah) Rmi(ctx context.Context, ref string, opts RmiOpts) error {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return err
    }

    runtime, err := b.getRuntime(sysCtx)
    if err != nil {
        return err
    }

    _, rmiErrors := runtime.RemoveImages(ctx, []string{ref}, &libimage.RemoveImagesOptions{
        Force: opts.Force,
        // Filters: []string{"readonly=false", "intermediate=false", "dangling=true"},
    })

    var multiErr *multierror.Error
    return multierror.Append(multiErr, rmiErrors...).ErrorOrNil()
}

func (b *NativeBuildah) Commit(ctx context.Context, container string, opts CommitOpts) (string, error) {
    builder, err := b.getBuilderFromContainer(ctx, container)
    if err != nil {
        return "", fmt.Errorf("error getting builder: %w", err)
    }

    var imageRef types.ImageReference
    if opts.Image != "" {
        normalizedImage, err := libimage.NormalizeName(opts.Image)
        if err != nil {
            return "", fmt.Errorf("normalizing target image name %q: %w", opts.Image, err)
        }
        imageRef, err = storageTransport.Transport.ParseStoreReference(b.Store, normalizedImage.String())
        if err != nil {
            return "", fmt.Errorf("parsing target image name %q: %w", opts.Image, err)
        }
    }

    builder.SetLabel(image.WerfBaseImageIDLabel, fmt.Sprintf("sha256:%s", builder.FromImageID))

    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return "", err
    }

    imgID, _, _, err := builder.Commit(ctx, imageRef, buildah.CommitOptions{
        PreferredManifestType: buildah.Dockerv2ImageManifest,
        SignaturePolicyPath:   b.SignaturePolicyPath,
        ReportWriter:          opts.LogWriter,
        SystemContext:         sysCtx,
        MaxRetries:            MaxPullPushRetries,
        RetryDelay:            PullPushRetryDelay,
    })
    if err != nil {
        return "", fmt.Errorf("error doing commit: %w", err)
    }

    return imgID, nil
}

func (b *NativeBuildah) Config(ctx context.Context, container string, opts ConfigOpts) error {
    builder, err := b.getBuilderFromContainer(ctx, container)
    if err != nil {
        return fmt.Errorf("error getting builder: %w", err)
    }

    for _, label := range opts.Labels {
        labelSlice := strings.SplitN(label, "=", 2)
        switch {
        case len(labelSlice) > 1:
            builder.SetLabel(labelSlice[0], labelSlice[1])
        case labelSlice[0] == "-":
            builder.ClearLabels()
        case strings.HasSuffix(labelSlice[0], "-"):
            builder.UnsetLabel(strings.TrimSuffix(labelSlice[0], "-"))
        default:
            builder.SetLabel(labelSlice[0], "")
        }
    }

    if opts.Maintainer != "" {
        builder.SetMaintainer(opts.Maintainer)
    }

    for name, value := range opts.Envs {
        builder.SetEnv(name, value)
    }

    for _, volume := range opts.Volumes {
        builder.AddVolume(volume)
    }

    for _, expose := range opts.Expose {
        builder.SetPort(expose)
    }

    if len(opts.Shell) > 0 {
        builder.SetShell(opts.Shell)
    }

    if len(opts.Cmd) > 0 {
        var cmd []string
        if opts.CmdPrependShell {
            if builder.Shell() != nil {
                cmd = builder.Shell()
            } else {
                cmd = DefaultShell
            }

            cmd = append(cmd, opts.Cmd...)
        } else {
            cmd = opts.Cmd
        }

        builder.SetCmd(cmd)
    }

    if len(opts.Entrypoint) > 0 {
        var entrypoint []string
        if opts.EntrypointPrependShell {
            if builder.Shell() != nil {
                entrypoint = builder.Shell()
            } else {
                entrypoint = DefaultShell
            }

            entrypoint = append(entrypoint, opts.Entrypoint...)
        } else {
            entrypoint = opts.Entrypoint
        }

        builder.SetEntrypoint(entrypoint)
    }

    if opts.User != "" {
        builder.SetUser(opts.User)
    }

    if opts.Workdir != "" {
        builder.SetWorkDir(opts.Workdir)
    }

    if opts.Healthcheck != nil {
        builder.SetHealthcheck((*docker.HealthConfig)(opts.Healthcheck))
    }

    if opts.StopSignal != "" {
        builder.SetStopSignal(opts.StopSignal)
    }

    if opts.OnBuild != "" {
        builder.SetOnBuild(opts.OnBuild)
    }

    return builder.Save()
}

func (b *NativeBuildah) Copy(ctx context.Context, container, contextDir string, src []string, dst string, opts CopyOpts) error {
    builder, err := b.getBuilderFromContainer(ctx, container)
    if err != nil {
        return fmt.Errorf("error getting builder: %w", err)
    }

    var absSrc []string
    for _, s := range src {
        absSrc = append(absSrc, filepath.Join(contextDir, s))
    }

    if err := builder.Add(dst, false, buildah.AddAndCopyOptions{
        Chown:             opts.Chown,
        Chmod:             opts.Chmod,
        PreserveOwnership: false,
        ContextDir:        contextDir,
        Excludes:          opts.Ignores,
    }, absSrc...); err != nil {
        return fmt.Errorf("error copying files to %q: %w", dst, err)
    }

    return nil
}

func (b *NativeBuildah) Add(ctx context.Context, container string, src []string, dst string, opts AddOpts) error {
    builder, err := b.getBuilderFromContainer(ctx, container)
    if err != nil {
        return fmt.Errorf("error getting builder: %w", err)
    }

    var expandedSrc []string
    for _, s := range src {
        if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") {
            expandedSrc = append(expandedSrc, s)
            continue
        }

        if opts.ContextDir == "" {
            return fmt.Errorf("context dir is required for adding local files")
        }

        expandedSrc = append(expandedSrc, filepath.Join(opts.ContextDir, s))
    }

    if err := builder.Add(dst, true, buildah.AddAndCopyOptions{
        Chmod:             opts.Chmod,
        Chown:             opts.Chown,
        PreserveOwnership: false,
        ContextDir:        opts.ContextDir,
        Excludes:          opts.Ignores,
    }, expandedSrc...); err != nil {
        return fmt.Errorf("error adding files to %q: %w", dst, err)
    }

    return nil
}

func (b *NativeBuildah) getImage(ref string, opts CommonOpts) (*libimage.Image, error) {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return nil, err
    }

    runtime, err := b.getRuntime(sysCtx)
    if err != nil {
        return nil, err
    }

    image, _, err := runtime.LookupImage(ref, &libimage.LookupImageOptions{
        ManifestList: true,
    })
    if err != nil {
        return nil, fmt.Errorf("error looking up image %q: %w", ref, err)
    }

    return image, nil
}

// getBuilderFromImage returns nil, nil if image not found.
func (b *NativeBuildah) getBuilderFromImage(ctx context.Context, imgName string, opts CommonOpts) (builder *buildah.Builder, err error) {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return nil, err
    }

    builder, err = buildah.ImportBuilderFromImage(ctx, b.Store, buildah.ImportFromImageOptions{
        Image:               imgName,
        SignaturePolicyPath: b.SignaturePolicyPath,
        SystemContext:       sysCtx,
    })
    switch {
    case err != nil && strings.HasSuffix(err.Error(), storage.ErrImageUnknown.Error()):
        return nil, nil
    case err != nil:
        return nil, fmt.Errorf("error getting builder from image %q: %w", imgName, err)
    case builder == nil:
        panic("error mocking up build configuration")
    }

    return builder, nil
}

func (b *NativeBuildah) getBuilderFromContainer(ctx context.Context, container string) (*buildah.Builder, error) {
    var builder *buildah.Builder
    var err error

    builder, err = buildah.OpenBuilder(b.Store, container)
    if os.IsNotExist(errors.Cause(err)) {
        builder, err = buildah.ImportBuilder(ctx, b.Store, buildah.ImportOptions{
            Container:           container,
            SignaturePolicyPath: b.SignaturePolicyPath,
        })
    }
    if err != nil {
        return nil, fmt.Errorf("unable to open builder: %w", err)
    }
    if builder == nil {
        return nil, fmt.Errorf("error finding build container")
    }

    return builder, nil
}

func (b *NativeBuildah) openContainerBuilder(ctx context.Context, container string) (*buildah.Builder, error) {
    builder, err := buildah.OpenBuilder(b.Store, container)
    switch {
    case os.IsNotExist(errors.Cause(err)):
        builder, err = buildah.ImportBuilder(ctx, b.Store, buildah.ImportOptions{
            Container:           container,
            SignaturePolicyPath: b.SignaturePolicyPath,
        })
        if err != nil {
            return nil, fmt.Errorf("unable to import builder for container %q: %w", container, err)
        }
    case err != nil:
        return nil, fmt.Errorf("unable to open builder for container %q: %w", container, err)
    }

    return builder, err
}

func (b *NativeBuildah) NewSessionTmpDir() (string, error) {
    sessionTmpDir, err := ioutil.TempDir(b.TmpDir, "session")
    if err != nil {
        return "", fmt.Errorf("unable to create session tmp dir: %w", err)
    }

    return sessionTmpDir, nil
}

func (b *NativeBuildah) Images(ctx context.Context, opts ImagesOptions) (image.ImagesList, error) {
    sysCtx, err := b.getSystemContext(opts.TargetPlatform)
    if err != nil {
        return nil, err
    }

    runtime, err := b.getRuntime(sysCtx)
    if err != nil {
        return nil, err
    }

    listOpts := &libimage.ListImagesOptions{}
    for _, filter := range opts.Filters {
        listOpts.Filters = append(listOpts.Filters, fmt.Sprintf("%s=%s", filter.First, filter.Second))
    }
    images, err := runtime.ListImages(ctx, opts.Names, listOpts)
    if err != nil {
        return nil, err
    }

    var res image.ImagesList
    for _, img := range images {
        repoTags, err := img.RepoTags()
        if err != nil {
            return nil, fmt.Errorf("unable to get image %s repo tags: %w", img.ID(), err)
        }
        repoDigests, err := img.RepoDigests()
        if err != nil {
            return nil, fmt.Errorf("unable to get image %s repo digests: %w", img.ID(), err)
        }
        res = append(res, image.Summary{RepoTags: repoTags, RepoDigests: repoDigests})
    }

    return res, nil
}

func (b *NativeBuildah) Containers(ctx context.Context, opts ContainersOptions) (image.ContainerList, error) {
    builders, err := buildah.OpenAllBuilders(b.Store)
    if err != nil {
        return nil, err
    }

    seenImages := make(map[string]string)
    imageNameForID := func(id string) string {
        if id == "" {
            return buildah.BaseImageFakeName
        }
        imageName, ok := seenImages[id]
        if ok {
            return imageName
        }
        img, err2 := b.Store.Image(id)
        if err2 == nil && len(img.Names) > 0 {
            seenImages[id] = img.Names[0]
        }
        return seenImages[id]
    }

    var res image.ContainerList
SelectContainers:
    for _, builder := range builders {
        imgID := builder.FromImageID
        imgName := imageNameForID(builder.FromImageID)

        for _, filter := range opts.Filters {
            if filter.ID != "" && !thirdparty.MatchesID(builder.ContainerID, filter.ID) {
                continue SelectContainers
            }
            if filter.Name != "" && !thirdparty.MatchesCtrName(builder.Container, filter.Name) {
                continue SelectContainers
            }
            if filter.Ancestor != "" && !thirdparty.MatchesAncestor(imgName, imgID, filter.Ancestor) {
                continue SelectContainers
            }
        }

        res = append(res, image.Container{
            ID:      builder.ContainerID,
            ImageID: builder.FromImageID,
            Names:   []string{builder.Container},
        })
    }

    return res, nil
}

func NewNativeStoreOptions(rootlessUID int, driver StorageDriver) (*thirdparty.StoreOptions, error) {
    var (
        runRoot string
        err     error
    )

    if rootlessUID == 0 {
        runRoot = "/run/containers/storage"
    } else {
        runRoot, err = homedir.GetRuntimeDir()
        if err != nil {
            return nil, fmt.Errorf("unable to get runtime dir: %w", err)
        }
    }

    home, err := homedir.GetDataHome()
    if err != nil {
        return nil, fmt.Errorf("unable to get HOME data dir: %w", err)
    }

    rootlessStoragePath := filepath.Join(home, "containers", "storage")

    var graphRoot string
    if rootlessUID == 0 {
        graphRoot = "/var/lib/containers/storage"
    } else {
        graphRoot = rootlessStoragePath
    }

    var graphDriverOptions []string
    if driver == StorageDriverOverlay {
        supportsNative, err := overlay.SupportsNativeOverlay(graphRoot, runRoot)
        if err != nil {
            return nil, fmt.Errorf("unable to check native overlayfs support: %w", err)
        }

        if !supportsNative {
            fuseOpts, err := GetFuseOverlayfsOptions()
            if err != nil {
                return nil, fmt.Errorf("unable to get fuse overlayfs options: %w", err)
            }

            graphDriverOptions = append(graphDriverOptions, fuseOpts...)
        }
    }

    return &thirdparty.StoreOptions{
        RunRoot:             runRoot,
        GraphRoot:           graphRoot,
        RootlessStoragePath: rootlessStoragePath,
        GraphDriverName:     string(driver),
        GraphDriverOptions:  graphDriverOptions,
    }, nil
}

func currentRlimits() (map[int]*syscall.Rlimit, error) {
    result := map[int]*syscall.Rlimit{
        syscall.RLIMIT_CORE:   {},
        syscall.RLIMIT_CPU:    {},
        syscall.RLIMIT_DATA:   {},
        syscall.RLIMIT_FSIZE:  {},
        syscall.RLIMIT_NOFILE: {},
        syscall.RLIMIT_STACK:  {},
    }

    for k, v := range result {
        if err := syscall.Getrlimit(k, v); err != nil {
            return nil, fmt.Errorf("error getting rlimit: %w", err)
        }
    }

    return result, nil
}

func rlimitsToBuildahUlimits(rlimits map[int]*syscall.Rlimit) []string {
    rlimitToBuildahUlimitFn := func(rlimitKey int, buildahKey string) string {
        rlimitUintToStrFn := func(val uint64) string {
            if int64(val) < 0 {
                return strconv.FormatInt(int64(val), 10)
            } else {
                return strconv.FormatUint(val, 10)
            }
        }

        cur := rlimitUintToStrFn(rlimits[rlimitKey].Cur)
        max := rlimitUintToStrFn(rlimits[rlimitKey].Max)
        return fmt.Sprintf("%s=%s:%s", buildahKey, cur, max)
    }

    return []string{
        rlimitToBuildahUlimitFn(syscall.RLIMIT_CORE, "core"),
        rlimitToBuildahUlimitFn(syscall.RLIMIT_CPU, "cpu"),
        rlimitToBuildahUlimitFn(syscall.RLIMIT_DATA, "data"),
        rlimitToBuildahUlimitFn(syscall.RLIMIT_FSIZE, "fsize"),
        rlimitToBuildahUlimitFn(syscall.RLIMIT_NOFILE, "nofile"),
        rlimitToBuildahUlimitFn(syscall.RLIMIT_STACK, "stack"),
    }
}

func generateNamespaceOptionsAndNetworkPolicy(network string) (define.NamespaceOptions, define.NetworkConfigurationPolicy) {
    var netPolicy define.NetworkConfigurationPolicy
    nsOpts := define.NamespaceOptions{}

    switch network {
    case "default", "":
        netPolicy = define.NetworkDefault
        nsOpts.AddOrReplace(define.NamespaceOption{
            Name: string(specs.NetworkNamespace),
        })
    case "host":
        netPolicy = define.NetworkEnabled
        nsOpts.AddOrReplace(define.NamespaceOption{
            Name: string(specs.NetworkNamespace),
            Host: true,
        })
    case "none":
        netPolicy = define.NetworkDisabled
        nsOpts.AddOrReplace(define.NamespaceOption{
            Name: string(specs.NetworkNamespace),
        })
    default:
        panic(fmt.Sprintf("unexpected network type: %v", network))
    }

    return nsOpts, netPolicy
}

func generateRunMounts(mounts []*instructions.Mount) []string {
    var runMounts []string

    for _, mount := range mounts {
        var options []string

        switch mount.Type {
        case instructions.MountTypeBind:
            options = append(
                options,
                fmt.Sprintf("type=%s", mount.Type),
                fmt.Sprintf("target=%s", mount.Target),
            )
            if mount.Source != "" {
                options = append(options, fmt.Sprintf("source=%s", mount.Source))
            }
            if mount.From != "" {
                options = append(options, fmt.Sprintf("from=%s", mount.From))
            }
            if mount.ReadOnly {
                options = append(options, "ro")
            } else {
                options = append(options, "rw")
            }
        case instructions.MountTypeCache:
            options = append(
                options,
                fmt.Sprintf("type=%s", mount.Type),
                fmt.Sprintf("target=%s", mount.Target),
            )
            if mount.CacheID != "" {
                options = append(options, fmt.Sprintf("id=%s", mount.CacheID))
            }
            if mount.ReadOnly {
                options = append(options, "ro")
            } else {
                options = append(options, "rw")
            }
            if mount.CacheSharing != "" {
                options = append(options, fmt.Sprintf("sharing=%s", mount.CacheSharing))
            }
            if mount.From != "" {
                options = append(options, fmt.Sprintf("from=%s", mount.From))
            }
            if mount.Source != "" {
                options = append(options, fmt.Sprintf("source=%s", mount.Source))
            }
            if mount.Mode != nil {
                options = append(options, fmt.Sprintf("mode=%d", *mount.Mode))
            }
            if mount.UID != nil {
                options = append(options, fmt.Sprintf("uid=%d", *mount.UID))
            }
            if mount.GID != nil {
                options = append(options, fmt.Sprintf("gid=%d", *mount.GID))
            }
        case instructions.MountTypeTmpfs:
            options = append(
                options,
                fmt.Sprintf("type=%s", mount.Type),
                fmt.Sprintf("target=%s", mount.Target),
            )
        case instructions.MountTypeSecret:
            options = append(
                options,
                fmt.Sprintf("type=%s", mount.Type),
            )
            if mount.CacheID != "" {
                options = append(options, fmt.Sprintf("id=%s", mount.CacheID))
            }
            if mount.Target != "" {
                options = append(options, fmt.Sprintf("target=%s", mount.Target))
            }
            if mount.Required {
                options = append(options, "required=true")
            }
            if mount.Mode != nil {
                options = append(options, fmt.Sprintf("mode=%d", *mount.Mode))
            }
            if mount.UID != nil {
                options = append(options, fmt.Sprintf("uid=%d", *mount.UID))
            }
            if mount.GID != nil {
                options = append(options, fmt.Sprintf("gid=%d", *mount.GID))
            }
        case instructions.MountTypeSSH:
            options = append(
                options,
                fmt.Sprintf("type=%s", mount.Type),
            )
            if mount.CacheID != "" {
                options = append(options, fmt.Sprintf("id=%s", mount.CacheID))
            }
            if mount.Target != "" {
                options = append(options, fmt.Sprintf("target=%s", mount.Target))
            }
            if mount.Required {
                options = append(options, "required=true")
            }
            if mount.Mode != nil {
                options = append(options, fmt.Sprintf("mode=%d", *mount.Mode))
            }
            if mount.UID != nil {
                options = append(options, fmt.Sprintf("uid=%d", *mount.UID))
            }
            if mount.GID != nil {
                options = append(options, fmt.Sprintf("gid=%d", *mount.GID))
            }
        default:
            panic(fmt.Sprintf("unexpected mount type %q", mount.Type))
        }

        runMounts = append(runMounts, strings.Join(options, ","))
    }

    return runMounts
}

func generateContextDir(rawContextDir string, runMounts []*instructions.Mount) string {
    usesBuildContext := false
    for _, mount := range runMounts {
        if mount.Type == instructions.MountTypeBind && mount.From == "" {
            usesBuildContext = true
            break
        }
    }

    var contextDir string
    if !usesBuildContext && rawContextDir == "" {
        // Buildah API requires ContextDir even if not actually used. In this case we will pass a dummy value.
        contextDir = strconv.Itoa(rand.Int())
    } else {
        contextDir = rawContextDir
    }

    return contextDir
}

func generateStdoutStderr(optionalLogWriter io.Writer) (stdout, stderr io.Writer, stderrBuf *bytes.Buffer) {
    stderrBuf = &bytes.Buffer{}
    if optionalLogWriter != nil {
        stdout = optionalLogWriter
        stderr = io.MultiWriter(optionalLogWriter, stderrBuf)
    } else {
        stderr = stderrBuf
    }

    return stdout, stderr, stderrBuf
}

func prependShellToCommand(prependShell bool, shell, command []string, builder *buildah.Builder) []string {
    if !prependShell {
        return command
    }

    if len(shell) > 0 {
        command = append(shell, command...)
    } else if len(builder.Shell()) > 0 {
        command = append(builder.Shell(), command...)
    } else {
        command = append(DefaultShell, command...)
    }

    return command
}

func generateGlobalMounts(rawGlobalMounts []*specs.Mount) []specs.Mount {
    var globalMounts []specs.Mount
    for _, mount := range rawGlobalMounts {
        globalMounts = append(globalMounts, *mount)
    }

    return globalMounts
}