buildah/cmd/buildah/run.go

222 lines
7.4 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/containers/buildah"
internalParse "github.com/containers/buildah/internal/parse"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/util"
"github.com/containers/storage/pkg/lockfile"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
type runInputOptions struct {
addHistory bool
capAdd []string
capDrop []string
contextDir string
env []string
hostname string
isolation string
mounts []string
runtime string
runtimeFlag []string
noHosts bool
noPivot bool
terminal bool
volumes []string
workingDir string
*buildahcli.NameSpaceResults
}
func init() {
var (
runDescription = "\n Runs a specified command using the container's root filesystem as a root\n filesystem, using configuration settings inherited from the container's\n image or as specified using previous calls to the config command."
opts runInputOptions
)
namespaceResults := buildahcli.NameSpaceResults{}
runCommand := &cobra.Command{
Use: "run",
Short: "Run a command inside of the container",
Long: runDescription,
RunE: func(cmd *cobra.Command, args []string) error {
opts.NameSpaceResults = &namespaceResults
return runCmd(cmd, args, opts)
},
Example: `buildah run containerID -- ps -auxw
buildah run --terminal containerID /bin/bash
buildah run --volume /path/on/host:/path/in/container:ro,z containerID /bin/sh`,
}
runCommand.SetUsageTemplate(UsageTemplate())
flags := runCommand.Flags()
flags.SetInterspersed(false)
flags.BoolVar(&opts.addHistory, "add-history", false, "add an entry for this operation to the image's history. Use BUILDAH_HISTORY environment variable to override. (default false)")
flags.StringSliceVar(&opts.capAdd, "cap-add", []string{}, "add the specified capability (default [])")
flags.StringSliceVar(&opts.capDrop, "cap-drop", []string{}, "drop the specified capability (default [])")
flags.StringVar(&opts.contextDir, "contextdir", "", "context directory path")
flags.StringArrayVarP(&opts.env, "env", "e", []string{}, "add environment variable to be set temporarily when running command (default [])")
flags.StringVar(&opts.hostname, "hostname", "", "set the hostname inside of the container")
flags.StringVar(&opts.isolation, "isolation", "", "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.")
// Do not set a default runtime here, we'll do that later in the processing.
flags.StringVar(&opts.runtime, "runtime", util.Runtime(), "`path` to an alternate OCI runtime")
flags.StringSliceVar(&opts.runtimeFlag, "runtime-flag", []string{}, "add global flags for the container runtime")
flags.BoolVar(&opts.noHosts, "no-hosts", false, "do not override the /etc/hosts file within the container")
flags.BoolVar(&opts.noPivot, "no-pivot", false, "do not use pivot root to jail process inside rootfs")
flags.BoolVarP(&opts.terminal, "terminal", "t", false, "allocate a pseudo-TTY in the container")
flags.StringArrayVarP(&opts.volumes, "volume", "v", []string{}, "bind mount a host location into the container while running the command")
flags.StringArrayVar(&opts.mounts, "mount", []string{}, "attach a filesystem mount to the container (default [])")
flags.StringVar(&opts.workingDir, "workingdir", "", "temporarily set working directory for command (default to container's workingdir)")
userFlags := getUserFlags()
namespaceFlags := buildahcli.GetNameSpaceFlags(&namespaceResults)
flags.AddFlagSet(&userFlags)
flags.AddFlagSet(&namespaceFlags)
flags.SetNormalizeFunc(buildahcli.AliasFlags)
rootCmd.AddCommand(runCommand)
}
func runCmd(c *cobra.Command, args []string, iopts runInputOptions) error {
if len(args) == 0 {
return errors.New("container ID must be specified")
}
name := args[0]
args = Tail(args)
if len(args) > 0 && args[0] == "--" {
args = args[1:]
}
if len(args) == 0 {
return errors.New("command must be specified")
}
store, err := getStore(c)
if err != nil {
return err
}
builder, err := openBuilder(getContext(), store, name)
if err != nil {
return fmt.Errorf("reading build container %q: %w", name, err)
}
isolation, err := parse.IsolationOption(c.Flag("isolation").Value.String())
if err != nil {
return err
}
runtimeFlags := []string{}
for _, arg := range iopts.runtimeFlag {
runtimeFlags = append(runtimeFlags, "--"+arg)
}
noPivot := iopts.noPivot || (os.Getenv("BUILDAH_NOPIVOT") != "")
namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c)
if err != nil {
return err
}
if c.Flag("network").Changed && c.Flag("isolation").Changed {
if isolation == buildah.IsolationChroot {
if ns := namespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil {
if !ns.Host {
return fmt.Errorf("cannot set --network other than host with --isolation %s", c.Flag("isolation").Value.String())
}
}
}
}
options := buildah.RunOptions{
Hostname: iopts.hostname,
Runtime: iopts.runtime,
Args: runtimeFlags,
NoHosts: iopts.noHosts,
NoPivot: noPivot,
User: c.Flag("user").Value.String(),
Isolation: isolation,
NamespaceOptions: namespaceOptions,
ConfigureNetwork: networkPolicy,
ContextDir: iopts.contextDir,
CNIPluginPath: iopts.CNIPlugInPath,
CNIConfigDir: iopts.CNIConfigDir,
AddCapabilities: iopts.capAdd,
DropCapabilities: iopts.capDrop,
Env: iopts.env,
WorkingDir: iopts.workingDir,
}
if c.Flag("terminal").Changed {
if iopts.terminal {
options.Terminal = buildah.WithTerminal
} else {
options.Terminal = buildah.WithoutTerminal
}
}
systemContext, err := parse.SystemContextFromOptions(c)
if err != nil {
return fmt.Errorf("building system context: %w", err)
}
mounts, mountedImages, lockedTargets, err := internalParse.GetVolumes(systemContext, store, iopts.volumes, iopts.mounts, iopts.contextDir)
if err != nil {
return err
}
options.Mounts = mounts
// Run() will automatically clean them up.
options.ExternalImageMounts = mountedImages
options.CgroupManager = globalFlagResults.CgroupManager
runerr := builder.Run(args, options)
if runerr != nil {
logrus.Debugf("error running %v in container %q: %v", args, builder.Container, runerr)
}
if runerr == nil {
shell := "/bin/sh -c"
if len(builder.Shell()) > 0 {
shell = strings.Join(builder.Shell(), " ")
}
conditionallyAddHistory(builder, c, "%s %s", shell, strings.Join(args, " "))
return builder.Save()
}
// unlock if any locked files from this RUN statement
for _, path := range lockedTargets {
_, err := os.Stat(path)
if err != nil {
// Lockfile not found this might be a problem,
// since LockedTargets must contain list of all locked files
// don't break here since we need to unlock other files but
// log so user can take a look
logrus.Warnf("Lockfile %q was expected here, stat failed with %v", path, err)
continue
}
lockfile, err := lockfile.GetLockfile(path)
if err != nil {
// unable to get lockfile
// lets log error and continue
// unlocking other files
logrus.Warn(err)
continue
}
if lockfile.Locked() {
lockfile.Unlock()
} else {
logrus.Warnf("Lockfile %q was expected to be locked, this is unexpected", path)
continue
}
}
return runerr
}