2017-02-14 05:12:02 +08:00
|
|
|
package buildah
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/containers/storage/pkg/ioutils"
|
2017-06-21 05:37:50 +08:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2017-02-18 02:58:34 +08:00
|
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
2017-02-14 05:12:02 +08:00
|
|
|
"github.com/opencontainers/runtime-tools/generate"
|
2017-11-29 03:22:47 +08:00
|
|
|
"github.com/opencontainers/selinux/go-selinux/label"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-10-10 03:05:56 +08:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
2017-02-14 05:12:02 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// DefaultWorkingDir is used if none was specified.
|
|
|
|
DefaultWorkingDir = "/"
|
2017-02-18 02:54:49 +08:00
|
|
|
// DefaultRuntime is the default command to use to run the container.
|
|
|
|
DefaultRuntime = "runc"
|
2017-02-14 05:12:02 +08:00
|
|
|
)
|
|
|
|
|
2017-04-05 06:36:55 +08:00
|
|
|
const (
|
2017-04-04 01:43:34 +08:00
|
|
|
// DefaultTerminal indicates that this Run invocation should be
|
|
|
|
// connected to a pseudoterminal if we're connected to a terminal.
|
2017-04-05 06:36:55 +08:00
|
|
|
DefaultTerminal = iota
|
2017-04-04 01:43:34 +08:00
|
|
|
// WithoutTerminal indicates that this Run invocation should NOT be
|
|
|
|
// connected to a pseudoterminal.
|
2017-04-05 06:36:55 +08:00
|
|
|
WithoutTerminal
|
2017-04-04 01:43:34 +08:00
|
|
|
// WithTerminal indicates that this Run invocation should be connected
|
|
|
|
// to a pseudoterminal.
|
2017-04-05 06:36:55 +08:00
|
|
|
WithTerminal
|
|
|
|
)
|
|
|
|
|
2017-02-14 05:12:02 +08:00
|
|
|
// RunOptions can be used to alter how a command is run in the container.
|
|
|
|
type RunOptions struct {
|
|
|
|
// Hostname is the hostname we set for the running container.
|
|
|
|
Hostname string
|
2017-02-18 02:54:49 +08:00
|
|
|
// Runtime is the name of the command to run. It should accept the same arguments that runc does.
|
|
|
|
Runtime string
|
|
|
|
// Args adds global arguments for the runtime.
|
|
|
|
Args []string
|
2017-02-18 02:58:34 +08:00
|
|
|
// Mounts are additional mount points which we want to provide.
|
|
|
|
Mounts []specs.Mount
|
2017-03-28 02:46:35 +08:00
|
|
|
// Env is additional environment variables to set.
|
|
|
|
Env []string
|
|
|
|
// User is the user as whom to run the command.
|
|
|
|
User string
|
|
|
|
// WorkingDir is an override for the working directory.
|
|
|
|
WorkingDir string
|
|
|
|
// Cmd is an override for the configured default command.
|
|
|
|
Cmd []string
|
|
|
|
// Entrypoint is an override for the configured entry point.
|
|
|
|
Entrypoint []string
|
|
|
|
// NetworkDisabled puts the container into its own network namespace.
|
|
|
|
NetworkDisabled bool
|
2017-04-05 06:36:55 +08:00
|
|
|
// Terminal provides a way to specify whether or not the command should
|
|
|
|
// be run with a pseudoterminal. By default (DefaultTerminal), a
|
|
|
|
// terminal is used if os.Stdout is connected to a terminal, but that
|
|
|
|
// decision can be overridden by specifying either WithTerminal or
|
|
|
|
// WithoutTerminal.
|
|
|
|
Terminal int
|
2017-02-14 05:12:02 +08:00
|
|
|
}
|
|
|
|
|
2017-06-21 05:37:50 +08:00
|
|
|
func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles, volumes []string) error {
|
2017-05-24 06:04:13 +08:00
|
|
|
// The passed-in mounts matter the most to us.
|
|
|
|
mounts := make([]specs.Mount, len(optionMounts))
|
|
|
|
copy(mounts, optionMounts)
|
|
|
|
haveMount := func(destination string) bool {
|
|
|
|
for _, mount := range mounts {
|
|
|
|
if mount.Destination == destination {
|
|
|
|
// Already have something to mount there.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// Add mounts from the generated list, unless they conflict.
|
|
|
|
for _, specMount := range spec.Mounts {
|
|
|
|
if haveMount(specMount.Destination) {
|
|
|
|
// Already have something to mount there, so skip this one.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
mounts = append(mounts, specMount)
|
|
|
|
}
|
|
|
|
// Add bind mounts for important files, unless they conflict.
|
|
|
|
for _, boundFile := range bindFiles {
|
|
|
|
if haveMount(boundFile) {
|
|
|
|
// Already have something to mount there, so skip this one.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
mounts = append(mounts, specs.Mount{
|
|
|
|
Source: boundFile,
|
|
|
|
Destination: boundFile,
|
|
|
|
Type: "bind",
|
|
|
|
Options: []string{"rbind", "ro"},
|
|
|
|
})
|
|
|
|
}
|
2017-11-08 06:44:24 +08:00
|
|
|
|
|
|
|
cdir, err := b.store.ContainerDirectory(b.ContainerID)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add secrets mounts
|
|
|
|
mountsFiles := []string{OverrideMountsFile, b.DefaultMountsFilePath}
|
|
|
|
for _, file := range mountsFiles {
|
|
|
|
secretMounts, err := secretMounts(file, b.MountLabel, cdir)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("error mounting secrets, skipping...")
|
|
|
|
}
|
|
|
|
for _, mount := range secretMounts {
|
|
|
|
if haveMount(mount.Destination) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
mounts = append(mounts, mount)
|
|
|
|
}
|
|
|
|
}
|
2017-06-21 05:37:50 +08:00
|
|
|
// Add temporary copies of the contents of volume locations at the
|
|
|
|
// volume locations, unless we already have something there.
|
2017-05-24 06:04:13 +08:00
|
|
|
for _, volume := range volumes {
|
|
|
|
if haveMount(volume) {
|
2017-06-21 05:37:50 +08:00
|
|
|
// Already mounting something there, no need to bother.
|
2017-05-24 06:04:13 +08:00
|
|
|
continue
|
|
|
|
}
|
2017-06-21 05:37:50 +08:00
|
|
|
subdir := digest.Canonical.FromString(volume).Hex()
|
|
|
|
volumePath := filepath.Join(cdir, "buildah-volumes", subdir)
|
|
|
|
// If we need to, initialize the volume path's initial contents.
|
|
|
|
if _, err = os.Stat(volumePath); os.IsNotExist(err) {
|
|
|
|
if err = os.MkdirAll(volumePath, 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, volume, b.ContainerID)
|
|
|
|
}
|
2017-11-29 03:22:47 +08:00
|
|
|
if err = label.Relabel(volumePath, b.MountLabel, false); err != nil {
|
|
|
|
return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, volume, b.ContainerID)
|
|
|
|
}
|
2017-06-21 05:37:50 +08:00
|
|
|
srcPath := filepath.Join(mountPoint, volume)
|
2017-10-10 03:05:56 +08:00
|
|
|
if err = copyFileWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) {
|
2017-06-21 05:37:50 +08:00
|
|
|
return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, volume, b.ContainerID, srcPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
// Add the bind mount.
|
2017-05-24 06:04:13 +08:00
|
|
|
mounts = append(mounts, specs.Mount{
|
2017-06-21 05:37:50 +08:00
|
|
|
Source: volumePath,
|
2017-05-24 06:04:13 +08:00
|
|
|
Destination: volume,
|
2017-06-21 05:37:50 +08:00
|
|
|
Type: "bind",
|
|
|
|
Options: []string{"bind"},
|
2017-05-24 06:04:13 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
// Set the list in the spec.
|
|
|
|
spec.Mounts = mounts
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-14 05:12:02 +08:00
|
|
|
// Run runs the specified command in the container's root filesystem.
|
|
|
|
func (b *Builder) Run(command []string, options RunOptions) error {
|
2017-03-28 02:46:35 +08:00
|
|
|
var user specs.User
|
2017-02-14 05:12:02 +08:00
|
|
|
path, err := ioutil.TempDir(os.TempDir(), Package)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Debugf("using %q to hold bundle data", path)
|
|
|
|
defer func() {
|
|
|
|
if err2 := os.RemoveAll(path); err2 != nil {
|
|
|
|
logrus.Errorf("error removing %q: %v", path, err2)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
g := generate.New()
|
|
|
|
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
for _, envSpec := range append(b.Env(), options.Env...) {
|
2017-02-14 05:12:02 +08:00
|
|
|
env := strings.SplitN(envSpec, "=", 2)
|
|
|
|
if len(env) > 1 {
|
|
|
|
g.AddProcessEnv(env[0], env[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(command) > 0 {
|
|
|
|
g.SetProcessArgs(command)
|
2017-06-23 23:53:51 +08:00
|
|
|
} else {
|
|
|
|
cmd := b.Cmd()
|
|
|
|
if len(options.Cmd) > 0 {
|
|
|
|
cmd = options.Cmd
|
|
|
|
}
|
|
|
|
ep := b.Entrypoint()
|
|
|
|
if len(options.Entrypoint) > 0 {
|
|
|
|
ep = options.Entrypoint
|
|
|
|
}
|
|
|
|
g.SetProcessArgs(append(ep, cmd...))
|
2017-02-14 05:12:02 +08:00
|
|
|
}
|
2017-03-28 02:46:35 +08:00
|
|
|
if options.WorkingDir != "" {
|
|
|
|
g.SetProcessCwd(options.WorkingDir)
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
} else if b.WorkDir() != "" {
|
|
|
|
g.SetProcessCwd(b.WorkDir())
|
2017-02-14 05:12:02 +08:00
|
|
|
}
|
|
|
|
if options.Hostname != "" {
|
|
|
|
g.SetHostname(options.Hostname)
|
2017-05-24 06:03:19 +08:00
|
|
|
} else if b.Hostname() != "" {
|
|
|
|
g.SetHostname(b.Hostname())
|
2017-02-14 05:12:02 +08:00
|
|
|
}
|
2017-10-20 05:47:15 +08:00
|
|
|
g.SetProcessSelinuxLabel(b.ProcessLabel)
|
|
|
|
g.SetLinuxMountLabel(b.MountLabel)
|
|
|
|
mountPoint, err := b.Mount(b.MountLabel)
|
2017-02-14 05:12:02 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err2 := b.Unmount(); err2 != nil {
|
|
|
|
logrus.Errorf("error unmounting container: %v", err2)
|
|
|
|
}
|
|
|
|
}()
|
2017-11-22 22:42:16 +08:00
|
|
|
for _, mp := range []string{
|
|
|
|
"/proc/kcore",
|
|
|
|
"/proc/latency_stats",
|
|
|
|
"/proc/timer_list",
|
|
|
|
"/proc/timer_stats",
|
|
|
|
"/proc/sched_debug",
|
|
|
|
"/proc/scsi",
|
|
|
|
"/sys/firmware",
|
|
|
|
} {
|
|
|
|
g.AddLinuxMaskedPaths(mp)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rp := range []string{
|
|
|
|
"/proc/asound",
|
|
|
|
"/proc/bus",
|
|
|
|
"/proc/fs",
|
|
|
|
"/proc/irq",
|
|
|
|
"/proc/sys",
|
|
|
|
"/proc/sysrq-trigger",
|
|
|
|
} {
|
|
|
|
g.AddLinuxReadonlyPaths(rp)
|
|
|
|
}
|
2017-02-14 05:12:02 +08:00
|
|
|
g.SetRootPath(mountPoint)
|
2017-04-05 06:36:55 +08:00
|
|
|
switch options.Terminal {
|
|
|
|
case DefaultTerminal:
|
2017-10-10 03:05:56 +08:00
|
|
|
g.SetProcessTerminal(terminal.IsTerminal(int(os.Stdout.Fd())))
|
2017-04-05 06:36:55 +08:00
|
|
|
case WithTerminal:
|
|
|
|
g.SetProcessTerminal(true)
|
|
|
|
case WithoutTerminal:
|
|
|
|
g.SetProcessTerminal(false)
|
|
|
|
}
|
2017-03-28 02:46:35 +08:00
|
|
|
if !options.NetworkDisabled {
|
2017-04-04 05:44:23 +08:00
|
|
|
if err = g.RemoveLinuxNamespace("network"); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error removing network namespace for run")
|
2017-04-04 05:44:23 +08:00
|
|
|
}
|
2017-03-28 02:46:35 +08:00
|
|
|
}
|
2017-04-05 05:31:02 +08:00
|
|
|
if options.User != "" {
|
|
|
|
user, err = getUser(mountPoint, options.User)
|
|
|
|
} else {
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
user, err = getUser(mountPoint, b.User())
|
2017-04-05 05:31:02 +08:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
g.SetProcessUID(user.UID)
|
|
|
|
g.SetProcessGID(user.GID)
|
2017-02-14 05:12:02 +08:00
|
|
|
spec := g.Spec()
|
|
|
|
if spec.Process.Cwd == "" {
|
|
|
|
spec.Process.Cwd = DefaultWorkingDir
|
|
|
|
}
|
2017-08-11 04:08:03 +08:00
|
|
|
if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring working directory %q exists", spec.Process.Cwd)
|
2017-03-24 01:47:07 +08:00
|
|
|
}
|
2017-05-24 06:04:13 +08:00
|
|
|
|
|
|
|
bindFiles := []string{"/etc/hosts", "/etc/resolv.conf"}
|
2017-06-21 05:37:50 +08:00
|
|
|
err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes())
|
2017-05-24 06:04:13 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error resolving mountpoints for container")
|
2017-02-18 02:58:34 +08:00
|
|
|
}
|
2017-02-14 05:12:02 +08:00
|
|
|
specbytes, err := json.Marshal(spec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = ioutils.AtomicWriteFile(filepath.Join(path, "config.json"), specbytes, 0600)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error storing runtime configuration")
|
2017-02-14 05:12:02 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("config = %v", string(specbytes))
|
2017-02-18 02:54:49 +08:00
|
|
|
runtime := options.Runtime
|
|
|
|
if runtime == "" {
|
|
|
|
runtime = DefaultRuntime
|
|
|
|
}
|
|
|
|
args := append(options.Args, "run", "-b", path, Package+"-"+b.ContainerID)
|
|
|
|
cmd := exec.Command(runtime, args...)
|
2017-02-14 05:12:02 +08:00
|
|
|
cmd.Dir = mountPoint
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("error running runc %v: %v", spec.Process.Args, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|