summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml1
-rw-r--r--Makefile4
-rw-r--r--cmd/podman/build.go2
-rw-r--r--cmd/podman/cliconfig/config.go14
-rw-r--r--cmd/podman/commands.go2
-rw-r--r--cmd/podman/common.go2
-rw-r--r--cmd/podman/container.go4
-rw-r--r--cmd/podman/errors_remote.go2
-rw-r--r--cmd/podman/generate.go1
-rw-r--r--cmd/podman/generate_systemd.go70
-rw-r--r--cmd/podman/init.go64
-rw-r--r--cmd/podman/libpodruntime/runtime.go6
-rw-r--r--cmd/podman/main.go22
-rw-r--r--cmd/podman/main_local.go27
-rw-r--r--cmd/podman/main_remote.go6
-rw-r--r--cmd/podman/port.go35
-rw-r--r--cmd/podman/search.go13
-rw-r--r--cmd/podman/shared/create.go5
-rw-r--r--cmd/podman/shared/workers.go5
-rw-r--r--cmd/podman/varlink/io.podman.varlink17
-rw-r--r--commands.md3
-rw-r--r--completions/bash/podman52
-rwxr-xr-xcontrib/cirrus/build_vm_images.sh20
-rwxr-xr-xcontrib/cirrus/integration_test.sh10
-rw-r--r--contrib/cirrus/lib.sh87
-rwxr-xr-xcontrib/cirrus/lib.sh.t81
-rw-r--r--contrib/cirrus/packer/centos_setup.sh7
-rw-r--r--contrib/cirrus/packer/fah_setup.sh4
-rw-r--r--contrib/cirrus/packer/fedora_setup.sh9
-rw-r--r--contrib/cirrus/packer/image-builder-image_base-setup.sh7
-rw-r--r--contrib/cirrus/packer/rhel_base-setup.sh4
-rw-r--r--contrib/cirrus/packer/rhel_setup.sh8
-rw-r--r--contrib/cirrus/packer/ubuntu_setup.sh8
-rwxr-xr-xcontrib/cirrus/rootless_test.sh6
-rwxr-xr-xcontrib/cirrus/setup_container_environment.sh6
-rwxr-xr-xcontrib/cirrus/setup_environment.sh7
-rwxr-xr-xcontrib/cirrus/success.sh5
-rwxr-xr-xcontrib/cirrus/system_test.sh6
-rwxr-xr-xcontrib/cirrus/unit_test.sh6
-rw-r--r--docs/podman-container.1.md1
-rw-r--r--docs/podman-create.1.md14
-rw-r--r--docs/podman-generate-systemd.1.md69
-rw-r--r--docs/podman-generate.1.md1
-rw-r--r--docs/podman-init.1.md41
-rw-r--r--docs/podman-run.1.md31
-rw-r--r--docs/podman.1.md1
-rw-r--r--install.md2
-rw-r--r--libpod/container_api.go2
-rw-r--r--libpod/container_internal.go18
-rw-r--r--libpod/healthcheck.go59
-rw-r--r--libpod/healthcheck_linux.go67
-rw-r--r--libpod/healthcheck_unsupported.go19
-rw-r--r--libpod/oci.go447
-rw-r--r--libpod/oci_linux.go447
-rw-r--r--libpod/oci_unsupported.go12
-rw-r--r--libpod/options.go21
-rw-r--r--libpod/runtime.go18
-rw-r--r--libpod/runtime_migrate.go5
-rw-r--r--pkg/adapter/containers.go82
-rw-r--r--pkg/adapter/containers_remote.go73
-rw-r--r--pkg/adapter/runtime_remote.go17
-rw-r--r--pkg/adapter/runtime_remote_supported.go1
-rw-r--r--pkg/adapter/sigproxy_linux.go (renamed from pkg/adapter/sigproxy.go)0
-rw-r--r--pkg/adapter/terminal.go81
-rw-r--r--pkg/adapter/terminal_linux.go91
-rw-r--r--pkg/spec/config_linux.go6
-rw-r--r--pkg/spec/createconfig.go7
-rw-r--r--pkg/systemdgen/systemdgen.go43
-rw-r--r--pkg/util/utils.go72
-rw-r--r--pkg/util/utils_supported.go60
-rw-r--r--pkg/util/utils_windows.go12
-rw-r--r--pkg/varlinkapi/containers.go15
-rw-r--r--pkg/varlinkapi/generate.go22
-rw-r--r--test/e2e/generate_systemd_test.go74
-rw-r--r--test/e2e/init_test.go129
-rw-r--r--utils/utils.go33
-rw-r--r--utils/utils_supported.go39
-rw-r--r--utils/utils_windows.go9
-rw-r--r--vendor.conf8
-rw-r--r--vendor/github.com/containers/buildah/buildah.go2
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/build.go9
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go (renamed from vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go)32
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go13
-rw-r--r--vendor/github.com/containers/buildah/pkg/cli/common.go4
-rw-r--r--vendor/github.com/containers/buildah/pkg/parse/parse.go53
-rw-r--r--vendor/github.com/containers/buildah/pkg/parse/parse_unix.go20
-rw-r--r--vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go7
-rw-r--r--vendor/github.com/containers/buildah/run.go2020
-rw-r--r--vendor/github.com/containers/buildah/run_linux.go2022
-rw-r--r--vendor/github.com/containers/buildah/run_unsupport.go11
-rw-r--r--vendor/github.com/containers/buildah/run_unsupported.go20
-rw-r--r--vendor/github.com/containers/buildah/util/util.go2
-rw-r--r--vendor/github.com/containers/storage/drivers/driver.go1
-rw-r--r--vendor/github.com/containers/storage/drivers/overlay/overlay.go26
-rw-r--r--vendor/github.com/containers/storage/drivers/vfs/driver.go7
-rw-r--r--vendor/github.com/containers/storage/layers.go1
-rw-r--r--vendor/github.com/openshift/imagebuilder/vendor.conf5
-rw-r--r--vendor/github.com/varlink/go/varlink/bridge_windows.go4
-rw-r--r--vendor/github.com/varlink/go/varlink/call.go19
-rw-r--r--vendor/github.com/varlink/go/varlink/service.go109
-rw-r--r--vendor/github.com/varlink/go/varlink/varlink_test.go26
101 files changed, 4085 insertions, 3115 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 392d7b72d..6ab8beda3 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -135,6 +135,7 @@ gating_task:
- '/usr/local/bin/entrypoint.sh clean podman-remote'
- '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp"'
- '/usr/local/bin/entrypoint.sh podman-remote-darwin'
+ - '/usr/local/bin/entrypoint.sh podman-remote-windows'
on_failure:
failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh'
diff --git a/Makefile b/Makefile
index 1990c2d11..0085537d0 100644
--- a/Makefile
+++ b/Makefile
@@ -120,6 +120,9 @@ podman-remote: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) ## Build with podman on
podman-remote-darwin: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) ## Build with podman on remote OSX environment
GOOS=darwin $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "remoteclient containers_image_openpgp exclude_graphdriver_devicemapper" -o bin/$@ $(PROJECT)/cmd/podman
+podman-remote-windows: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) ## Build with podman for a remote windows environment
+ GOOS=windows $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "remoteclient containers_image_openpgp exclude_graphdriver_devicemapper" -o bin/$@.exe $(PROJECT)/cmd/podman
+
local-cross: $(CROSS_BUILD_TARGETS) ## Cross local compilation
bin/podman.cross.%: .gopathok
@@ -185,6 +188,7 @@ localunit: test/goecho/goecho varlink_generate
--tags "$(BUILDTAGS)" \
--succinct
$(MAKE) -C contrib/cirrus/packer test
+ ./contrib/cirrus/lib.sh.t
ginkgo:
ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 test/e2e/.
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
index 647ff1e86..24be9bb46 100644
--- a/cmd/podman/build.go
+++ b/cmd/podman/build.go
@@ -267,7 +267,7 @@ func buildCmd(c *cliconfig.BuildValues) error {
MemorySwap: memorySwap,
ShmSize: c.ShmSize,
Ulimit: c.Ulimit,
- Volumes: c.Volume,
+ Volumes: c.Volumes,
}
options := imagebuildah.BuildOptions{
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index 77156f47a..b770aaca0 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -136,12 +136,18 @@ type ExportValues struct {
PodmanCommand
Output string
}
-
type GenerateKubeValues struct {
PodmanCommand
Service bool
}
+type GenerateSystemdValues struct {
+ PodmanCommand
+ Name bool
+ RestartPolicy string
+ StopTimeout int
+}
+
type HistoryValues struct {
PodmanCommand
Human bool
@@ -177,6 +183,12 @@ type InfoValues struct {
Format string
}
+type InitValues struct {
+ PodmanCommand
+ All bool
+ Latest bool
+}
+
type InspectValues struct {
PodmanCommand
TypeObject string
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go
index 4b0641d82..14451d944 100644
--- a/cmd/podman/commands.go
+++ b/cmd/podman/commands.go
@@ -17,7 +17,6 @@ func getMainCommands() []*cobra.Command {
_loginCommand,
_logoutCommand,
_mountCommand,
- _portCommand,
_refreshCommand,
_searchCommand,
_statsCommand,
@@ -45,7 +44,6 @@ func getContainerSubCommands() []*cobra.Command {
_commitCommand,
_execCommand,
_mountCommand,
- _portCommand,
_refreshCommand,
_restoreCommand,
_runlabelCommand,
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index b02aa5990..8aca08248 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -315,7 +315,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
)
createFlags.Bool(
"http-proxy", true,
- "Set proxy environment variables in container based on the host proxy vars",
+ "Set proxy environment variables in the container based on the host proxy vars",
)
createFlags.String(
"image-volume", cliconfig.DefaultImageVolume,
diff --git a/cmd/podman/container.go b/cmd/podman/container.go
index b3058bf12..bbf01d1f8 100644
--- a/cmd/podman/container.go
+++ b/cmd/podman/container.go
@@ -56,12 +56,14 @@ var (
_diffCommand,
_exportCommand,
_createCommand,
+ _initCommand,
_killCommand,
_listSubCommand,
_logsCommand,
_pauseCommand,
- _restartCommand,
+ _portCommand,
_pruneContainersCommand,
+ _restartCommand,
_runCommand,
_rmCommand,
_startCommand,
diff --git a/cmd/podman/errors_remote.go b/cmd/podman/errors_remote.go
index ab255ea56..1e276be10 100644
--- a/cmd/podman/errors_remote.go
+++ b/cmd/podman/errors_remote.go
@@ -33,6 +33,8 @@ func outputError(err error) {
ne = errors.New(e.Reason)
case *iopodman.VolumeNotFound:
ne = errors.New(e.Reason)
+ case *iopodman.InvalidState:
+ ne = errors.New(e.Reason)
case *iopodman.ErrorOccurred:
ne = errors.New(e.Reason)
default:
diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go
index a0637ecb2..98bfb00a1 100644
--- a/cmd/podman/generate.go
+++ b/cmd/podman/generate.go
@@ -18,6 +18,7 @@ var (
// Commands that are universally implemented
generateCommands = []*cobra.Command{
_containerKubeCommand,
+ _containerSystemdCommand,
}
)
diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go
new file mode 100644
index 000000000..b4779e512
--- /dev/null
+++ b/cmd/podman/generate_systemd.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/pkg/adapter"
+ "github.com/containers/libpod/pkg/systemdgen"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ containerSystemdCommand cliconfig.GenerateSystemdValues
+ containerSystemdDescription = `Command generates a systemd unit file for a Podman container
+ `
+ _containerSystemdCommand = &cobra.Command{
+ Use: "systemd [flags] CONTAINER | POD",
+ Short: "Generate a systemd unit file for a Podman container",
+ Long: containerSystemdDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ containerSystemdCommand.InputArgs = args
+ containerSystemdCommand.GlobalFlags = MainGlobalOpts
+ containerSystemdCommand.Remote = remoteclient
+ return generateSystemdCmd(&containerSystemdCommand)
+ },
+ Args: func(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || len(args) < 1 {
+ return errors.New("provide only one container name or ID")
+ }
+ return nil
+ },
+ Example: `podman generate kube ctrID
+`,
+ }
+)
+
+func init() {
+ containerSystemdCommand.Command = _containerSystemdCommand
+ containerSystemdCommand.SetHelpTemplate(HelpTemplate())
+ containerSystemdCommand.SetUsageTemplate(UsageTemplate())
+ flags := containerSystemdCommand.Flags()
+ flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container name instead of ID")
+ flags.IntVarP(&containerSystemdCommand.StopTimeout, "timeout", "t", -1, "stop timeout override")
+ flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy")
+}
+
+func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error {
+ runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ // User input stop timeout must be 0 or greater
+ if c.Flag("timeout").Changed && c.StopTimeout < 0 {
+ return errors.New("timeout value must be 0 or greater")
+ }
+ // Make sure the input restart policy is valid
+ if err := systemdgen.ValidateRestartPolicy(c.RestartPolicy); err != nil {
+ return err
+ }
+
+ unit, err := runtime.GenerateSystemd(c)
+ if err != nil {
+ return err
+ }
+ fmt.Println(unit)
+ return nil
+}
diff --git a/cmd/podman/init.go b/cmd/podman/init.go
new file mode 100644
index 000000000..68c80631d
--- /dev/null
+++ b/cmd/podman/init.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/pkg/adapter"
+ "github.com/opentracing/opentracing-go"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ initCommand cliconfig.InitValues
+ initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.`
+
+ _initCommand = &cobra.Command{
+ Use: "init [flags] CONTAINER [CONTAINER...]",
+ Short: "Initialize one or more containers",
+ Long: initDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ initCommand.InputArgs = args
+ initCommand.GlobalFlags = MainGlobalOpts
+ initCommand.Remote = remoteclient
+ return initCmd(&initCommand)
+ },
+ Args: func(cmd *cobra.Command, args []string) error {
+ return checkAllAndLatest(cmd, args, false)
+ },
+ Example: `podman init --latest
+ podman init 3c45ef19d893
+ podman init test1`,
+ }
+)
+
+func init() {
+ initCommand.Command = _initCommand
+ initCommand.SetHelpTemplate(HelpTemplate())
+ initCommand.SetUsageTemplate(UsageTemplate())
+ flags := initCommand.Flags()
+ flags.BoolVarP(&initCommand.All, "all", "a", false, "Initialize all containers")
+ flags.BoolVarP(&initCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ markFlagHiddenForRemoteClient("latest", flags)
+}
+
+// initCmd initializes a container
+func initCmd(c *cliconfig.InitValues) error {
+ if c.Bool("trace") {
+ span, _ := opentracing.StartSpanFromContext(Ctx, "initCmd")
+ defer span.Finish()
+ }
+
+ ctx := getContext()
+
+ runtime, err := adapter.GetRuntime(ctx, &c.PodmanCommand)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ ok, failures, err := runtime.InitContainers(ctx, c)
+ if err != nil {
+ return err
+ }
+ return printCmdResults(ok, failures)
+}
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go
index b03846bbc..b533dc056 100644
--- a/cmd/podman/libpodruntime/runtime.go
+++ b/cmd/podman/libpodruntime/runtime.go
@@ -78,8 +78,6 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool,
options = append(options, libpod.WithRenumber())
}
- options = append(options, libpod.WithContext(ctx))
-
// Only set this if the user changes storage config on the command line
if storageSet {
options = append(options, libpod.WithStorageConfig(storageOpts))
@@ -146,7 +144,7 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool,
options = append(options, libpod.WithDefaultInfraCommand(infraCommand))
}
if c.Flags().Changed("config") {
- return libpod.NewRuntimeFromConfig(c.GlobalFlags.Config, options...)
+ return libpod.NewRuntimeFromConfig(ctx, c.GlobalFlags.Config, options...)
}
- return libpod.NewRuntime(options...)
+ return libpod.NewRuntime(ctx, options...)
}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index a0f1cf401..787dd55c0 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -4,7 +4,6 @@ import (
"context"
"io"
"os"
- "syscall"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/libpod"
@@ -13,7 +12,6 @@ import (
"github.com/containers/libpod/version"
"github.com/containers/storage/pkg/reexec"
"github.com/opentracing/opentracing-go"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -41,12 +39,14 @@ var mainCommands = []*cobra.Command{
&_imagesCommand,
_importCommand,
_infoCommand,
+ _initCommand,
&_inspectCommand,
_killCommand,
_loadCommand,
_logsCommand,
_pauseCommand,
podCommand.Command,
+ _portCommand,
&_psCommand,
_pullCommand,
_pushCommand,
@@ -118,25 +118,13 @@ func before(cmd *cobra.Command, args []string) error {
}
logrus.SetLevel(level)
- rlimits := new(syscall.Rlimit)
- rlimits.Cur = 1048576
- rlimits.Max = 1048576
- if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
- if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
- return errors.Wrapf(err, "error getting rlimits")
- }
- rlimits.Cur = rlimits.Max
- if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
- return errors.Wrapf(err, "error setting new rlimits")
- }
+ if err := setRLimits(); err != nil {
+ return err
}
-
if rootless.IsRootless() {
logrus.Info("running as rootless")
}
-
- // Be sure we can create directories with 0755 mode.
- syscall.Umask(0022)
+ setUMask()
return profileOn(cmd)
}
diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go
index 5afd51e28..7452965a2 100644
--- a/cmd/podman/main_local.go
+++ b/cmd/podman/main_local.go
@@ -4,16 +4,17 @@ package main
import (
"context"
- "github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/libpodruntime"
- "github.com/containers/libpod/pkg/rootless"
"io/ioutil"
"log/syslog"
"os"
"runtime/pprof"
"strconv"
"strings"
+ "syscall"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/tracing"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
@@ -154,3 +155,23 @@ func setupRootless(cmd *cobra.Command, args []string) error {
}
return nil
}
+func setRLimits() error {
+ rlimits := new(syscall.Rlimit)
+ rlimits.Cur = 1048576
+ rlimits.Max = 1048576
+ if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
+ return errors.Wrapf(err, "error getting rlimits")
+ }
+ rlimits.Cur = rlimits.Max
+ if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
+ return errors.Wrapf(err, "error setting new rlimits")
+ }
+ }
+ return nil
+}
+
+func setUMask() {
+ // Be sure we can create directories with 0755 mode.
+ syscall.Umask(0022)
+}
diff --git a/cmd/podman/main_remote.go b/cmd/podman/main_remote.go
index 2a7d184cd..a3335050a 100644
--- a/cmd/podman/main_remote.go
+++ b/cmd/podman/main_remote.go
@@ -41,3 +41,9 @@ func setupRootless(cmd *cobra.Command, args []string) error {
}
return nil
}
+
+func setRLimits() error {
+ return nil
+}
+
+func setUMask() {}
diff --git a/cmd/podman/port.go b/cmd/podman/port.go
index 7a9f01fe6..1bd2d623e 100644
--- a/cmd/podman/port.go
+++ b/cmd/podman/port.go
@@ -6,8 +6,7 @@ import (
"strings"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/libpodruntime"
- "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -51,10 +50,7 @@ func portCmd(c *cliconfig.PortValues) error {
var (
userProto, containerName string
userPort int
- container *libpod.Container
- containers []*libpod.Container
)
-
args := c.InputArgs
if c.Latest && c.All {
@@ -66,9 +62,6 @@ func portCmd(c *cliconfig.PortValues) error {
if len(args) == 0 && !c.Latest && !c.All {
return errors.Errorf("you must supply a running container name or id")
}
- if !c.Latest && !c.All {
- containerName = args[0]
- }
port := ""
if len(args) > 1 && !c.Latest {
@@ -98,36 +91,14 @@ func portCmd(c *cliconfig.PortValues) error {
}
}
- runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
+ runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
- if !c.Latest && !c.All {
- container, err = runtime.LookupContainer(containerName)
- if err != nil {
- return errors.Wrapf(err, "unable to find container %s", containerName)
- }
- containers = append(containers, container)
- } else if c.Latest {
- container, err = runtime.GetLatestContainer()
- if err != nil {
- return errors.Wrapf(err, "unable to get last created container")
- }
- containers = append(containers, container)
- } else {
- containers, err = runtime.GetRunningContainers()
- if err != nil {
- return errors.Wrapf(err, "unable to get all containers")
- }
- }
-
+ containers, err := runtime.Port(c)
for _, con := range containers {
- if state, _ := con.State(); state != libpod.ContainerStateRunning {
- continue
- }
-
portmappings, err := con.PortMappings()
if err != nil {
return err
diff --git a/cmd/podman/search.go b/cmd/podman/search.go
index 13948aef0..b236f3055 100644
--- a/cmd/podman/search.go
+++ b/cmd/podman/search.go
@@ -118,16 +118,3 @@ func searchToGeneric(params []image.SearchResult) (genericParams []interface{})
}
return genericParams
}
-
-func genSearchOutputMap() map[string]string {
- io := image.SearchResult{}
- v := reflect.Indirect(reflect.ValueOf(io))
- values := make(map[string]string)
-
- for i := 0; i < v.NumField(); i++ {
- key := v.Type().Field(i).Name
- value := key
- values[key] = strings.ToUpper(splitCamelCase(value))
- }
- return values
-}
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index 1e185d21f..81566326b 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -711,11 +711,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
return config, nil
}
-type namespace interface {
- IsContainer() bool
- Container() string
-}
-
func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) {
runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod)
if err != nil {
diff --git a/cmd/podman/shared/workers.go b/cmd/podman/shared/workers.go
index 112af89cc..b6e3f10e7 100644
--- a/cmd/podman/shared/workers.go
+++ b/cmd/podman/shared/workers.go
@@ -110,9 +110,14 @@ func (p *Pool) newWorker(slot int) {
func DefaultPoolSize(name string) int {
numCpus := runtime.NumCPU()
switch name {
+ case "init":
+ fallthrough
case "kill":
+ fallthrough
case "pause":
+ fallthrough
case "rm":
+ fallthrough
case "unpause":
if numCpus <= 3 {
return numCpus * 3
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 309f9765a..ace81646c 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -641,6 +641,14 @@ method StartContainer(name: string) -> (container: string)
# ~~~
method StopContainer(name: string, timeout: int) -> (container: string)
+# InitContainer initializes the given container. It accepts a container name or
+# ID, and will initialize the container matching that ID if possible, and error
+# if not. Containers can only be initialized when they are in the Created or
+# Exited states. Initialization prepares a container to be started, but does not
+# start the container. It is intended to be used to debug a container's state
+# prior to starting it.
+method InitContainer(name: string) -> (container: string)
+
# RestartContainer will restart a running container given a container name or ID and timeout value. The timeout
# value is the time before a forcible stop is used to stop the container. If the container cannot be found by
# name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise, the ID of the
@@ -1210,6 +1218,8 @@ method GetLayersMapWithImageInfo() -> (layerMap: string)
# BuildImageHierarchyMap is for the development of Podman and should not be used.
method BuildImageHierarchyMap(name: string) -> (imageInfo: string)
+method GenerateSystemd(name: string, restart: string, timeout: int, useName: bool) -> (unit: string)
+
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (id: string, reason: string)
@@ -1225,7 +1235,7 @@ error PodNotFound (name: string, reason: string)
# VolumeNotFound means the volume could not be found by the name or ID in local storage.
error VolumeNotFound (id: string, reason: string)
-# PodContainerError means a container associated with a pod failed to preform an operation. It contains
+# PodContainerError means a container associated with a pod failed to perform an operation. It contains
# a container ID of the container that failed.
error PodContainerError (podname: string, errors: []PodContainerErrorData)
@@ -1233,6 +1243,9 @@ error PodContainerError (podname: string, errors: []PodContainerErrorData)
# the pod ID.
error NoContainersInPod (name: string)
+# InvalidState indicates that a container or pod was in an improper state for the requested operation
+error InvalidState (id: string, reason: string)
+
# ErrorOccurred is a generic error for an error that occurs during the execution. The actual error message
# is includes as part of the error's text.
error ErrorOccurred (reason: string)
@@ -1241,4 +1254,4 @@ error ErrorOccurred (reason: string)
error RuntimeError (reason: string)
# The Podman endpoint requires that you use a streaming connection.
-error WantsMoreRequired (reason: string)
+error WantsMoreRequired (reason: string) \ No newline at end of file
diff --git a/commands.md b/commands.md
index 1c05640f2..e6c211254 100644
--- a/commands.md
+++ b/commands.md
@@ -25,6 +25,8 @@ Command | Descr
[podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container |
[podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |
[podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods |
+[podman-generate-kube(1)](/docs/podman-generate-kube.1.md) | Generate Kubernetes YAML based on a container or Pod |
+[podman-generate-systemd(1)](/docs/podman-generate-systemd.1.md) | Generate a Systemd unit file for a container |
[podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |
[podman-image(1)](/docs/podman-image.1.md) | Manage Images |
[podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage |
@@ -34,6 +36,7 @@ Command | Descr
[podman-images(1)](/docs/podman-images.1.md) | List images in local storage | [![...](/docs/play.png)](https://podman.io/asciinema/podman/images/) | [Here](https://github.com/containers/Demos/blob/master/podman_cli/podman_images.sh)
[podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image |
[podman-info(1)](/docs/podman-info.1.md) | Display system information |
+[podman-init(1)](/docs/podman-init.1.md) | Initialize a container |
[podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image | [![...](/docs/play.png)](https://asciinema.org/a/133418)
[podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers |
[podman-load(1)](/docs/podman-load.1.md) | Load an image from a container image archive |
diff --git a/completions/bash/podman b/completions/bash/podman
index b5963f8b9..e3c0c1dbf 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -780,6 +780,10 @@ _podman_container_export() {
_podman_export
}
+_podman_container_init() {
+ _podman_init
+}
+
_podman_container_inspect() {
_podman_inspect
}
@@ -915,6 +919,7 @@ _podman_generate() {
"
subcommands="
kube
+ systemd
"
__podman_subcommands "$subcommands $aliases" && return
@@ -2223,6 +2228,27 @@ _podman_ps() {
_complete_ "$options_with_args" "$boolean_options"
}
+_podman_init() {
+ local boolean_options="
+ --all
+ -a
+ --help
+ -h
+ --latest
+ -l
+ "
+ local options_with_args="
+ "
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_containers_unpauseable
+ ;;
+ esac
+}
+
_podman_start() {
local options_with_args="
--detach-keys
@@ -2435,6 +2461,32 @@ _podman_generate_kube() {
esac
}
+_podman_generate_systemd() {
+ local options_with_args="
+ --restart-policy
+ -t
+ --timeout"
+
+ local boolean_options="
+ -h
+ --help
+ -n
+ --name
+ "
+
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "
+ $(__podman_containers --all)
+ " -- "$cur" ) )
+ __ltrim_colon_completions "$cur"
+ ;;
+ esac
+}
+
_podman_play_kube() {
local options_with_args="
--authfile
diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh
index 6b86aa4d4..fc8cbb404 100755
--- a/contrib/cirrus/build_vm_images.sh
+++ b/contrib/cirrus/build_vm_images.sh
@@ -3,25 +3,7 @@
set -e
source $(dirname $0)/lib.sh
-req_env_var "
-CNI_COMMIT $CNI_COMMIT
-CRIO_COMMIT $CRIO_COMMIT
-RUNC_COMMIT $RUNC_COMMIT
-PACKER_BUILDS $PACKER_BUILDS
-BUILT_IMAGE_SUFFIX $BUILT_IMAGE_SUFFIX
-CENTOS_BASE_IMAGE $CENTOS_BASE_IMAGE
-UBUNTU_BASE_IMAGE $UBUNTU_BASE_IMAGE
-FEDORA_BASE_IMAGE $FEDORA_BASE_IMAGE
-FAH_BASE_IMAGE $FAH_BASE_IMAGE
-RHEL_BASE_IMAGE $RHEL_BASE_IMAGE
-RHSM_COMMAND $RHSM_COMMAND
-SERVICE_ACCOUNT $SERVICE_ACCOUNT
-GCE_SSH_USERNAME $GCE_SSH_USERNAME
-GCP_PROJECT_ID $GCP_PROJECT_ID
-PACKER_VER $PACKER_VER
-SCRIPT_BASE $SCRIPT_BASE
-PACKER_BASE $PACKER_BASE
-"
+req_env_var CNI_COMMIT CRIO_COMMIT RUNC_COMMIT PACKER_BUILDS BUILT_IMAGE_SUFFIX CENTOS_BASE_IMAGE UBUNTU_BASE_IMAGE FEDORA_BASE_IMAGE FAH_BASE_IMAGE RHEL_BASE_IMAGE RHSM_COMMAND SERVICE_ACCOUNT GCE_SSH_USERNAME GCP_PROJECT_ID PACKER_VER SCRIPT_BASE PACKER_BASE
record_timestamp "cache-image build start"
diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh
index 95387ff49..71223803c 100755
--- a/contrib/cirrus/integration_test.sh
+++ b/contrib/cirrus/integration_test.sh
@@ -3,13 +3,7 @@
set -e
source $(dirname $0)/lib.sh
-req_env_var "
-GOSRC $GOSRC
-SCRIPT_BASE $SCRIPT_BASE
-OS_RELEASE_ID $OS_RELEASE_ID
-OS_RELEASE_VER $OS_RELEASE_VER
-CONTAINER_RUNTIME $CONTAINER_RUNTIME
-"
+req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME
exit_handler() {
set +ex
@@ -39,7 +33,7 @@ then
exit $?
elif [[ "$SPECIALMODE" == "rootless" ]]
then
- req_env_var "ROOTLESS_USER $ROOTLESS_USER"
+ req_env_var ROOTLESS_USER
set -x
ssh $ROOTLESS_USER@localhost \
-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no \
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index d663616b2..1c459ebc7 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -33,15 +33,27 @@ then
source "$HOME/$ENVLIB"
fi
-# Pass in a line delimited list of, space delimited name/value pairs
-# exit non-zero with helpful error message if any value is empty
+# Pass in a list of one or more envariable names; exit non-zero with
+# helpful error message if any value is empty
req_env_var() {
- echo "$1" | while read NAME VALUE
- do
- if [[ -n "$NAME" ]] && [[ -z "$VALUE" ]]
- then
- echo "Required env. var. \$$NAME is not set"
- exit 9
+ # Provide context. If invoked from function use its name; else script name
+ local caller=${FUNCNAME[1]}
+ if [[ -n "$caller" ]]; then
+ # Indicate that it's a function name
+ caller="$caller()"
+ else
+ # Not called from a function: use script name
+ caller=$(basename $0)
+ fi
+
+ # Usage check
+ [[ -n "$1" ]] || die 1 "FATAL: req_env_var: invoked without arguments"
+
+ # Each input arg is an envariable name, e.g. HOME PATH etc. Expand each.
+ # If any is empty, bail out and explain why.
+ for i; do
+ if [[ -z "${!i}" ]]; then
+ die 9 "FATAL: $caller requires \$$i to be non-empty"
fi
done
}
@@ -97,20 +109,14 @@ PACKER_BUILDS $PACKER_BUILDS
# Unset environment variables not needed for testing purposes
clean_env() {
- req_env_var "
- UNSET_ENV_VARS $UNSET_ENV_VARS
- "
+ req_env_var UNSET_ENV_VARS
echo "Unsetting $(echo $UNSET_ENV_VARS | wc -w) environment variables"
unset -v UNSET_ENV_VARS $UNSET_ENV_VARS || true # don't fail on read-only
}
die() {
- req_env_var "
- 1 $1
- 2 $2
- "
- echo "$2"
- exit $1
+ echo "${2:-FATAL ERROR (but no message given!) in ${FUNCNAME[1]}()}"
+ exit ${1:-1}
}
# Return a GCE image-name compatible string representation of distribution name
@@ -135,10 +141,8 @@ stub() {
}
ircmsg() {
- req_env_var "
- CIRRUS_TASK_ID $CIRRUS_TASK_ID
- @ $@
- "
+ req_env_var CIRRUS_TASK_ID
+ [[ -n "$*" ]] || die 9 "ircmsg() invoked without args"
# Sometimes setup_environment.sh didn't run
SCRIPT="$(dirname $0)/podbot.py"
NICK="podbot_$CIRRUS_TASK_ID"
@@ -151,7 +155,7 @@ ircmsg() {
record_timestamp() {
set +x # sometimes it's turned on
- req_env_var "TIMESTAMPS_FILEPATH $TIMESTAMPS_FILEPATH"
+ req_env_var TIMESTAMPS_FILEPATH
echo "." # cirrus webui strips blank-lines
STAMPMSG="The $1 time at the tone will be:"
echo -e "$STAMPMSG\t$(date --iso-8601=seconds)" | \
@@ -160,11 +164,7 @@ record_timestamp() {
}
setup_rootless() {
- req_env_var "
- ROOTLESS_USER $ROOTLESS_USER
- GOSRC $GOSRC
- ENVLIB $ENVLIB
- "
+ req_env_var ROOTLESS_USER GOSRC ENVLIB
if passwd --status $ROOTLESS_USER
then
@@ -220,7 +220,7 @@ setup_rootless() {
# Helper/wrapper script to only show stderr/stdout on non-zero exit
install_ooe() {
- req_env_var "SCRIPT_BASE $SCRIPT_BASE"
+ req_env_var SCRIPT_BASE
echo "Installing script to mask stdout/stderr unless non-zero exit."
sudo install -D -m 755 "/tmp/libpod/$SCRIPT_BASE/ooe.sh" /usr/local/bin/ooe.sh
}
@@ -241,10 +241,7 @@ EOF
install_cni_plugins() {
echo "Installing CNI Plugins from commit $CNI_COMMIT"
- req_env_var "
- GOPATH $GOPATH
- CNI_COMMIT $CNI_COMMIT
- "
+ req_env_var GOPATH CNI_COMMIT
DEST="$GOPATH/src/github.com/containernetworking/plugins"
rm -rf "$DEST"
ooe.sh git clone "https://github.com/containernetworking/plugins.git" "$DEST"
@@ -272,11 +269,7 @@ install_runc(){
OS_RELEASE_ID=$(os_release_id)
echo "Installing RunC from commit $RUNC_COMMIT"
echo "Platform is $OS_RELEASE_ID"
- req_env_var "
- GOPATH $GOPATH
- RUNC_COMMIT $RUNC_COMMIT
- OS_RELEASE_ID $OS_RELEASE_ID
- "
+ req_env_var GOPATH RUNC_COMMIT OS_RELEASE_ID
if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then
echo "Running make install.libseccomp.sudo for ubuntu"
if ! [[ -d "/tmp/libpod" ]]
@@ -295,7 +288,7 @@ install_runc(){
install_buildah() {
echo "Installing buildah from latest upstream master"
- req_env_var "GOPATH $GOPATH"
+ req_env_var GOPATH
DEST="$GOPATH/src/github.com/containers/buildah"
rm -rf "$DEST"
ooe.sh git clone https://github.com/containers/buildah "$DEST"
@@ -307,10 +300,7 @@ install_buildah() {
# Requires $GOPATH and $CRIO_COMMIT to be set
install_conmon(){
echo "Installing conmon from commit $CRIO_COMMIT"
- req_env_var "
- GOPATH $GOPATH
- CRIO_COMMIT $CRIO_COMMIT
- "
+ req_env_var GOPATH CRIO_COMMIT
DEST="$GOPATH/src/github.com/kubernetes-sigs/cri-o.git"
rm -rf "$DEST"
ooe.sh git clone https://github.com/kubernetes-sigs/cri-o.git "$DEST"
@@ -327,9 +317,7 @@ install_criu(){
echo "Installing CRIU"
echo "Installing CRIU from commit $CRIU_COMMIT"
echo "Platform is $OS_RELEASE_ID"
- req_env_var "
- CRIU_COMMIT $CRIU_COMMIT
- "
+ req_env_var CRIU_COMMIT
if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then
ooe.sh sudo -E add-apt-repository -y ppa:criu/ppa
@@ -418,10 +406,7 @@ ubuntu_finalize(){
rhel_exit_handler() {
set +ex
- req_env_var "
- GOPATH $GOPATH
- RHSMCMD $RHSMCMD
- "
+ req_env_var GOPATH RHSMCMD
cd /
sudo rm -rf "$RHSMCMD"
sudo rm -rf "$GOPATH"
@@ -431,9 +416,7 @@ rhel_exit_handler() {
}
rhsm_enable() {
- req_env_var "
- RHSM_COMMAND $RHSM_COMMAND
- "
+ req_env_var RHSM_COMMAND
export GOPATH="$(mktemp -d)"
export RHSMCMD="$(mktemp)"
trap "rhel_exit_handler" EXIT
diff --git a/contrib/cirrus/lib.sh.t b/contrib/cirrus/lib.sh.t
new file mode 100755
index 000000000..ce51f8ad2
--- /dev/null
+++ b/contrib/cirrus/lib.sh.t
@@ -0,0 +1,81 @@
+#!/bin/bash
+#
+# Unit tests for some functions in lib.sh
+#
+source $(dirname $0)/lib.sh
+
+# Iterator and return code; updated in test functions
+testnum=0
+rc=0
+
+function check_result {
+ testnum=$(expr $testnum + 1)
+ if [ "$1" = "$2" ]; then
+ echo "ok $testnum $3 = $1"
+ else
+ echo "not ok $testnum $3"
+ echo "# expected: $2"
+ echo "# actual: $1"
+ rc=1
+ fi
+}
+
+###############################################################################
+# tests for die()
+
+function test_die() {
+ local input_status=$1
+ local input_msg=$2
+ local expected_status=$3
+ local expected_msg=$4
+
+ local msg
+ msg=$(die $input_status "$input_msg")
+ local status=$?
+
+ check_result "$msg" "$expected_msg" "die $input_status $input_msg"
+}
+
+test_die 1 "a message" 1 "a message"
+test_die 2 "" 2 "FATAL ERROR (but no message given!) in test_die()"
+test_die '' '' 1 "FATAL ERROR (but no message given!) in test_die()"
+
+###############################################################################
+# tests for req_env_var()
+
+function test_rev() {
+ local input_args=$1
+ local expected_status=$2
+ local expected_msg=$3
+
+ # bash gotcha: doing 'local msg=...' on one line loses exit status
+ local msg
+ msg=$(req_env_var $input_args)
+ local status=$?
+
+ check_result "$msg" "$expected_msg" "req_env_var $input_args"
+ check_result "$status" "$expected_status" "req_env_var $input_args (rc)"
+}
+
+# error if called with no args
+test_rev '' 1 'FATAL: req_env_var: invoked without arguments'
+
+# error if desired envariable is unset
+unset FOO BAR
+test_rev FOO 9 'FATAL: test_rev() requires $FOO to be non-empty'
+test_rev BAR 9 'FATAL: test_rev() requires $BAR to be non-empty'
+
+# OK if desired envariable is unset
+FOO=1
+test_rev FOO 0 ''
+
+# ...but error if any single desired one is unset
+test_rev "FOO BAR" 9 'FATAL: test_rev() requires $BAR to be non-empty'
+
+# ...and OK if all args are set
+BAR=1
+test_rev "FOO BAR" 0 ''
+
+###############################################################################
+
+exit $rc
diff --git a/contrib/cirrus/packer/centos_setup.sh b/contrib/cirrus/packer/centos_setup.sh
index d947a1d7f..91b1963c2 100644
--- a/contrib/cirrus/packer/centos_setup.sh
+++ b/contrib/cirrus/packer/centos_setup.sh
@@ -8,12 +8,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source /tmp/libpod/$SCRIPT_BASE/lib.sh
-req_env_var "
-SCRIPT_BASE $SCRIPT_BASE
-CNI_COMMIT $CNI_COMMIT
-CRIO_COMMIT $CRIO_COMMIT
-CRIU_COMMIT $CRIU_COMMIT
-"
+req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT
install_ooe
diff --git a/contrib/cirrus/packer/fah_setup.sh b/contrib/cirrus/packer/fah_setup.sh
index 2e053b396..18c4db0af 100644
--- a/contrib/cirrus/packer/fah_setup.sh
+++ b/contrib/cirrus/packer/fah_setup.sh
@@ -8,9 +8,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source /tmp/libpod/$SCRIPT_BASE/lib.sh
-req_env_var "
-SCRIPT_BASE $SCRIPT_BASE
-"
+req_env_var SCRIPT_BASE
install_ooe
diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh
index 84aee7667..36a65eb71 100644
--- a/contrib/cirrus/packer/fedora_setup.sh
+++ b/contrib/cirrus/packer/fedora_setup.sh
@@ -8,14 +8,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source /tmp/libpod/$SCRIPT_BASE/lib.sh
-req_env_var "
-SCRIPT_BASE $SCRIPT_BASE
-FEDORA_CNI_COMMIT $FEDORA_CNI_COMMIT
-CNI_COMMIT $CNI_COMMIT
-CRIO_COMMIT $CRIO_COMMIT
-CRIU_COMMIT $CRIU_COMMIT
-RUNC_COMMIT $RUNC_COMMIT
-"
+req_env_var SCRIPT_BASE FEDORA_CNI_COMMIT CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RUNC_COMMIT
install_ooe
diff --git a/contrib/cirrus/packer/image-builder-image_base-setup.sh b/contrib/cirrus/packer/image-builder-image_base-setup.sh
index 8cf9fd8ab..43cfa7180 100644
--- a/contrib/cirrus/packer/image-builder-image_base-setup.sh
+++ b/contrib/cirrus/packer/image-builder-image_base-setup.sh
@@ -11,12 +11,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source $GOSRC/$SCRIPT_BASE/lib.sh
-req_env_var "
- TIMESTAMP $TIMESTAMP
- GOSRC $GOSRC
- SCRIPT_BASE $SCRIPT_BASE
- PACKER_BASE $PACKER_BASE
-"
+req_env_var TIMESTAMP GOSRC SCRIPT_BASE PACKER_BASE
install_ooe
diff --git a/contrib/cirrus/packer/rhel_base-setup.sh b/contrib/cirrus/packer/rhel_base-setup.sh
index fbf9f61af..8d5892d7d 100644
--- a/contrib/cirrus/packer/rhel_base-setup.sh
+++ b/contrib/cirrus/packer/rhel_base-setup.sh
@@ -10,9 +10,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source $GOSRC/$SCRIPT_BASE/lib.sh
-req_env_var "
- RHSM_COMMAND $RHSM_COMMAND
-"
+req_env_var RHSM_COMMAND
install_ooe
diff --git a/contrib/cirrus/packer/rhel_setup.sh b/contrib/cirrus/packer/rhel_setup.sh
index 20be97f9b..45f5c3e9b 100644
--- a/contrib/cirrus/packer/rhel_setup.sh
+++ b/contrib/cirrus/packer/rhel_setup.sh
@@ -8,13 +8,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source /tmp/libpod/$SCRIPT_BASE/lib.sh
-req_env_var "
-SCRIPT_BASE $SCRIPT_BASE
-CNI_COMMIT $CNI_COMMIT
-CRIO_COMMIT $CRIO_COMMIT
-CRIU_COMMIT $CRIU_COMMIT
-RHSM_COMMAND $RHSM_COMMAND
-"
+req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RHSM_COMMAND
install_ooe
diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh
index e84566ce3..d3ac8bddb 100644
--- a/contrib/cirrus/packer/ubuntu_setup.sh
+++ b/contrib/cirrus/packer/ubuntu_setup.sh
@@ -8,13 +8,7 @@ set -e
# Load in library (copied by packer, before this script was run)
source /tmp/libpod/$SCRIPT_BASE/lib.sh
-req_env_var "
-SCRIPT_BASE $SCRIPT_BASE
-CNI_COMMIT $CNI_COMMIT
-CRIO_COMMIT $CRIO_COMMIT
-CRIU_COMMIT $CRIU_COMMIT
-RUNC_COMMIT $RUNC_COMMIT
-"
+req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RUNC_COMMIT
install_ooe
diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh
index 09c57f993..2803d4318 100755
--- a/contrib/cirrus/rootless_test.sh
+++ b/contrib/cirrus/rootless_test.sh
@@ -6,11 +6,7 @@ source $HOME/.bash_profile
cd $GOSRC
source $(dirname $0)/lib.sh
-req_env_var "
-GOSRC $GOSRC
-OS_RELEASE_ID $OS_RELEASE_ID
-OS_RELEASE_VER $OS_RELEASE_VER
-"
+req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER
if [[ "$UID" == "0" ]]
then
diff --git a/contrib/cirrus/setup_container_environment.sh b/contrib/cirrus/setup_container_environment.sh
index 23df4fe8b..eda6f6167 100755
--- a/contrib/cirrus/setup_container_environment.sh
+++ b/contrib/cirrus/setup_container_environment.sh
@@ -3,11 +3,7 @@ set -e
source $(dirname $0)/lib.sh
-req_env_var "
-GOSRC $GOSRC
-OS_RELEASE_ID $OS_RELEASE_ID
-CONTAINER_RUNTIME $CONTAINER_RUNTIME
-"
+req_env_var GOSRC OS_RELEASE_ID CONTAINER_RUNTIME
DIST=$OS_RELEASE_ID
IMAGE=${DIST}podmanbuild
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index 3818abbc7..3bc6c2290 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -6,12 +6,7 @@ source $(dirname $0)/lib.sh
record_timestamp "env. setup start"
-req_env_var "
-USER $USER
-HOME $HOME
-ENVLIB $ENVLIB
-SCRIPT_BASE $SCRIPT_BASE
-CIRRUS_BUILD_ID $CIRRUS_BUILD_ID"
+req_env_var USER HOME ENVLIB SCRIPT_BASE CIRRUS_BUILD_ID
[[ "$SHELL" =~ "bash" ]] || chsh -s /bin/bash
diff --git a/contrib/cirrus/success.sh b/contrib/cirrus/success.sh
index 2b0cf4655..c4e150514 100755
--- a/contrib/cirrus/success.sh
+++ b/contrib/cirrus/success.sh
@@ -4,10 +4,7 @@ set -e
source $(dirname $0)/lib.sh
-req_env_var "
- CIRRUS_BRANCH $CIRRUS_BRANCH
- CIRRUS_BUILD_ID $CIRRUS_BUILD_ID
-"
+req_env_var CIRRUS_BRANCH CIRRUS_BUILD_ID
REF=$(basename $CIRRUS_BRANCH) # PR number or branch named
URL="https://cirrus-ci.com/build/$CIRRUS_BUILD_ID"
diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh
index cb179407a..dd5ef511d 100755
--- a/contrib/cirrus/system_test.sh
+++ b/contrib/cirrus/system_test.sh
@@ -3,11 +3,7 @@
set -e
source $(dirname $0)/lib.sh
-req_env_var "
-GOSRC $GOSRC
-OS_RELEASE_ID $OS_RELEASE_ID
-OS_RELEASE_VER $OS_RELEASE_VER
-"
+req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER
clean_env
diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh
index 4ace19d10..a0964061f 100755
--- a/contrib/cirrus/unit_test.sh
+++ b/contrib/cirrus/unit_test.sh
@@ -3,11 +3,7 @@
set -e
source $(dirname $0)/lib.sh
-req_env_var "
-GOSRC $GOSRC
-OS_RELEASE_ID $OS_RELEASE_ID
-OS_RELEASE_VER $OS_RELEASE_VER
-"
+req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER
record_timestamp "unit test start"
diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md
index 1ba957480..564d791fa 100644
--- a/docs/podman-container.1.md
+++ b/docs/podman-container.1.md
@@ -22,6 +22,7 @@ The container command allows you to manage containers
| exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
| exists | [podman-container-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage |
| export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
+| init | [podman-init(1)](podman-init.1.md) | Initialize a container |
| inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. |
| kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. |
| list | [podman-ps(1)](podman-ps.1.md) | List the containers on the system.(alias ls) |
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 884a8adcc..6d7d983b6 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -250,7 +250,17 @@ By default proxy environment variables are passed into the container if set
for the podman process. This can be disabled by setting the `--http-proxy`
option to `false`. The environment variables passed in include `http_proxy`,
`https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of
-those.
+those. This option is only needed when the host system must use a proxy but
+the container should not use any proxy. Proxy environment variables specified
+for the container in any other way will override the values that would have
+been passed thru from the host. (Other ways to specify the proxy for the
+container include passing the values with the `--env` flag, or hardcoding the
+proxy environment at container build time.)
+
+For example, to disable passing these environment variables from host to
+container:
+
+`--http-proxy=false`
Defaults to `true`
@@ -269,7 +279,7 @@ The following example maps uids 0-2000 in the container to the uids 30000-31999
Add additional groups to run as
-**--healthchech**=""
+**--healthcheck**=""
Set or alter a healthcheck for a container. The value must be of the format of:
diff --git a/docs/podman-generate-systemd.1.md b/docs/podman-generate-systemd.1.md
new file mode 100644
index 000000000..cc3f098a6
--- /dev/null
+++ b/docs/podman-generate-systemd.1.md
@@ -0,0 +1,69 @@
+% podman-generate Podman Man Pages
+% Brent Baude
+% April 2019
+# NAME
+podman-generate-systemd- Generate Systemd Unit file
+
+# SYNOPSIS
+**podman generate systemd** [*-n*|*--name*] [*-t*|*--timeout*] [*--restart-policy*] *container*
+
+# DESCRIPTION
+**podman generate systemd** will create a Systemd unit file that can be used to control a container. The
+command will dynamically create the unit file and output it to stdout where it can be piped by the user
+to a file. The options can be used to influence the results of the output as well.
+
+
+# OPTIONS:
+
+**--name** **-n**
+
+Use the name of the container for the start, stop, and description in the unit file
+
+**--timeout** **-t**
+
+Override the default stop timeout for the container with the given value.
+
+**--restart-policy**
+Set the SystemD restart policy. The restart-policy must be one of: "no", "on-success", "on-failure", "on-abnormal",
+"on-watchdog", "on-abort", or "always". The default policy is *on-failure*.
+
+## Examples ##
+
+Create a systemd unit file for a container running nginx:
+
+```
+$ sudo podman generate systemd nginx
+[Unit]
+Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+[Service]
+Restart=on-failure
+ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+ExecStop=/usr/bin/podman stop -t 10 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+KillMode=none
+Type=forking
+PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.pid
+[Install]
+WantedBy=multi-user.target
+```
+
+Create a systemd unit file for a container running nginx with an *always* restart policy and 1-second timeout.
+```
+$ sudo podman generate systemd --restart-policy=always -t 1 nginx
+[Unit]
+Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+[Service]
+Restart=always
+ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+ExecStop=/usr/bin/podman stop -t 1 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+KillMode=none
+Type=forking
+PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.pid
+[Install]
+WantedBy=multi-user.target
+```
+
+## SEE ALSO
+podman(1), podman-container(1)
+
+# HISTORY
+April 2019, Originally compiled by Brent Baude (bbaude at redhat dot com)
diff --git a/docs/podman-generate.1.md b/docs/podman-generate.1.md
index d1736f38e..5a2386778 100644
--- a/docs/podman-generate.1.md
+++ b/docs/podman-generate.1.md
@@ -14,6 +14,7 @@ The generate command will create structured output (like YAML) based on a contai
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| kube | [podman-generate-kube(1)](podman-generate-kube.1.md)| Generate Kubernetes YAML based on a pod or container. |
+| systemd | [podman-generate-systemd(1)](podman-generate-systemd.1.md)| Generate a systemd unit file for a container. |
## SEE ALSO
podman, podman-pod, podman-container
diff --git a/docs/podman-init.1.md b/docs/podman-init.1.md
new file mode 100644
index 000000000..f43757f62
--- /dev/null
+++ b/docs/podman-init.1.md
@@ -0,0 +1,41 @@
+% podman-init(1)
+
+## NAME
+podman\-init - Initialize one or more containers
+
+## SYNOPSIS
+**podman init** [*options*] *container* ...
+
+## DESCRIPTION
+Initialize one or more containers.
+You may use container IDs or names as input.
+Initializing a container performs all tasks necessary for starting the container (mounting filesystems, creating an OCI spec, initializing the container network) but does not start the container.
+If a container is not initialized, the `podman start` and `podman run` commands will do so automatically prior to starting it.
+This command is intended to be used for inspecting or modifying the container's filesystem or OCI spec prior to starting it.
+This can be used to inspect the container before it runs, or debug why a container is failing to run.
+
+## OPTIONS
+
+**--all, -a**
+
+Initialize all containers. Containers that have already initialized (including containers that have been started and are running) are ignored.
+
+**--latest, -l**
+Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
+to run containers such as CRI-O, the last started container could be from either of those methods.
+
+The latest option is not supported on the remote client.
+
+## EXAMPLE
+
+podman init 35480fc9d568
+
+podman init test1
+
+podman init --latest
+
+## SEE ALSO
+podman(1), podman-start(1)
+
+## HISTORY
+April 2019, Originally compiled by Matthew Heon <mheon@redhat.com>
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index a0c17652a..9efb7f51c 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -257,7 +257,17 @@ By default proxy environment variables are passed into the container if set
for the podman process. This can be disabled by setting the `--http-proxy`
option to `false`. The environment variables passed in include `http_proxy`,
`https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of
-those.
+those. This option is only needed when the host system must use a proxy but
+the container should not use any proxy. Proxy environment variables specified
+for the container in any other way will override the values that would have
+been passed thru from the host. (Other ways to specify the proxy for the
+container include passing the values with the `--env` flag, or hardcoding the
+proxy environment at container build time.)
+
+For example, to disable passing these environment variables from host to
+container:
+
+`--http-proxy=false`
Defaults to `true`
@@ -277,7 +287,7 @@ The example maps gids 0-2000 in the container to the gids 30000-31999 on the hos
Add additional groups to run as
-**--healthchech**=""
+**--healthcheck**=""
Set or alter a healthcheck for a container. The value must be of the format of:
@@ -583,7 +593,7 @@ Not implemented.
Restart should be handled via a systemd unit files. Please add your podman
commands to a unit file and allow systemd or your init system to handle the
-restarting of the container processes. See example below.
+restarting of the container processes. See *podman generate systemd*.
**--rm**=*true*|*false*
@@ -1141,21 +1151,6 @@ the uids and gids from the host.
$ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
```
-### Running a podman container to restart inside of a systemd unit file
-
-
-```
-[Unit]
-Description=My App
-[Service]
-Restart=always
-ExecStart=/usr/bin/podman start -a my_app
-ExecStop=/usr/bin/podman stop -t 10 my_app
-KillMode=process
-[Install]
-WantedBy=multi-user.target
-```
-
### Configuring Storage Options from the command line
Podman allows for the configuration of storage by changing the values
diff --git a/docs/podman.1.md b/docs/podman.1.md
index 9c0ca8a7a..ef12cf1cc 100644
--- a/docs/podman.1.md
+++ b/docs/podman.1.md
@@ -147,6 +147,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-images(1)](podman-images.1.md) | List images in local storage. |
| [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. |
| [podman-info(1)](podman-info.1.md) | Displays Podman related system information. |
+| [podman-init(1)](podman-init.1.md) | Initialize a container |
| [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. |
| [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. |
| [podman-load(1)](podman-load.1.md) | Load an image from a container image archive into container storage. |
diff --git a/install.md b/install.md
index 548b38c1b..bd7f326c3 100644
--- a/install.md
+++ b/install.md
@@ -8,6 +8,8 @@
sudo pacman -S podman
```
+If you have problems when running podman in [rootless](README.md#rootless) mode follow [these instructions](https://wiki.archlinux.org/index.php/Linux_Containers#Enable_support_to_run_unprivileged_containers_(optional))
+
#### [Fedora](https://www.fedoraproject.org), [CentOS](https://www.centos.org)
```bash
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 465b23831..5bfd869b3 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -40,7 +40,7 @@ func (c *Container) Init(ctx context.Context) (err error) {
if !(c.state.State == ContainerStateConfigured ||
c.state.State == ContainerStateStopped ||
c.state.State == ContainerStateExited) {
- return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID())
+ return errors.Wrapf(ErrCtrStateInvalid, "container %s has already been created in runtime", c.ID())
}
// don't recursively start
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 927b71b2b..a791df491 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -299,7 +299,7 @@ func (c *Container) setupStorage(ctx context.Context) error {
return errors.Wrapf(err, "error creating container storage")
}
- if !rootless.IsRootless() && (len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0) {
+ if len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0 {
if err := os.Chown(containerInfo.RunDir, c.RootUID(), c.RootGID()); err != nil {
return err
}
@@ -811,8 +811,9 @@ func (c *Container) cleanupRuntime(ctx context.Context) error {
span.SetTag("struct", "container")
defer span.Finish()
- // If the container is not ContainerStateStopped, do nothing
- if c.state.State != ContainerStateStopped {
+ // If the container is not ContainerStateStopped or
+ // ContainerStateCreated, do nothing.
+ if c.state.State != ContainerStateStopped && c.state.State != ContainerStateCreated {
return nil
}
@@ -825,9 +826,14 @@ func (c *Container) cleanupRuntime(ctx context.Context) error {
return err
}
- // Our state is now Exited, as we've removed ourself from
- // the runtime.
- c.state.State = ContainerStateExited
+ // If we were Stopped, we are now Exited, as we've removed ourself
+ // from the runtime.
+ // If we were Created, we are now Configured.
+ if c.state.State == ContainerStateStopped {
+ c.state.State = ContainerStateExited
+ } else if c.state.State == ContainerStateCreated {
+ c.state.State = ContainerStateConfigured
+ }
if c.valid {
if err := c.save(); err != nil {
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index 3a6609740..5c48cc8ee 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -3,16 +3,13 @@ package libpod
import (
"bufio"
"bytes"
- "fmt"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"strings"
"time"
"github.com/containers/libpod/pkg/inspect"
- "github.com/coreos/go-systemd/dbus"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -241,62 +238,6 @@ func (c *Container) GetHealthCheckLog() (inspect.HealthCheckResults, error) {
return healthCheck, nil
}
-// createTimer systemd timers for healthchecks of a container
-func (c *Container) createTimer() error {
- if c.disableHealthCheckSystemd() {
- return nil
- }
- podman, err := os.Executable()
- if err != nil {
- return errors.Wrapf(err, "failed to get path for podman for a health check timer")
- }
-
- var cmd = []string{"--unit", fmt.Sprintf("%s", c.ID()), fmt.Sprintf("--on-unit-inactive=%s", c.HealthCheckConfig().Interval.String()), "--timer-property=AccuracySec=1s", podman, "healthcheck", "run", c.ID()}
-
- conn, err := dbus.NewSystemdConnection()
- if err != nil {
- return errors.Wrapf(err, "unable to get systemd connection to add healthchecks")
- }
- conn.Close()
- logrus.Debugf("creating systemd-transient files: %s %s", "systemd-run", cmd)
- systemdRun := exec.Command("systemd-run", cmd...)
- _, err = systemdRun.CombinedOutput()
- if err != nil {
- return err
- }
- return nil
-}
-
-// startTimer starts a systemd timer for the healthchecks
-func (c *Container) startTimer() error {
- if c.disableHealthCheckSystemd() {
- return nil
- }
- conn, err := dbus.NewSystemdConnection()
- if err != nil {
- return errors.Wrapf(err, "unable to get systemd connection to start healthchecks")
- }
- defer conn.Close()
- _, err = conn.StartUnit(fmt.Sprintf("%s.service", c.ID()), "fail", nil)
- return err
-}
-
-// removeTimer removes the systemd timer and unit files
-// for the container
-func (c *Container) removeTimer() error {
- if c.disableHealthCheckSystemd() {
- return nil
- }
- conn, err := dbus.NewSystemdConnection()
- if err != nil {
- return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks")
- }
- defer conn.Close()
- serviceFile := fmt.Sprintf("%s.timer", c.ID())
- _, err = conn.StopUnit(serviceFile, "fail", nil)
- return err
-}
-
// HealthCheckStatus returns the current state of a container with a healthcheck
func (c *Container) HealthCheckStatus() (string, error) {
if !c.HasHealthCheck() {
diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go
new file mode 100644
index 000000000..869605ea8
--- /dev/null
+++ b/libpod/healthcheck_linux.go
@@ -0,0 +1,67 @@
+package libpod
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/coreos/go-systemd/dbus"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// createTimer systemd timers for healthchecks of a container
+func (c *Container) createTimer() error {
+ if c.disableHealthCheckSystemd() {
+ return nil
+ }
+ podman, err := os.Executable()
+ if err != nil {
+ return errors.Wrapf(err, "failed to get path for podman for a health check timer")
+ }
+
+ var cmd = []string{"--unit", fmt.Sprintf("%s", c.ID()), fmt.Sprintf("--on-unit-inactive=%s", c.HealthCheckConfig().Interval.String()), "--timer-property=AccuracySec=1s", podman, "healthcheck", "run", c.ID()}
+
+ conn, err := dbus.NewSystemdConnection()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get systemd connection to add healthchecks")
+ }
+ conn.Close()
+ logrus.Debugf("creating systemd-transient files: %s %s", "systemd-run", cmd)
+ systemdRun := exec.Command("systemd-run", cmd...)
+ _, err = systemdRun.CombinedOutput()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// startTimer starts a systemd timer for the healthchecks
+func (c *Container) startTimer() error {
+ if c.disableHealthCheckSystemd() {
+ return nil
+ }
+ conn, err := dbus.NewSystemdConnection()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get systemd connection to start healthchecks")
+ }
+ defer conn.Close()
+ _, err = conn.StartUnit(fmt.Sprintf("%s.service", c.ID()), "fail", nil)
+ return err
+}
+
+// removeTimer removes the systemd timer and unit files
+// for the container
+func (c *Container) removeTimer() error {
+ if c.disableHealthCheckSystemd() {
+ return nil
+ }
+ conn, err := dbus.NewSystemdConnection()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks")
+ }
+ defer conn.Close()
+ serviceFile := fmt.Sprintf("%s.timer", c.ID())
+ _, err = conn.StopUnit(serviceFile, "fail", nil)
+ return err
+}
diff --git a/libpod/healthcheck_unsupported.go b/libpod/healthcheck_unsupported.go
new file mode 100644
index 000000000..d01d1ccd4
--- /dev/null
+++ b/libpod/healthcheck_unsupported.go
@@ -0,0 +1,19 @@
+// +build !linux
+
+package libpod
+
+// createTimer systemd timers for healthchecks of a container
+func (c *Container) createTimer() error {
+ return ErrNotImplemented
+}
+
+// startTimer starts a systemd timer for the healthchecks
+func (c *Container) startTimer() error {
+ return ErrNotImplemented
+}
+
+// removeTimer removes the systemd timer and unit files
+// for the container
+func (c *Container) removeTimer() error {
+ return ErrNotImplemented
+}
diff --git a/libpod/oci.go b/libpod/oci.go
index 189359753..3dfde4f24 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -1,7 +1,6 @@
package libpod
import (
- "bufio"
"bytes"
"fmt"
"io/ioutil"
@@ -9,21 +8,15 @@ import (
"os"
"os/exec"
"path/filepath"
- "runtime"
"strings"
- "syscall"
"time"
- "github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
- "github.com/coreos/go-systemd/activation"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/opencontainers/selinux/go-selinux"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "golang.org/x/sys/unix"
kwait "k8s.io/apimachinery/pkg/util/wait"
// TODO import these functions into libpod and remove the import
@@ -118,70 +111,6 @@ func createUnitName(prefix string, name string) string {
return fmt.Sprintf("%s-%s.scope", prefix, name)
}
-// Wait for a container which has been sent a signal to stop
-func waitContainerStop(ctr *Container, timeout time.Duration) error {
- done := make(chan struct{})
- chControl := make(chan struct{})
- go func() {
- for {
- select {
- case <-chControl:
- return
- default:
- // Check if the process is still around
- err := unix.Kill(ctr.state.PID, 0)
- if err == unix.ESRCH {
- close(done)
- return
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- }()
- select {
- case <-done:
- return nil
- case <-time.After(timeout):
- close(chControl)
- logrus.Debugf("container %s did not die within timeout %d", ctr.ID(), timeout)
- return errors.Errorf("container %s did not die within timeout", ctr.ID())
- }
-}
-
-// Wait for a set of given PIDs to stop
-func waitPidsStop(pids []int, timeout time.Duration) error {
- done := make(chan struct{})
- chControl := make(chan struct{})
- go func() {
- for {
- select {
- case <-chControl:
- return
- default:
- allClosed := true
- for _, pid := range pids {
- if err := unix.Kill(pid, 0); err != unix.ESRCH {
- allClosed = false
- break
- }
- }
- if allClosed {
- close(done)
- return
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- }()
- select {
- case <-done:
- return nil
- case <-time.After(timeout):
- close(chControl)
- return errors.Errorf("given PIDs did not die within timeout")
- }
-}
-
func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
var files []*os.File
notifySCTP := false
@@ -234,241 +163,6 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
return files, nil
}
-func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
- var stderrBuf bytes.Buffer
-
- runtimeDir, err := util.GetRootlessRuntimeDir()
- if err != nil {
- return err
- }
-
- parentPipe, childPipe, err := newPipe()
- if err != nil {
- return errors.Wrapf(err, "error creating socket pair")
- }
-
- childStartPipe, parentStartPipe, err := newPipe()
- if err != nil {
- return errors.Wrapf(err, "error creating socket pair for start pipe")
- }
-
- defer parentPipe.Close()
- defer parentStartPipe.Close()
-
- args := []string{}
- if r.cgroupManager == SystemdCgroupsManager {
- args = append(args, "-s")
- }
- args = append(args, "-c", ctr.ID())
- args = append(args, "-u", ctr.ID())
- args = append(args, "-r", r.path)
- args = append(args, "-b", ctr.bundlePath())
- args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile"))
- args = append(args, "-l", ctr.LogPath())
- args = append(args, "--exit-dir", r.exitsDir)
- if ctr.config.ConmonPidFile != "" {
- args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile)
- }
- if len(ctr.config.ExitCommand) > 0 {
- args = append(args, "--exit-command", ctr.config.ExitCommand[0])
- for _, arg := range ctr.config.ExitCommand[1:] {
- args = append(args, []string{"--exit-command-arg", arg}...)
- }
- }
- args = append(args, "--socket-dir-path", r.socketsDir)
- if ctr.config.Spec.Process.Terminal {
- args = append(args, "-t")
- } else if ctr.config.Stdin {
- args = append(args, "-i")
- }
- if r.logSizeMax >= 0 {
- args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
- }
- if r.noPivot {
- args = append(args, "--no-pivot")
- }
-
- logLevel := logrus.GetLevel()
- args = append(args, "--log-level", logLevel.String())
-
- if logLevel == logrus.DebugLevel {
- logrus.Debugf("%s messages will be logged to syslog", r.conmonPath)
- args = append(args, "--syslog")
- }
-
- if restoreOptions != nil {
- args = append(args, "--restore", ctr.CheckpointPath())
- if restoreOptions.TCPEstablished {
- args = append(args, "--restore-arg", "--tcp-established")
- }
- }
-
- logrus.WithFields(logrus.Fields{
- "args": args,
- }).Debugf("running conmon: %s", r.conmonPath)
-
- cmd := exec.Command(r.conmonPath, args...)
- cmd.Dir = ctr.bundlePath()
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setpgid: true,
- }
- // TODO this is probably a really bad idea for some uses
- // Make this configurable
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if ctr.config.Spec.Process.Terminal {
- cmd.Stderr = &stderrBuf
- }
-
- cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe, childStartPipe)
- // 0, 1 and 2 are stdin, stdout and stderr
- cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
- cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
- cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
- cmd.Env = append(cmd.Env, fmt.Sprintf("_CONTAINERS_USERNS_CONFIGURED=%s", os.Getenv("_CONTAINERS_USERNS_CONFIGURED")))
- cmd.Env = append(cmd.Env, fmt.Sprintf("_CONTAINERS_ROOTLESS_UID=%s", os.Getenv("_CONTAINERS_ROOTLESS_UID")))
- cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME")))
-
- if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() {
- ports, err := bindPorts(ctr.config.PortMappings)
- if err != nil {
- return err
- }
-
- // Leak the port we bound in the conmon process. These fd's won't be used
- // by the container and conmon will keep the ports busy so that another
- // process cannot use them.
- cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
- }
-
- if ctr.config.NetMode.IsSlirp4netns() {
- ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
- if err != nil {
- return errors.Wrapf(err, "failed to create rootless network sync pipe")
- }
- // Leak one end in conmon, the other one will be leaked into slirp4netns
- cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
- }
-
- if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
- cmd.Env = append(cmd.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
- }
- if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok {
- cmd.Env = append(cmd.Env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1")
- fds := activation.Files(false)
- cmd.ExtraFiles = append(cmd.ExtraFiles, fds...)
- }
- if selinux.GetEnabled() {
- // Set the label of the conmon process to be level :s0
- // This will allow the container processes to talk to fifo-files
- // passed into the container by conmon
- var (
- plabel string
- con selinux.Context
- )
- plabel, err = selinux.CurrentLabel()
- if err != nil {
- childPipe.Close()
- return errors.Wrapf(err, "Failed to get current SELinux label")
- }
-
- con, err = selinux.NewContext(plabel)
- if err != nil {
- return errors.Wrapf(err, "Failed to get new context from SELinux label")
- }
-
- runtime.LockOSThread()
- if con["level"] != "s0" && con["level"] != "" {
- con["level"] = "s0"
- if err = label.SetProcessLabel(con.Get()); err != nil {
- runtime.UnlockOSThread()
- return err
- }
- }
- err = cmd.Start()
- // Ignore error returned from SetProcessLabel("") call,
- // can't recover.
- label.SetProcessLabel("")
- runtime.UnlockOSThread()
- } else {
- err = cmd.Start()
- }
- if err != nil {
- childPipe.Close()
- return err
- }
- defer cmd.Wait()
-
- // We don't need childPipe on the parent side
- childPipe.Close()
- childStartPipe.Close()
-
- // Move conmon to specified cgroup
- if err := r.moveConmonToCgroup(ctr, cgroupParent, cmd); err != nil {
- return err
- }
-
- /* We set the cgroup, now the child can start creating children */
- someData := []byte{0}
- _, err = parentStartPipe.Write(someData)
- if err != nil {
- return err
- }
-
- /* Wait for initial setup and fork, and reap child */
- err = cmd.Wait()
- if err != nil {
- return err
- }
-
- defer func() {
- if err != nil {
- if err2 := r.deleteContainer(ctr); err2 != nil {
- logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
- }
- }
- }()
-
- // Wait to get container pid from conmon
- type syncStruct struct {
- si *syncInfo
- err error
- }
- ch := make(chan syncStruct)
- go func() {
- var si *syncInfo
- rdr := bufio.NewReader(parentPipe)
- b, err := rdr.ReadBytes('\n')
- if err != nil {
- ch <- syncStruct{err: err}
- }
- if err := json.Unmarshal(b, &si); err != nil {
- ch <- syncStruct{err: err}
- return
- }
- ch <- syncStruct{si: si}
- }()
-
- select {
- case ss := <-ch:
- if ss.err != nil {
- return errors.Wrapf(ss.err, "error reading container (probably exited) json message")
- }
- logrus.Debugf("Received container pid: %d", ss.si.Pid)
- if ss.si.Pid == -1 {
- if ss.si.Message != "" {
- return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message)
- }
- return errors.Wrapf(ErrInternal, "container create failed")
- }
- ctr.state.PID = ss.si.Pid
- case <-time.After(ContainerCreateTimeout):
- return errors.Wrapf(ErrInternal, "container creation timeout")
- }
- return nil
-}
-
// updateContainerStatus retrieves the current status of the container from the
// runtime. It updates the container's state but does not save it.
// If useRunc is false, we will not directly hit runc to see the container's
@@ -631,82 +325,6 @@ func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error {
return nil
}
-// stopContainer stops a container, first using its given stop signal (or
-// SIGTERM if no signal was specified), then using SIGKILL
-// Timeout is given in seconds. If timeout is 0, the container will be
-// immediately kill with SIGKILL
-// Does not set finished time for container, assumes you will run updateStatus
-// after to pull the exit code
-func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error {
- logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID)
-
- // Ping the container to see if it's alive
- // If it's not, it's already stopped, return
- err := unix.Kill(ctr.state.PID, 0)
- if err == unix.ESRCH {
- return nil
- }
-
- stopSignal := ctr.config.StopSignal
- if stopSignal == 0 {
- stopSignal = uint(syscall.SIGTERM)
- }
-
- if timeout > 0 {
- if err := r.killContainer(ctr, stopSignal); err != nil {
- // Is the container gone?
- // If so, it probably died between the first check and
- // our sending the signal
- // The container is stopped, so exit cleanly
- err := unix.Kill(ctr.state.PID, 0)
- if err == unix.ESRCH {
- return nil
- }
-
- return err
- }
-
- if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID())
- } else {
- // No error, the container is dead
- return nil
- }
- }
-
- var args []string
- if rootless.IsRootless() {
- // we don't use --all for rootless containers as the OCI runtime might use
- // the cgroups to determine the PIDs, but for rootless containers there is
- // not any.
- args = []string{"kill", ctr.ID(), "KILL"}
- } else {
- args = []string{"kill", "--all", ctr.ID(), "KILL"}
- }
-
- runtimeDir, err := util.GetRootlessRuntimeDir()
- if err != nil {
- return err
- }
- env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil {
- // Again, check if the container is gone. If it is, exit cleanly.
- err := unix.Kill(ctr.state.PID, 0)
- if err == unix.ESRCH {
- return nil
- }
-
- return errors.Wrapf(err, "error sending SIGKILL to container %s", ctr.ID())
- }
-
- // Give runtime a few seconds to make it happen
- if err := waitContainerStop(ctr, killContainerTimeout); err != nil {
- return err
- }
-
- return nil
-}
-
// deleteContainer deletes a container from the OCI runtime
func (r *OCIRuntime) deleteContainer(ctr *Container) error {
runtimeDir, err := util.GetRootlessRuntimeDir()
@@ -834,71 +452,6 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty
return execCmd, nil
}
-// execStopContainer stops all active exec sessions in a container
-// It will also stop all other processes in the container. It is only intended
-// to be used to assist in cleanup when removing a container.
-// SIGTERM is used by default to stop processes. If SIGTERM fails, SIGKILL will be used.
-func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
- // Do we have active exec sessions?
- if len(ctr.state.ExecSessions) == 0 {
- return nil
- }
-
- // Get a list of active exec sessions
- execSessions := []int{}
- for _, session := range ctr.state.ExecSessions {
- pid := session.PID
- // Ping the PID with signal 0 to see if it still exists
- if err := unix.Kill(pid, 0); err == unix.ESRCH {
- continue
- }
-
- execSessions = append(execSessions, pid)
- }
-
- // All the sessions may be dead
- // If they are, just return
- if len(execSessions) == 0 {
- return nil
- }
- runtimeDir, err := util.GetRootlessRuntimeDir()
- if err != nil {
- return err
- }
- env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
-
- // If timeout is 0, just use SIGKILL
- if timeout > 0 {
- // Stop using SIGTERM by default
- // Use SIGSTOP after a timeout
- logrus.Debugf("Killing all processes in container %s with SIGTERM", ctr.ID())
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "TERM"); err != nil {
- return errors.Wrapf(err, "error sending SIGTERM to container %s processes", ctr.ID())
- }
-
- // Wait for all processes to stop
- if err := waitPidsStop(execSessions, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out stopping container %s exec sessions", ctr.ID())
- } else {
- // No error, all exec sessions are dead
- return nil
- }
- }
-
- // Send SIGKILL
- logrus.Debugf("Killing all processes in container %s with SIGKILL", ctr.ID())
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "KILL"); err != nil {
- return errors.Wrapf(err, "error sending SIGKILL to container %s processes", ctr.ID())
- }
-
- // Give the processes a few seconds to go down
- if err := waitPidsStop(execSessions, killContainerTimeout); err != nil {
- return errors.Wrapf(err, "failed to kill container %s exec sessions", ctr.ID())
- }
-
- return nil
-}
-
// checkpointContainer checkpoints the given container
func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error {
label.SetSocketLabel(ctr.ProcessLabel())
diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go
index 1f5411c1f..1c1e4a203 100644
--- a/libpod/oci_linux.go
+++ b/libpod/oci_linux.go
@@ -3,6 +3,8 @@
package libpod
import (
+ "bufio"
+ "bytes"
"fmt"
"os"
"os/exec"
@@ -10,12 +12,17 @@ import (
"runtime"
"strings"
"syscall"
+ "time"
"github.com/containerd/cgroups"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
pmount "github.com/containers/storage/pkg/mount"
+ "github.com/coreos/go-systemd/activation"
spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/selinux/go-selinux"
+ "github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -179,3 +186,443 @@ func (r *OCIRuntime) conmonPackage() string {
}
return dpkgVersion(r.conmonPath)
}
+
+func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
+ var stderrBuf bytes.Buffer
+
+ runtimeDir, err := util.GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+
+ parentPipe, childPipe, err := newPipe()
+ if err != nil {
+ return errors.Wrapf(err, "error creating socket pair")
+ }
+
+ childStartPipe, parentStartPipe, err := newPipe()
+ if err != nil {
+ return errors.Wrapf(err, "error creating socket pair for start pipe")
+ }
+
+ defer parentPipe.Close()
+ defer parentStartPipe.Close()
+
+ args := []string{}
+ if r.cgroupManager == SystemdCgroupsManager {
+ args = append(args, "-s")
+ }
+ args = append(args, "-c", ctr.ID())
+ args = append(args, "-u", ctr.ID())
+ args = append(args, "-r", r.path)
+ args = append(args, "-b", ctr.bundlePath())
+ args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile"))
+ args = append(args, "-l", ctr.LogPath())
+ args = append(args, "--exit-dir", r.exitsDir)
+ if ctr.config.ConmonPidFile != "" {
+ args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile)
+ }
+ if len(ctr.config.ExitCommand) > 0 {
+ args = append(args, "--exit-command", ctr.config.ExitCommand[0])
+ for _, arg := range ctr.config.ExitCommand[1:] {
+ args = append(args, []string{"--exit-command-arg", arg}...)
+ }
+ }
+ args = append(args, "--socket-dir-path", r.socketsDir)
+ if ctr.config.Spec.Process.Terminal {
+ args = append(args, "-t")
+ } else if ctr.config.Stdin {
+ args = append(args, "-i")
+ }
+ if r.logSizeMax >= 0 {
+ args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
+ }
+ if r.noPivot {
+ args = append(args, "--no-pivot")
+ }
+
+ logLevel := logrus.GetLevel()
+ args = append(args, "--log-level", logLevel.String())
+
+ if logLevel == logrus.DebugLevel {
+ logrus.Debugf("%s messages will be logged to syslog", r.conmonPath)
+ args = append(args, "--syslog")
+ }
+
+ if restoreOptions != nil {
+ args = append(args, "--restore", ctr.CheckpointPath())
+ if restoreOptions.TCPEstablished {
+ args = append(args, "--restore-arg", "--tcp-established")
+ }
+ }
+
+ logrus.WithFields(logrus.Fields{
+ "args": args,
+ }).Debugf("running conmon: %s", r.conmonPath)
+
+ cmd := exec.Command(r.conmonPath, args...)
+ cmd.Dir = ctr.bundlePath()
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setpgid: true,
+ }
+ // TODO this is probably a really bad idea for some uses
+ // Make this configurable
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if ctr.config.Spec.Process.Terminal {
+ cmd.Stderr = &stderrBuf
+ }
+
+ cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe, childStartPipe)
+ // 0, 1 and 2 are stdin, stdout and stderr
+ cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("_CONTAINERS_USERNS_CONFIGURED=%s", os.Getenv("_CONTAINERS_USERNS_CONFIGURED")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("_CONTAINERS_ROOTLESS_UID=%s", os.Getenv("_CONTAINERS_ROOTLESS_UID")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME")))
+
+ if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() {
+ ports, err := bindPorts(ctr.config.PortMappings)
+ if err != nil {
+ return err
+ }
+
+ // Leak the port we bound in the conmon process. These fd's won't be used
+ // by the container and conmon will keep the ports busy so that another
+ // process cannot use them.
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
+ }
+
+ if ctr.config.NetMode.IsSlirp4netns() {
+ ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "failed to create rootless network sync pipe")
+ }
+ // Leak one end in conmon, the other one will be leaked into slirp4netns
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
+ }
+
+ if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
+ cmd.Env = append(cmd.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
+ }
+ if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok {
+ cmd.Env = append(cmd.Env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1")
+ fds := activation.Files(false)
+ cmd.ExtraFiles = append(cmd.ExtraFiles, fds...)
+ }
+ if selinux.GetEnabled() {
+ // Set the label of the conmon process to be level :s0
+ // This will allow the container processes to talk to fifo-files
+ // passed into the container by conmon
+ var (
+ plabel string
+ con selinux.Context
+ )
+ plabel, err = selinux.CurrentLabel()
+ if err != nil {
+ childPipe.Close()
+ return errors.Wrapf(err, "Failed to get current SELinux label")
+ }
+
+ con, err = selinux.NewContext(plabel)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to get new context from SELinux label")
+ }
+
+ runtime.LockOSThread()
+ if con["level"] != "s0" && con["level"] != "" {
+ con["level"] = "s0"
+ if err = label.SetProcessLabel(con.Get()); err != nil {
+ runtime.UnlockOSThread()
+ return err
+ }
+ }
+ err = cmd.Start()
+ // Ignore error returned from SetProcessLabel("") call,
+ // can't recover.
+ label.SetProcessLabel("")
+ runtime.UnlockOSThread()
+ } else {
+ err = cmd.Start()
+ }
+ if err != nil {
+ childPipe.Close()
+ return err
+ }
+ defer cmd.Wait()
+
+ // We don't need childPipe on the parent side
+ childPipe.Close()
+ childStartPipe.Close()
+
+ // Move conmon to specified cgroup
+ if err := r.moveConmonToCgroup(ctr, cgroupParent, cmd); err != nil {
+ return err
+ }
+
+ /* We set the cgroup, now the child can start creating children */
+ someData := []byte{0}
+ _, err = parentStartPipe.Write(someData)
+ if err != nil {
+ return err
+ }
+
+ /* Wait for initial setup and fork, and reap child */
+ err = cmd.Wait()
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ if err2 := r.deleteContainer(ctr); err2 != nil {
+ logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
+ }
+ }
+ }()
+
+ // Wait to get container pid from conmon
+ type syncStruct struct {
+ si *syncInfo
+ err error
+ }
+ ch := make(chan syncStruct)
+ go func() {
+ var si *syncInfo
+ rdr := bufio.NewReader(parentPipe)
+ b, err := rdr.ReadBytes('\n')
+ if err != nil {
+ ch <- syncStruct{err: err}
+ }
+ if err := json.Unmarshal(b, &si); err != nil {
+ ch <- syncStruct{err: err}
+ return
+ }
+ ch <- syncStruct{si: si}
+ }()
+
+ select {
+ case ss := <-ch:
+ if ss.err != nil {
+ return errors.Wrapf(ss.err, "error reading container (probably exited) json message")
+ }
+ logrus.Debugf("Received container pid: %d", ss.si.Pid)
+ if ss.si.Pid == -1 {
+ if ss.si.Message != "" {
+ return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message)
+ }
+ return errors.Wrapf(ErrInternal, "container create failed")
+ }
+ ctr.state.PID = ss.si.Pid
+ case <-time.After(ContainerCreateTimeout):
+ return errors.Wrapf(ErrInternal, "container creation timeout")
+ }
+ return nil
+}
+
+// Wait for a container which has been sent a signal to stop
+func waitContainerStop(ctr *Container, timeout time.Duration) error {
+ done := make(chan struct{})
+ chControl := make(chan struct{})
+ go func() {
+ for {
+ select {
+ case <-chControl:
+ return
+ default:
+ // Check if the process is still around
+ err := unix.Kill(ctr.state.PID, 0)
+ if err == unix.ESRCH {
+ close(done)
+ return
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+ }()
+ select {
+ case <-done:
+ return nil
+ case <-time.After(timeout):
+ close(chControl)
+ logrus.Debugf("container %s did not die within timeout %d", ctr.ID(), timeout)
+ return errors.Errorf("container %s did not die within timeout", ctr.ID())
+ }
+}
+
+// Wait for a set of given PIDs to stop
+func waitPidsStop(pids []int, timeout time.Duration) error {
+ done := make(chan struct{})
+ chControl := make(chan struct{})
+ go func() {
+ for {
+ select {
+ case <-chControl:
+ return
+ default:
+ allClosed := true
+ for _, pid := range pids {
+ if err := unix.Kill(pid, 0); err != unix.ESRCH {
+ allClosed = false
+ break
+ }
+ }
+ if allClosed {
+ close(done)
+ return
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+ }()
+ select {
+ case <-done:
+ return nil
+ case <-time.After(timeout):
+ close(chControl)
+ return errors.Errorf("given PIDs did not die within timeout")
+ }
+}
+
+// stopContainer stops a container, first using its given stop signal (or
+// SIGTERM if no signal was specified), then using SIGKILL
+// Timeout is given in seconds. If timeout is 0, the container will be
+// immediately kill with SIGKILL
+// Does not set finished time for container, assumes you will run updateStatus
+// after to pull the exit code
+func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error {
+ logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID)
+
+ // Ping the container to see if it's alive
+ // If it's not, it's already stopped, return
+ err := unix.Kill(ctr.state.PID, 0)
+ if err == unix.ESRCH {
+ return nil
+ }
+
+ stopSignal := ctr.config.StopSignal
+ if stopSignal == 0 {
+ stopSignal = uint(syscall.SIGTERM)
+ }
+
+ if timeout > 0 {
+ if err := r.killContainer(ctr, stopSignal); err != nil {
+ // Is the container gone?
+ // If so, it probably died between the first check and
+ // our sending the signal
+ // The container is stopped, so exit cleanly
+ err := unix.Kill(ctr.state.PID, 0)
+ if err == unix.ESRCH {
+ return nil
+ }
+
+ return err
+ }
+
+ if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil {
+ logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID())
+ } else {
+ // No error, the container is dead
+ return nil
+ }
+ }
+
+ var args []string
+ if rootless.IsRootless() {
+ // we don't use --all for rootless containers as the OCI runtime might use
+ // the cgroups to determine the PIDs, but for rootless containers there is
+ // not any.
+ args = []string{"kill", ctr.ID(), "KILL"}
+ } else {
+ args = []string{"kill", "--all", ctr.ID(), "KILL"}
+ }
+
+ runtimeDir, err := util.GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil {
+ // Again, check if the container is gone. If it is, exit cleanly.
+ err := unix.Kill(ctr.state.PID, 0)
+ if err == unix.ESRCH {
+ return nil
+ }
+
+ return errors.Wrapf(err, "error sending SIGKILL to container %s", ctr.ID())
+ }
+
+ // Give runtime a few seconds to make it happen
+ if err := waitContainerStop(ctr, killContainerTimeout); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// execStopContainer stops all active exec sessions in a container
+// It will also stop all other processes in the container. It is only intended
+// to be used to assist in cleanup when removing a container.
+// SIGTERM is used by default to stop processes. If SIGTERM fails, SIGKILL will be used.
+func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
+ // Do we have active exec sessions?
+ if len(ctr.state.ExecSessions) == 0 {
+ return nil
+ }
+
+ // Get a list of active exec sessions
+ execSessions := []int{}
+ for _, session := range ctr.state.ExecSessions {
+ pid := session.PID
+ // Ping the PID with signal 0 to see if it still exists
+ if err := unix.Kill(pid, 0); err == unix.ESRCH {
+ continue
+ }
+
+ execSessions = append(execSessions, pid)
+ }
+
+ // All the sessions may be dead
+ // If they are, just return
+ if len(execSessions) == 0 {
+ return nil
+ }
+ runtimeDir, err := util.GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+
+ // If timeout is 0, just use SIGKILL
+ if timeout > 0 {
+ // Stop using SIGTERM by default
+ // Use SIGSTOP after a timeout
+ logrus.Debugf("Killing all processes in container %s with SIGTERM", ctr.ID())
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "TERM"); err != nil {
+ return errors.Wrapf(err, "error sending SIGTERM to container %s processes", ctr.ID())
+ }
+
+ // Wait for all processes to stop
+ if err := waitPidsStop(execSessions, time.Duration(timeout)*time.Second); err != nil {
+ logrus.Warnf("Timed out stopping container %s exec sessions", ctr.ID())
+ } else {
+ // No error, all exec sessions are dead
+ return nil
+ }
+ }
+
+ // Send SIGKILL
+ logrus.Debugf("Killing all processes in container %s with SIGKILL", ctr.ID())
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "KILL"); err != nil {
+ return errors.Wrapf(err, "error sending SIGKILL to container %s processes", ctr.ID())
+ }
+
+ // Give the processes a few seconds to go down
+ if err := waitPidsStop(execSessions, killContainerTimeout); err != nil {
+ return errors.Wrapf(err, "failed to kill container %s exec sessions", ctr.ID())
+ }
+
+ return nil
+}
diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go
index 8c084d1e2..12183faf3 100644
--- a/libpod/oci_unsupported.go
+++ b/libpod/oci_unsupported.go
@@ -26,3 +26,15 @@ func (r *OCIRuntime) pathPackage() string {
func (r *OCIRuntime) conmonPackage() string {
return ""
}
+
+func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
+ return ErrOSNotSupported
+}
+
+func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
+ return ErrOSNotSupported
+}
+
+func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error {
+ return ErrOSNotSupported
+}
diff --git a/libpod/options.go b/libpod/options.go
index dde9bc64c..86c04db09 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1,7 +1,6 @@
package libpod
import (
- "context"
"net"
"os"
"path/filepath"
@@ -438,10 +437,9 @@ func WithRenumber() RuntimeOption {
}
}
-// WithMigrate instructs libpod to perform a lock migrateing while
-// initializing. This will handle migrations from early versions of libpod with
-// file locks to newer versions with SHM locking, as well as changes in the
-// number of configured locks.
+// WithMigrate instructs libpod to migrate container configurations to account
+// for changes between Libpod versions. All running containers will be stopped
+// during a migration, then restarted after the migration is complete.
func WithMigrate() RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
@@ -468,19 +466,6 @@ func WithShmDir(dir string) CtrCreateOption {
}
}
-// WithContext sets the context to use.
-func WithContext(ctx context.Context) RuntimeOption {
- return func(rt *Runtime) error {
- if rt.valid {
- return ErrRuntimeFinalized
- }
-
- rt.ctx = ctx
-
- return nil
- }
-}
-
// WithSystemd turns on systemd mode in the container
func WithSystemd() CtrCreateOption {
return func(ctr *Container) error {
diff --git a/libpod/runtime.go b/libpod/runtime.go
index e85242028..6b8d97fd9 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -112,8 +112,6 @@ type Runtime struct {
// mechanism to read and write even logs
eventer events.Eventer
-
- ctx context.Context
}
// OCIRuntimePath contains information about an OCI runtime.
@@ -353,8 +351,8 @@ func SetXdgRuntimeDir(val string) error {
// NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
-func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
- return newRuntimeFromConfig("", options...)
+func NewRuntime(ctx context.Context, options ...RuntimeOption) (runtime *Runtime, err error) {
+ return newRuntimeFromConfig(ctx, "", options...)
}
// NewRuntimeFromConfig creates a new container runtime using the given
@@ -362,14 +360,14 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
// functions can be used to mutate this configuration further.
// An error will be returned if the configuration file at the given path does
// not exist or cannot be loaded
-func NewRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
+func NewRuntimeFromConfig(ctx context.Context, userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
if userConfigPath == "" {
return nil, errors.New("invalid configuration file specified")
}
- return newRuntimeFromConfig(userConfigPath, options...)
+ return newRuntimeFromConfig(ctx, userConfigPath, options...)
}
-func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
+func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
runtime = new(Runtime)
runtime.config = new(RuntimeConfig)
runtime.configuredFrom = new(runtimeConfiguredFrom)
@@ -563,7 +561,7 @@ func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runt
}
}
}
- if err := makeRuntime(runtime); err != nil {
+ if err := makeRuntime(ctx, runtime); err != nil {
return nil, err
}
return runtime, nil
@@ -571,7 +569,7 @@ func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runt
// Make a new runtime based on the given configuration
// Sets up containers/storage, state store, OCI runtime
-func makeRuntime(runtime *Runtime) (err error) {
+func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// Backward compatibility for `runtime_path`
if runtime.config.RuntimePath != nil {
// Don't print twice in rootless mode.
@@ -980,7 +978,7 @@ func makeRuntime(runtime *Runtime) (err error) {
os.Exit(ret)
}
}
- if err := runtime.migrate(); err != nil {
+ if err := runtime.migrate(ctx); err != nil {
return err
}
}
diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go
index a084df289..0bb8e952f 100644
--- a/libpod/runtime_migrate.go
+++ b/libpod/runtime_migrate.go
@@ -1,13 +1,14 @@
package libpod
import (
+ "context"
"path/filepath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-func (r *Runtime) migrate() error {
+func (r *Runtime) migrate(ctx context.Context) error {
runningContainers, err := r.GetRunningContainers()
if err != nil {
return err
@@ -38,7 +39,7 @@ func (r *Runtime) migrate() error {
}
for _, ctr := range runningContainers {
- if err := ctr.Start(r.ctx, true); err != nil {
+ if err := ctr.Start(ctx, true); err != nil {
logrus.Errorf("error restarting container %s", ctr.ID())
}
}
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 9ec897a60..d575bc9b0 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter/shortcuts"
+ "github.com/containers/libpod/pkg/systemdgen"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -133,6 +134,43 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
return pool.Run()
}
+// InitContainers initializes container(s) based on CLI inputs.
+// Returns list of successful id(s), map of failed id(s) to errors, or a general
+// error not from the container.
+func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) {
+ maxWorkers := shared.DefaultPoolSize("init")
+ if cli.GlobalIsSet("max-workers") {
+ maxWorkers = cli.GlobalFlags.MaxWorks
+ }
+ logrus.Debugf("Setting maximum init workers to %d", maxWorkers)
+
+ ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pool := shared.NewPool("init", maxWorkers, len(ctrs))
+ for _, c := range ctrs {
+ ctr := c
+
+ pool.Add(shared.Job{
+ ctr.ID(),
+ func() error {
+ err := ctr.Init(ctx)
+ if err != nil {
+ // If we're initializing all containers, ignore invalid state errors
+ if cli.All && errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ return nil
+ }
+ return err
+ }
+ return nil
+ },
+ })
+ }
+ return pool.Run()
+}
+
// RemoveContainers removes container(s) based on CLI inputs.
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
var (
@@ -876,3 +914,47 @@ func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *Local
}
return nil
}
+
+// Port displays port information about existing containers
+func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
+ var (
+ portContainers []*Container
+ containers []*libpod.Container
+ err error
+ )
+
+ if !c.All {
+ containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
+ } else {
+ containers, err = r.Runtime.GetRunningContainers()
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ //Convert libpod containers to adapter Containers
+ for _, con := range containers {
+ if state, _ := con.State(); state != libpod.ContainerStateRunning {
+ continue
+ }
+ portContainers = append(portContainers, &Container{con})
+ }
+ return portContainers, nil
+}
+
+// GenerateSystemd creates a unit file for a container
+func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ ctr, err := r.Runtime.LookupContainer(c.InputArgs[0])
+ if err != nil {
+ return "", err
+ }
+ timeout := int(ctr.StopTimeout())
+ if c.StopTimeout >= 0 {
+ timeout = int(c.StopTimeout)
+ }
+ name := ctr.ID()
+ if c.Name {
+ name = ctr.Name()
+ }
+ return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, ctr.Config().StaticDir, timeout)
+}
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go
index a3a48a564..201249fc3 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
+ "github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/docker/pkg/term"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
@@ -63,6 +64,19 @@ func (c *Container) Unpause() error {
return err
}
+func (c *Container) PortMappings() ([]ocicni.PortMapping, error) {
+ // First check if the container belongs to a network namespace (like a pod)
+ // Taken from libpod portmappings()
+ if len(c.config.NetNsCtr) > 0 {
+ netNsCtr, err := c.Runtime.LookupContainer(c.config.NetNsCtr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to lookup network namespace for container %s", c.ID())
+ }
+ return netNsCtr.PortMappings()
+ }
+ return c.config.PortMappings, nil
+}
+
// Config returns a container config
func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig {
// TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer
@@ -234,6 +248,40 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa
return ok, failures, nil
}
+// InitContainers initializes container(s) based on Varlink.
+// It returns a list of successful ID(s), a map of failed container ID to error,
+// or an error if a more general error occurred.
+func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) {
+ var (
+ ok = []string{}
+ failures = map[string]error{}
+ )
+
+ ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, id := range ids {
+ initialized, err := iopodman.InitContainer().Call(r.Conn, id)
+ if err != nil {
+ if cli.All {
+ switch err.(type) {
+ case *iopodman.InvalidState:
+ ok = append(ok, initialized)
+ default:
+ failures[id] = err
+ }
+ } else {
+ failures[id] = err
+ }
+ } else {
+ ok = append(ok, initialized)
+ }
+ }
+ return ok, failures, nil
+}
+
// KillContainers sends signal to container(s) based on varlink.
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) {
@@ -888,3 +936,28 @@ func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([
func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) {
return nil, nil, errors.New("container cleanup not supported for remote clients")
}
+
+// Port displays port information about existing containers
+func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
+ var (
+ containers []*Container
+ err error
+ )
+ // This one is a bit odd because when all is used, we only use running containers.
+ if !c.All {
+ containers, err = r.GetContainersByContext(false, c.Latest, c.InputArgs)
+ } else {
+ // we need to only use running containers if all
+ filters := []string{libpod.ContainerStateRunning.String()}
+ containers, err = r.LookupContainersWithStatus(filters)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return containers, nil
+}
+
+// GenerateSystemd creates a systemd until for a container
+func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ return iopodman.GenerateSystemd().Call(r.Conn, c.InputArgs[0], c.RestartPolicy, int64(c.StopTimeout), c.Name)
+}
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 6102daccf..4986d16f7 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -889,3 +889,20 @@ func (r *LocalRuntime) GenerateKube(c *cliconfig.GenerateKubeValues) (*v1.Pod, *
err = json.Unmarshal([]byte(reply.Service), &service)
return &pod, &service, err
}
+
+// GetContainersByContext looks up containers based on the cli input of all, latest, or a list
+func (r *LocalRuntime) GetContainersByContext(all bool, latest bool, namesOrIDs []string) ([]*Container, error) {
+ var containers []*Container
+ cids, err := iopodman.GetContainersByContext().Call(r.Conn, all, latest, namesOrIDs)
+ if err != nil {
+ return nil, err
+ }
+ for _, cid := range cids {
+ ctr, err := r.LookupContainer(cid)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, ctr)
+ }
+ return containers, nil
+}
diff --git a/pkg/adapter/runtime_remote_supported.go b/pkg/adapter/runtime_remote_supported.go
new file mode 100644
index 000000000..b8e8da308
--- /dev/null
+++ b/pkg/adapter/runtime_remote_supported.go
@@ -0,0 +1 @@
+package adapter
diff --git a/pkg/adapter/sigproxy.go b/pkg/adapter/sigproxy_linux.go
index af968cb89..af968cb89 100644
--- a/pkg/adapter/sigproxy.go
+++ b/pkg/adapter/sigproxy_linux.go
diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go
index 0b608decf..373c78322 100644
--- a/pkg/adapter/terminal.go
+++ b/pkg/adapter/terminal.go
@@ -2,16 +2,12 @@ package adapter
import (
"context"
- "fmt"
"os"
gosignal "os/signal"
- "github.com/containers/libpod/libpod"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/remotecommand"
)
@@ -19,83 +15,6 @@ import (
type RawTtyFormatter struct {
}
-// StartAttachCtr starts and (if required) attaches to a container
-func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error {
- resize := make(chan remotecommand.TerminalSize)
-
- haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
-
- // Check if we are attached to a terminal. If we are, generate resize
- // events, and set the terminal to raw mode
- if haveTerminal && ctr.Spec().Process.Terminal {
- logrus.Debugf("Handling terminal attach")
-
- subCtx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- resizeTty(subCtx, resize)
-
- oldTermState, err := term.SaveState(os.Stdin.Fd())
- if err != nil {
- return errors.Wrapf(err, "unable to save terminal state")
- }
-
- logrus.SetFormatter(&RawTtyFormatter{})
- term.SetRawTerminal(os.Stdin.Fd())
-
- defer restoreTerminal(oldTermState)
- }
-
- streams := new(libpod.AttachStreams)
- streams.OutputStream = stdout
- streams.ErrorStream = stderr
- streams.InputStream = stdin
- streams.AttachOutput = true
- streams.AttachError = true
- streams.AttachInput = true
-
- if stdout == nil {
- logrus.Debugf("Not attaching to stdout")
- streams.AttachOutput = false
- }
- if stderr == nil {
- logrus.Debugf("Not attaching to stderr")
- streams.AttachError = false
- }
- if stdin == nil {
- logrus.Debugf("Not attaching to stdin")
- streams.AttachInput = false
- }
-
- if !startContainer {
- if sigProxy {
- ProxySignals(ctr)
- }
-
- return ctr.Attach(streams, detachKeys, resize)
- }
-
- attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive)
- if err != nil {
- return err
- }
-
- if sigProxy {
- ProxySignals(ctr)
- }
-
- if stdout == nil && stderr == nil {
- fmt.Printf("%s\n", ctr.ID())
- }
-
- err = <-attachChan
- if err != nil {
- return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
- }
-
- return nil
-}
-
// getResize returns a TerminalSize command matching stdin's current
// size on success, and nil on errors.
func getResize() *remotecommand.TerminalSize {
diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go
new file mode 100644
index 000000000..3c4c3bd38
--- /dev/null
+++ b/pkg/adapter/terminal_linux.go
@@ -0,0 +1,91 @@
+package adapter
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/docker/docker/pkg/term"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+// StartAttachCtr starts and (if required) attaches to a container
+func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error {
+ resize := make(chan remotecommand.TerminalSize)
+
+ haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
+
+ // Check if we are attached to a terminal. If we are, generate resize
+ // events, and set the terminal to raw mode
+ if haveTerminal && ctr.Spec().Process.Terminal {
+ logrus.Debugf("Handling terminal attach")
+
+ subCtx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ resizeTty(subCtx, resize)
+
+ oldTermState, err := term.SaveState(os.Stdin.Fd())
+ if err != nil {
+ return errors.Wrapf(err, "unable to save terminal state")
+ }
+
+ logrus.SetFormatter(&RawTtyFormatter{})
+ term.SetRawTerminal(os.Stdin.Fd())
+
+ defer restoreTerminal(oldTermState)
+ }
+
+ streams := new(libpod.AttachStreams)
+ streams.OutputStream = stdout
+ streams.ErrorStream = stderr
+ streams.InputStream = stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ if stdout == nil {
+ logrus.Debugf("Not attaching to stdout")
+ streams.AttachOutput = false
+ }
+ if stderr == nil {
+ logrus.Debugf("Not attaching to stderr")
+ streams.AttachError = false
+ }
+ if stdin == nil {
+ logrus.Debugf("Not attaching to stdin")
+ streams.AttachInput = false
+ }
+
+ if !startContainer {
+ if sigProxy {
+ ProxySignals(ctr)
+ }
+
+ return ctr.Attach(streams, detachKeys, resize)
+ }
+
+ attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive)
+ if err != nil {
+ return err
+ }
+
+ if sigProxy {
+ ProxySignals(ctr)
+ }
+
+ if stdout == nil && stderr == nil {
+ fmt.Printf("%s\n", ctr.ID())
+ }
+
+ err = <-attachChan
+ if err != nil {
+ return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
+ }
+
+ return nil
+}
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index a1873086e..eb2acf984 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -244,3 +244,9 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
}
return ltds, nil
}
+
+func getStatFromPath(path string) (unix.Stat_t, error) {
+ s := unix.Stat_t{}
+ err := unix.Stat(path, &s)
+ return s, err
+}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 4d2d8304c..90e7accf3 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -17,7 +17,6 @@ import (
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "golang.org/x/sys/unix"
)
// Type constants
@@ -411,9 +410,3 @@ func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) {
func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error {
return c.addPrivilegedDevices(g)
}
-
-func getStatFromPath(path string) (unix.Stat_t, error) {
- s := unix.Stat_t{}
- err := unix.Stat(path, &s)
- return s, err
-}
diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go
new file mode 100644
index 000000000..3d1c31b5d
--- /dev/null
+++ b/pkg/systemdgen/systemdgen.go
@@ -0,0 +1,43 @@
+package systemdgen
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+)
+
+var template = `[Unit]
+Description=%s Podman Container
+[Service]
+Restart=%s
+ExecStart=/usr/bin/podman start %s
+ExecStop=/usr/bin/podman stop -t %d %s
+KillMode=none
+Type=forking
+PIDFile=%s
+[Install]
+WantedBy=multi-user.target`
+
+var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
+
+// ValidateRestartPolicy checks that the user-provided policy is valid
+func ValidateRestartPolicy(restart string) error {
+ for _, i := range restartPolicies {
+ if i == restart {
+ return nil
+ }
+ }
+ return errors.Errorf("%s is not a valid restart policy", restart)
+}
+
+// CreateSystemdUnitAsString takes variables to create a systemd unit file used to control
+// a libpod container
+func CreateSystemdUnitAsString(name, cid, restart, pidPath string, stopTimeout int) (string, error) {
+ if err := ValidateRestartPolicy(restart); err != nil {
+ return "", err
+ }
+ pidFile := filepath.Join(pidPath, fmt.Sprintf("%s.pid", cid))
+ unit := fmt.Sprintf(template, name, restart, name, stopTimeout, name, pidFile)
+ return unit, nil
+}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 136f8fadd..14b0c2b55 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -6,12 +6,10 @@ import (
"path/filepath"
"strings"
"sync"
- "syscall"
"time"
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
- "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1"
@@ -187,76 +185,6 @@ var (
rootlessRuntimeDir string
)
-// GetRootlessRuntimeDir returns the runtime directory when running as non root
-func GetRootlessRuntimeDir() (string, error) {
- var rootlessRuntimeDirError error
-
- rootlessRuntimeDirOnce.Do(func() {
- runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
- uid := fmt.Sprintf("%d", rootless.GetRootlessUID())
- if runtimeDir == "" {
- tmpDir := filepath.Join("/run", "user", uid)
- os.MkdirAll(tmpDir, 0700)
- st, err := os.Stat(tmpDir)
- if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
- runtimeDir = tmpDir
- }
- }
- if runtimeDir == "" {
- tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid))
- os.MkdirAll(tmpDir, 0700)
- st, err := os.Stat(tmpDir)
- if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
- runtimeDir = tmpDir
- }
- }
- if runtimeDir == "" {
- home := os.Getenv("HOME")
- if home == "" {
- rootlessRuntimeDirError = fmt.Errorf("neither XDG_RUNTIME_DIR nor HOME was set non-empty")
- return
- }
- resolvedHome, err := filepath.EvalSymlinks(home)
- if err != nil {
- rootlessRuntimeDirError = errors.Wrapf(err, "cannot resolve %s", home)
- return
- }
- runtimeDir = filepath.Join(resolvedHome, "rundir")
- }
- rootlessRuntimeDir = runtimeDir
- })
-
- if rootlessRuntimeDirError != nil {
- return "", rootlessRuntimeDirError
- }
- return rootlessRuntimeDir, nil
-}
-
-// GetRootlessDirInfo returns the parent path of where the storage for containers and
-// volumes will be in rootless mode
-func GetRootlessDirInfo() (string, string, error) {
- rootlessRuntime, err := GetRootlessRuntimeDir()
- if err != nil {
- return "", "", err
- }
-
- dataDir := os.Getenv("XDG_DATA_HOME")
- if dataDir == "" {
- home := os.Getenv("HOME")
- if home == "" {
- return "", "", fmt.Errorf("neither XDG_DATA_HOME nor HOME was set non-empty")
- }
- // runc doesn't like symlinks in the rootfs path, and at least
- // on CoreOS /home is a symlink to /var/home, so resolve any symlink.
- resolvedHome, err := filepath.EvalSymlinks(home)
- if err != nil {
- return "", "", errors.Wrapf(err, "cannot resolve %s", home)
- }
- dataDir = filepath.Join(resolvedHome, ".local", "share")
- }
- return dataDir, rootlessRuntime, nil
-}
-
type tomlOptionsConfig struct {
MountProgram string `toml:"mount_program"`
}
diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go
new file mode 100644
index 000000000..af5e67fc1
--- /dev/null
+++ b/pkg/util/utils_supported.go
@@ -0,0 +1,60 @@
+// +build linux darwin
+
+package util
+
+// TODO once rootless function is consolidated under libpod, we
+// should work to take darwin from this
+
+import (
+ "fmt"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// GetRootlessRuntimeDir returns the runtime directory when running as non root
+func GetRootlessRuntimeDir() (string, error) {
+ var rootlessRuntimeDirError error
+
+ rootlessRuntimeDirOnce.Do(func() {
+ runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
+ uid := fmt.Sprintf("%d", rootless.GetRootlessUID())
+ if runtimeDir == "" {
+ tmpDir := filepath.Join("/run", "user", uid)
+ os.MkdirAll(tmpDir, 0700)
+ st, err := os.Stat(tmpDir)
+ if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
+ runtimeDir = tmpDir
+ }
+ }
+ if runtimeDir == "" {
+ tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid))
+ os.MkdirAll(tmpDir, 0700)
+ st, err := os.Stat(tmpDir)
+ if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
+ runtimeDir = tmpDir
+ }
+ }
+ if runtimeDir == "" {
+ home := os.Getenv("HOME")
+ if home == "" {
+ rootlessRuntimeDirError = fmt.Errorf("neither XDG_RUNTIME_DIR nor HOME was set non-empty")
+ return
+ }
+ resolvedHome, err := filepath.EvalSymlinks(home)
+ if err != nil {
+ rootlessRuntimeDirError = errors.Wrapf(err, "cannot resolve %s", home)
+ return
+ }
+ runtimeDir = filepath.Join(resolvedHome, "rundir")
+ }
+ rootlessRuntimeDir = runtimeDir
+ })
+
+ if rootlessRuntimeDirError != nil {
+ return "", rootlessRuntimeDirError
+ }
+ return rootlessRuntimeDir, nil
+}
diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go
new file mode 100644
index 000000000..1e9ccea90
--- /dev/null
+++ b/pkg/util/utils_windows.go
@@ -0,0 +1,12 @@
+// +build windows
+
+package util
+
+import (
+ "github.com/pkg/errors"
+)
+
+// GetRootlessRuntimeDir returns the runtime directory when running as non root
+func GetRootlessRuntimeDir() (string, error) {
+ return "", errors.New("this function is not implemented for windows")
+}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 872c7bc26..c8be41636 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -365,6 +365,21 @@ func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error
return call.ReplyStartContainer(ctr.ID())
}
+// InitContainer initializes the container given by Varlink.
+func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error {
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyContainerNotFound(name, err.Error())
+ }
+ if err := ctr.Init(getContext()); err != nil {
+ if errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ return call.ReplyInvalidState(ctr.ID(), err.Error())
+ }
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyInitContainer(ctr.ID())
+}
+
// StopContainer ...
func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error {
ctr, err := i.Runtime.LookupContainer(name)
diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go
index bc600c397..9dc20d582 100644
--- a/pkg/varlinkapi/generate.go
+++ b/pkg/varlinkapi/generate.go
@@ -6,6 +6,7 @@ import (
"encoding/json"
"github.com/containers/libpod/cmd/podman/shared"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
+ "github.com/containers/libpod/pkg/systemdgen"
)
// GenerateKube ...
@@ -28,3 +29,24 @@ func (i *LibpodAPI) GenerateKube(call iopodman.VarlinkCall, name string, service
Service: string(servB),
})
}
+
+// GenerateSystemd ...
+func (i *LibpodAPI) GenerateSystemd(call iopodman.VarlinkCall, nameOrID, restart string, stopTimeout int64, useName bool) error {
+ ctr, err := i.Runtime.LookupContainer(nameOrID)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ timeout := int(ctr.StopTimeout())
+ if stopTimeout >= 0 {
+ timeout = int(stopTimeout)
+ }
+ name := ctr.ID()
+ if useName {
+ name = ctr.Name()
+ }
+ unit, err := systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), restart, ctr.Config().StaticDir, timeout)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyGenerateSystemd(unit)
+}
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
new file mode 100644
index 000000000..940e894bc
--- /dev/null
+++ b/test/e2e/generate_systemd_test.go
@@ -0,0 +1,74 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "os"
+)
+
+var _ = Describe("Podman generate systemd", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman generate systemd on bogus container", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd bad restart policy", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd bad timeout value", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "-1", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd", func() {
+ n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "nginx"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman generate systemd with timeout", func() {
+ n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "5", "nginx"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+})
diff --git a/test/e2e/init_test.go b/test/e2e/init_test.go
new file mode 100644
index 000000000..5865930a5
--- /dev/null
+++ b/test/e2e/init_test.go
@@ -0,0 +1,129 @@
+package integration
+
+import (
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman init", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman init bogus container", func() {
+ session := podmanTest.Podman([]string{"start", "123456"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
+ It("podman init with no arguments", func() {
+ session := podmanTest.Podman([]string{"start"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
+ It("podman init single container by ID", func() {
+ session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+ init := podmanTest.Podman([]string{"init", cid})
+ init.WaitWithDefaultTimeout()
+ Expect(init.ExitCode()).To(Equal(0))
+ result := podmanTest.Podman([]string{"inspect", cid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ conData := result.InspectContainerToJSON()
+ Expect(conData[0].State.Status).To(Equal("created"))
+ })
+
+ It("podman init single container by name", func() {
+ name := "test1"
+ session := podmanTest.Podman([]string{"create", "--name", name, "-d", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ init := podmanTest.Podman([]string{"init", name})
+ init.WaitWithDefaultTimeout()
+ Expect(init.ExitCode()).To(Equal(0))
+ result := podmanTest.Podman([]string{"inspect", name})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ conData := result.InspectContainerToJSON()
+ Expect(conData[0].State.Status).To(Equal("created"))
+ })
+
+ It("podman init latest container", func() {
+ session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ init := podmanTest.Podman([]string{"init", "--latest"})
+ init.WaitWithDefaultTimeout()
+ Expect(init.ExitCode()).To(Equal(0))
+ result := podmanTest.Podman([]string{"inspect", "--latest"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ conData := result.InspectContainerToJSON()
+ Expect(conData[0].State.Status).To(Equal("created"))
+ })
+
+ It("podman init all three containers, one running", func() {
+ session := podmanTest.Podman([]string{"create", "--name", "test1", "-d", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session2 := podmanTest.Podman([]string{"create", "--name", "test2", "-d", ALPINE, "ls"})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2.ExitCode()).To(Equal(0))
+ session3 := podmanTest.Podman([]string{"run", "--name", "test3", "-d", ALPINE, "top"})
+ session3.WaitWithDefaultTimeout()
+ Expect(session3.ExitCode()).To(Equal(0))
+ init := podmanTest.Podman([]string{"init", "--all"})
+ init.WaitWithDefaultTimeout()
+ Expect(init.ExitCode()).To(Equal(0))
+ result := podmanTest.Podman([]string{"inspect", "test1"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ conData := result.InspectContainerToJSON()
+ Expect(conData[0].State.Status).To(Equal("created"))
+ result2 := podmanTest.Podman([]string{"inspect", "test2"})
+ result2.WaitWithDefaultTimeout()
+ Expect(result2.ExitCode()).To(Equal(0))
+ conData2 := result2.InspectContainerToJSON()
+ Expect(conData2[0].State.Status).To(Equal("created"))
+ result3 := podmanTest.Podman([]string{"inspect", "test3"})
+ result3.WaitWithDefaultTimeout()
+ Expect(result3.ExitCode()).To(Equal(0))
+ conData3 := result3.InspectContainerToJSON()
+ Expect(conData3[0].State.Status).To(Equal("running"))
+ })
+
+ It("podman init running container errors", func() {
+ session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ init := podmanTest.Podman([]string{"init", "--latest"})
+ init.WaitWithDefaultTimeout()
+ Expect(init.ExitCode()).To(Equal(125))
+ })
+})
diff --git a/utils/utils.go b/utils/utils.go
index c195daa5d..86adfb967 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -9,8 +9,6 @@ import (
"strings"
"github.com/containers/storage/pkg/archive"
- systemdDbus "github.com/coreos/go-systemd/dbus"
- "github.com/godbus/dbus"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -55,37 +53,6 @@ func StatusToExitCode(status int) int {
return ((status) & 0xff00) >> 8
}
-// RunUnderSystemdScope adds the specified pid to a systemd scope
-func RunUnderSystemdScope(pid int, slice string, unitName string) error {
- var properties []systemdDbus.Property
- conn, err := systemdDbus.New()
- if err != nil {
- return err
- }
- properties = append(properties, systemdDbus.PropSlice(slice))
- properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
- properties = append(properties, newProp("Delegate", true))
- properties = append(properties, newProp("DefaultDependencies", false))
- ch := make(chan string)
- _, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
- if err != nil {
- return err
- }
- defer conn.Close()
-
- // Block until job is started
- <-ch
-
- return nil
-}
-
-func newProp(name string, units interface{}) systemdDbus.Property {
- return systemdDbus.Property{
- Name: name,
- Value: dbus.MakeVariant(units),
- }
-}
-
// ErrDetach is an error indicating that the user manually detached from the
// container.
var ErrDetach = errors.New("detached from container")
diff --git a/utils/utils_supported.go b/utils/utils_supported.go
new file mode 100644
index 000000000..8b0ba4438
--- /dev/null
+++ b/utils/utils_supported.go
@@ -0,0 +1,39 @@
+// +build linux darwin
+
+package utils
+
+import (
+ systemdDbus "github.com/coreos/go-systemd/dbus"
+ "github.com/godbus/dbus"
+)
+
+// RunUnderSystemdScope adds the specified pid to a systemd scope
+func RunUnderSystemdScope(pid int, slice string, unitName string) error {
+ var properties []systemdDbus.Property
+ conn, err := systemdDbus.New()
+ if err != nil {
+ return err
+ }
+ properties = append(properties, systemdDbus.PropSlice(slice))
+ properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
+ properties = append(properties, newProp("Delegate", true))
+ properties = append(properties, newProp("DefaultDependencies", false))
+ ch := make(chan string)
+ _, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ // Block until job is started
+ <-ch
+
+ return nil
+}
+
+func newProp(name string, units interface{}) systemdDbus.Property {
+ return systemdDbus.Property{
+ Name: name,
+ Value: dbus.MakeVariant(units),
+ }
+}
diff --git a/utils/utils_windows.go b/utils/utils_windows.go
new file mode 100644
index 000000000..db27877d9
--- /dev/null
+++ b/utils/utils_windows.go
@@ -0,0 +1,9 @@
+// +build windows
+
+package utils
+
+import "github.com/pkg/errors"
+
+func RunUnderSystemdScope(pid int, slice string, unitName string) error {
+ return errors.New("not implemented for windows")
+}
diff --git a/vendor.conf b/vendor.conf
index 02283beb9..c99b2c1d7 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -19,7 +19,7 @@ github.com/containers/image v1.5.1
github.com/vbauerster/mpb v3.3.4
github.com/mattn/go-isatty v0.0.4
github.com/VividCortex/ewma v1.1.1
-github.com/containers/storage v1.12.5
+github.com/containers/storage v1.12.6
github.com/containers/psgo v1.2.1
github.com/coreos/go-systemd v14
github.com/coreos/pkg v4
@@ -93,12 +93,12 @@ k8s.io/api kubernetes-1.10.13-beta.0 https://github.com/kubernetes/api
k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apimachinery
k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go
github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7
-github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199
-github.com/containers/buildah v1.8.0
+github.com/varlink/go 64e07fabffa33e385817b41971cf2674f692f391
+github.com/containers/buildah v1.8.1
# TODO: Gotty has not been updated since 2012. Can we find replacement?
github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512
github.com/fsouza/go-dockerclient v1.3.0
-github.com/openshift/imagebuilder 705fe9255c57f8505efb9723a9ac4082b67973bc
+github.com/openshift/imagebuilder v1.1.0
github.com/ulikunitz/xz v0.5.5
github.com/coreos/go-iptables v0.4.0
github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe
diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go
index b6e6545ec..13526057c 100644
--- a/vendor/github.com/containers/buildah/buildah.go
+++ b/vendor/github.com/containers/buildah/buildah.go
@@ -26,7 +26,7 @@ const (
Package = "buildah"
// Version for the Package. Bump version in contrib/rpm/buildah.spec
// too.
- Version = "1.8.0"
+ Version = "1.8.1"
// The value we use to identify what type of information, currently a
// serialized Builder structure, we are using as per-container state.
// This should only be changed when we make incompatible changes to
diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go
index d9909cdc8..85848e297 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/build.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/build.go
@@ -1558,6 +1558,9 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
// stages.
for i := range cleanupImages {
removeID := cleanupImages[len(cleanupImages)-i-1]
+ if removeID == imageID {
+ continue
+ }
if _, err := b.store.DeleteImage(removeID, true); err != nil {
logrus.Debugf("failed to remove intermediate image %q: %v", removeID, err)
if b.forceRmIntermediateCtrs || errors.Cause(err) != storage.ErrImageUsedByContainer {
@@ -1663,6 +1666,7 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
if !b.layers {
cleanupImages = append(cleanupImages, imageID)
}
+ imageID = ""
}
}
@@ -1812,9 +1816,10 @@ func (b *Executor) deleteSuccessfulIntermediateCtrs() error {
}
func (s *StageExecutor) EnsureContainerPath(path string) error {
- _, err := os.Stat(filepath.Join(s.mountPoint, path))
+ targetPath := filepath.Join(s.mountPoint, path)
+ _, err := os.Lstat(targetPath)
if err != nil && os.IsNotExist(err) {
- err = os.MkdirAll(filepath.Join(s.mountPoint, path), 0755)
+ err = os.MkdirAll(targetPath, 0755)
}
if err != nil {
return errors.Wrapf(err, "error ensuring container path %q", path)
diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go
index 0789c2b3c..e9d745b67 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go
@@ -24,6 +24,22 @@ func init() {
reexec.Register(symlinkModifiedTime, resolveSymlinkTimeModified)
}
+// resolveSymlink uses a child subprocess to resolve any symlinks in filename
+// in the context of rootdir.
+func resolveSymlink(rootdir, filename string) (string, error) {
+ // The child process expects a chroot and one path that
+ // will be consulted relative to the chroot directory and evaluated
+ // for any symbolic links present.
+ cmd := reexec.Command(symlinkChrootedCommand, rootdir, filename)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", errors.Wrapf(err, string(output))
+ }
+
+ // Hand back the resolved symlink, will be filename if a symlink is not found
+ return string(output), nil
+}
+
// main() for resolveSymlink()'s subprocess.
func resolveChrootedSymlinks() {
status := 0
@@ -55,22 +71,6 @@ func resolveChrootedSymlinks() {
os.Exit(status)
}
-// resolveSymlink uses a child subprocess to resolve any symlinks in filename
-// in the context of rootdir.
-func resolveSymlink(rootdir, filename string) (string, error) {
- // The child process expects a chroot and one path that
- // will be consulted relative to the chroot directory and evaluated
- // for any symbolic links present.
- cmd := reexec.Command(symlinkChrootedCommand, rootdir, filename)
- output, err := cmd.CombinedOutput()
- if err != nil {
- return "", errors.Wrapf(err, string(output))
- }
-
- // Hand back the resolved symlink, will be filename if a symlink is not found
- return string(output), nil
-}
-
// main() for grandparent subprocess. Its main job is to shuttle stdio back
// and forth, managing a pseudo-terminal if we want one, for our child, the
// parent subprocess.
diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go
new file mode 100644
index 000000000..2cec4fe21
--- /dev/null
+++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go
@@ -0,0 +1,13 @@
+// +build !linux
+
+package imagebuildah
+
+import "github.com/pkg/errors"
+
+func resolveSymlink(rootdir, filename string) (string, error) {
+ return "", errors.New("function not supported on non-linux systems")
+}
+
+func resolveModifiedTime(rootdir, filename, historyTime string) (bool, error) {
+ return false, errors.New("function not supported on non-linux systems")
+}
diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go
index 7fa0a7777..e7a571db6 100644
--- a/vendor/github.com/containers/buildah/pkg/cli/common.go
+++ b/vendor/github.com/containers/buildah/pkg/cli/common.go
@@ -96,7 +96,7 @@ type FromAndBudResults struct {
SecurityOpt []string
ShmSize string
Ulimit []string
- Volume []string
+ Volumes []string
}
// GetUserNSFlags returns the common flags for usernamespace
@@ -190,7 +190,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults,
fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])")
fs.StringVar(&flags.ShmSize, "shm-size", "65536k", "size of '/dev/shm'. The format is `<number><unit>`.")
fs.StringSliceVar(&flags.Ulimit, "ulimit", []string{}, "ulimit options (default [])")
- fs.StringSliceVarP(&flags.Volume, "volume", "v", []string{}, "bind mount a volume into the container (default [])")
+ fs.StringSliceVarP(&flags.Volumes, "volume", "v", []string{}, "bind mount a volume into the container (default [])")
// Add in the usernamespace and namespaceflags
usernsFlags := GetUserNSFlags(usernsResults)
diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go
index cc85136fd..e8517eafb 100644
--- a/vendor/github.com/containers/buildah/pkg/parse/parse.go
+++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go
@@ -22,7 +22,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
- "golang.org/x/sys/unix"
)
const (
@@ -39,14 +38,9 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) {
memorySwap int64
err error
)
- rlim := unix.Rlimit{Cur: 1048576, Max: 1048576}
- defaultLimits := []string{}
- if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rlim); err == nil {
- defaultLimits = append(defaultLimits, fmt.Sprintf("nofile=%d:%d", rlim.Cur, rlim.Max))
- }
- if err := unix.Setrlimit(unix.RLIMIT_NPROC, &rlim); err == nil {
- defaultLimits = append(defaultLimits, fmt.Sprintf("nproc=%d:%d", rlim.Cur, rlim.Max))
- }
+
+ defaultLimits := getDefaultProcessLimits()
+
memVal, _ := c.Flags().GetString("memory")
if memVal != "" {
memoryLimit, err = units.RAMInBytes(memVal)
@@ -155,27 +149,42 @@ func parseSecurityOpts(securityOpts []string, commonOpts *buildah.CommonBuildOpt
return nil
}
+func ParseVolume(volume string) (specs.Mount, error) {
+ mount := specs.Mount{}
+ arr := strings.SplitN(volume, ":", 3)
+ if len(arr) < 2 {
+ return mount, errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume)
+ }
+ if err := validateVolumeHostDir(arr[0]); err != nil {
+ return mount, err
+ }
+ if err := validateVolumeCtrDir(arr[1]); err != nil {
+ return mount, err
+ }
+ mountOptions := ""
+ if len(arr) > 2 {
+ mountOptions = arr[2]
+ if err := validateVolumeOpts(arr[2]); err != nil {
+ return mount, err
+ }
+ }
+ mountOpts := strings.Split(mountOptions, ",")
+ mount.Source = arr[0]
+ mount.Destination = arr[1]
+ mount.Type = "rbind"
+ mount.Options = mountOpts
+ return mount, nil
+}
+
// ParseVolumes validates the host and container paths passed in to the --volume flag
func ParseVolumes(volumes []string) error {
if len(volumes) == 0 {
return nil
}
for _, volume := range volumes {
- arr := strings.SplitN(volume, ":", 3)
- if len(arr) < 2 {
- return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume)
- }
- if err := validateVolumeHostDir(arr[0]); err != nil {
- return err
- }
- if err := validateVolumeCtrDir(arr[1]); err != nil {
+ if _, err := ParseVolume(volume); err != nil {
return err
}
- if len(arr) > 2 {
- if err := validateVolumeOpts(arr[2]); err != nil {
- return err
- }
- }
}
return nil
}
diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go b/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go
new file mode 100644
index 000000000..d056c1bb3
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go
@@ -0,0 +1,20 @@
+// +build linux darwin
+
+package parse
+
+import (
+ "fmt"
+ "golang.org/x/sys/unix"
+)
+
+func getDefaultProcessLimits() []string {
+ rlim := unix.Rlimit{Cur: 1048576, Max: 1048576}
+ defaultLimits := []string{}
+ if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rlim); err == nil {
+ defaultLimits = append(defaultLimits, fmt.Sprintf("nofile=%d:%d", rlim.Cur, rlim.Max))
+ }
+ if err := unix.Setrlimit(unix.RLIMIT_NPROC, &rlim); err == nil {
+ defaultLimits = append(defaultLimits, fmt.Sprintf("nproc=%d:%d", rlim.Cur, rlim.Max))
+ }
+ return defaultLimits
+}
diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go b/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go
new file mode 100644
index 000000000..7e970624f
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go
@@ -0,0 +1,7 @@
+// +build !linux,!darwin
+
+package parse
+
+func getDefaultProcessLimits() []string {
+ return []string{}
+}
diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go
index 00eac8e39..88900b6b7 100644
--- a/vendor/github.com/containers/buildah/run.go
+++ b/vendor/github.com/containers/buildah/run.go
@@ -1,44 +1,10 @@
package buildah
import (
- "bytes"
- "context"
- "encoding/json"
"fmt"
"io"
- "io/ioutil"
- "net"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
- "github.com/containernetworking/cni/libcni"
- "github.com/containers/buildah/bind"
- "github.com/containers/buildah/chroot"
- "github.com/containers/buildah/pkg/secrets"
- "github.com/containers/buildah/pkg/unshare"
- "github.com/containers/buildah/util"
- "github.com/containers/storage/pkg/idtools"
- "github.com/containers/storage/pkg/ioutils"
- "github.com/containers/storage/pkg/reexec"
- "github.com/containers/storage/pkg/stringid"
- units "github.com/docker/go-units"
- "github.com/docker/libnetwork/resolvconf"
- "github.com/docker/libnetwork/types"
- digest "github.com/opencontainers/go-digest"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/opencontainers/runtime-tools/generate"
- "github.com/opencontainers/selinux/go-selinux/label"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- "golang.org/x/crypto/ssh/terminal"
- "golang.org/x/sys/unix"
+ "github.com/opencontainers/runtime-spec/specs-go"
)
const (
@@ -203,34 +169,6 @@ type RunOptions struct {
DropCapabilities []string
}
-// DefaultNamespaceOptions returns the default namespace settings from the
-// runtime-tools generator library.
-func DefaultNamespaceOptions() (NamespaceOptions, error) {
- options := NamespaceOptions{
- {Name: string(specs.CgroupNamespace), Host: true},
- {Name: string(specs.IPCNamespace), Host: true},
- {Name: string(specs.MountNamespace), Host: true},
- {Name: string(specs.NetworkNamespace), Host: true},
- {Name: string(specs.PIDNamespace), Host: true},
- {Name: string(specs.UserNamespace), Host: true},
- {Name: string(specs.UTSNamespace), Host: true},
- }
- g, err := generate.New("linux")
- if err != nil {
- return options, errors.Wrapf(err, "error generating new 'linux' runtime spec")
- }
- spec := g.Config
- if spec.Linux != nil {
- for _, ns := range spec.Linux.Namespaces {
- options.AddOrReplace(NamespaceOption{
- Name: string(ns.Type),
- Path: ns.Path,
- })
- }
- }
- return options, nil
-}
-
// Find the configuration for the namespace of the given type. If there are
// duplicates, find the _last_ one of the type, since we assume it was appended
// more recently.
@@ -258,1959 +196,3 @@ nextOption:
*n = append(*n, option)
}
}
-
-func addRlimits(ulimit []string, g *generate.Generator) error {
- var (
- ul *units.Ulimit
- err error
- )
-
- for _, u := range ulimit {
- if ul, err = units.ParseUlimit(u); err != nil {
- return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
- }
-
- g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft))
- }
- return nil
-}
-
-func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) error {
- // Resources - CPU
- if commonOpts.CPUPeriod != 0 {
- g.SetLinuxResourcesCPUPeriod(commonOpts.CPUPeriod)
- }
- if commonOpts.CPUQuota != 0 {
- g.SetLinuxResourcesCPUQuota(commonOpts.CPUQuota)
- }
- if commonOpts.CPUShares != 0 {
- g.SetLinuxResourcesCPUShares(commonOpts.CPUShares)
- }
- if commonOpts.CPUSetCPUs != "" {
- g.SetLinuxResourcesCPUCpus(commonOpts.CPUSetCPUs)
- }
- if commonOpts.CPUSetMems != "" {
- g.SetLinuxResourcesCPUMems(commonOpts.CPUSetMems)
- }
-
- // Resources - Memory
- if commonOpts.Memory != 0 {
- g.SetLinuxResourcesMemoryLimit(commonOpts.Memory)
- }
- if commonOpts.MemorySwap != 0 {
- g.SetLinuxResourcesMemorySwap(commonOpts.MemorySwap)
- }
-
- // cgroup membership
- if commonOpts.CgroupParent != "" {
- g.SetLinuxCgroupsPath(commonOpts.CgroupParent)
- }
-
- // Other process resource limits
- if err := addRlimits(commonOpts.Ulimit, g); err != nil {
- return err
- }
-
- logrus.Debugf("Resources: %#v", commonOpts)
- return nil
-}
-
-func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error {
- // Start building a new list of mounts.
- var mounts []specs.Mount
- haveMount := func(destination string) bool {
- for _, mount := range mounts {
- if mount.Destination == destination {
- // Already have something to mount there.
- return true
- }
- }
- return false
- }
-
- ipc := namespaceOptions.Find(string(specs.IPCNamespace))
- hostIPC := ipc == nil || ipc.Host
- net := namespaceOptions.Find(string(specs.NetworkNamespace))
- hostNetwork := net == nil || net.Host
- user := namespaceOptions.Find(string(specs.UserNamespace))
- hostUser := user == nil || user.Host
-
- // Copy mounts from the generated list.
- mountCgroups := true
- specMounts := []specs.Mount{}
- for _, specMount := range spec.Mounts {
- // Override some of the mounts from the generated list if we're doing different things with namespaces.
- if specMount.Destination == "/dev/shm" {
- specMount.Options = []string{"nosuid", "noexec", "nodev", "mode=1777", "size=" + shmSize}
- if hostIPC && !hostUser {
- if _, err := os.Stat("/dev/shm"); err != nil && os.IsNotExist(err) {
- logrus.Debugf("/dev/shm is not present, not binding into container")
- continue
- }
- specMount = specs.Mount{
- Source: "/dev/shm",
- Type: "bind",
- Destination: "/dev/shm",
- Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"},
- }
- }
- }
- if specMount.Destination == "/dev/mqueue" {
- if hostIPC && !hostUser {
- if _, err := os.Stat("/dev/mqueue"); err != nil && os.IsNotExist(err) {
- logrus.Debugf("/dev/mqueue is not present, not binding into container")
- continue
- }
- specMount = specs.Mount{
- Source: "/dev/mqueue",
- Type: "bind",
- Destination: "/dev/mqueue",
- Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"},
- }
- }
- }
- if specMount.Destination == "/sys" {
- if hostNetwork && !hostUser {
- mountCgroups = false
- if _, err := os.Stat("/sys"); err != nil && os.IsNotExist(err) {
- logrus.Debugf("/sys is not present, not binding into container")
- continue
- }
- specMount = specs.Mount{
- Source: "/sys",
- Type: "bind",
- Destination: "/sys",
- Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev", "ro"},
- }
- }
- }
- specMounts = append(specMounts, specMount)
- }
-
- // Add a mount for the cgroups filesystem, unless we're already
- // recursively bind mounting all of /sys, in which case we shouldn't
- // bother with it.
- sysfsMount := []specs.Mount{}
- if mountCgroups {
- sysfsMount = []specs.Mount{{
- Destination: "/sys/fs/cgroup",
- Type: "cgroup",
- Source: "cgroup",
- Options: []string{bind.NoBindOption, "nosuid", "noexec", "nodev", "relatime", "ro"},
- }}
- }
-
- // Get the list of files we need to bind into the container.
- bindFileMounts, err := runSetupBoundFiles(bundlePath, bindFiles)
- if err != nil {
- return err
- }
-
- // After this point we need to know the per-container persistent storage directory.
- cdir, err := b.store.ContainerDirectory(b.ContainerID)
- if err != nil {
- return errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID)
- }
-
- // Figure out which UID and GID to tell the secrets package to use
- // for files that it creates.
- rootUID, rootGID, err := util.GetHostRootIDs(spec)
- if err != nil {
- return err
- }
-
- // Get the list of secrets mounts.
- secretMounts := secrets.SecretMountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, cdir, int(rootUID), int(rootGID), unshare.IsRootless())
-
- // Add temporary copies of the contents of volume locations at the
- // volume locations, unless we already have something there.
- copyWithTar := b.copyWithTar(nil, nil)
- builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID))
- if err != nil {
- return err
- }
-
- // Get the list of explicitly-specified volume mounts.
- volumes, err := runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts, optionMounts)
- if err != nil {
- return err
- }
-
- // Add them all, in the preferred order, except where they conflict with something that was previously added.
- for _, mount := range append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...) {
- if haveMount(mount.Destination) {
- // Already mounting something there, no need to bother with this one.
- continue
- }
- // Add the mount.
- mounts = append(mounts, mount)
- }
-
- // Set the list in the spec.
- spec.Mounts = mounts
- return nil
-}
-
-func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount, err error) {
- for dest, src := range bindFiles {
- options := []string{"rbind"}
- if strings.HasPrefix(src, bundlePath) {
- options = append(options, bind.NoBindOption)
- }
- mounts = append(mounts, specs.Mount{
- Source: src,
- Destination: dest,
- Type: "bind",
- Options: options,
- })
- }
- return mounts, nil
-}
-
-func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string, rootUID, rootGID int) ([]specs.Mount, error) {
- var mounts []specs.Mount
- hostOwner := idtools.IDPair{UID: rootUID, GID: rootGID}
- // Add temporary copies of the contents of volume locations at the
- // volume locations, unless we already have something there.
- for _, volume := range builtinVolumes {
- subdir := digest.Canonical.FromString(volume).Hex()
- volumePath := filepath.Join(containerDir, "buildah-volumes", subdir)
- srcPath := filepath.Join(mountPoint, volume)
- initializeVolume := false
- // If we need to, initialize the volume path's initial contents.
- if _, err := os.Stat(volumePath); err != nil {
- if !os.IsNotExist(err) {
- return nil, errors.Wrapf(err, "failed to stat %q for volume %q", volumePath, volume)
- }
- logrus.Debugf("setting up built-in volume at %q", volumePath)
- if err = os.MkdirAll(volumePath, 0755); err != nil {
- return nil, errors.Wrapf(err, "error creating directory %q for volume %q", volumePath, volume)
- }
- if err = label.Relabel(volumePath, mountLabel, false); err != nil {
- return nil, errors.Wrapf(err, "error relabeling directory %q for volume %q", volumePath, volume)
- }
- initializeVolume = true
- }
- stat, err := os.Stat(srcPath)
- if err != nil {
- if !os.IsNotExist(err) {
- return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume)
- }
- if err = idtools.MkdirAllAndChownNew(srcPath, 0755, hostOwner); err != nil {
- return nil, errors.Wrapf(err, "error creating directory %q for volume %q", srcPath, volume)
- }
- if stat, err = os.Stat(srcPath); err != nil {
- return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume)
- }
- }
- if initializeVolume {
- if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil {
- return nil, errors.Wrapf(err, "failed to chmod %q for volume %q", volumePath, volume)
- }
- if err = os.Chown(volumePath, int(stat.Sys().(*syscall.Stat_t).Uid), int(stat.Sys().(*syscall.Stat_t).Gid)); err != nil {
- return nil, errors.Wrapf(err, "error chowning directory %q for volume %q", volumePath, volume)
- }
- if err = copyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(errors.Cause(err)) {
- return nil, errors.Wrapf(err, "error populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath)
- }
- }
- // Add the bind mount.
- mounts = append(mounts, specs.Mount{
- Source: volumePath,
- Destination: volume,
- Type: "bind",
- Options: []string{"bind"},
- })
- }
- return mounts, nil
-}
-
-func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount) ([]specs.Mount, error) {
- var mounts []specs.Mount
-
- parseMount := func(host, container string, options []string) (specs.Mount, error) {
- var foundrw, foundro, foundz, foundZ bool
- var rootProp string
- for _, opt := range options {
- switch opt {
- case "rw":
- foundrw = true
- case "ro":
- foundro = true
- case "z":
- foundz = true
- case "Z":
- foundZ = true
- case "private", "rprivate", "slave", "rslave", "shared", "rshared":
- rootProp = opt
- }
- }
- if !foundrw && !foundro {
- options = append(options, "rw")
- }
- if foundz {
- if err := label.Relabel(host, mountLabel, true); err != nil {
- return specs.Mount{}, errors.Wrapf(err, "relabeling %q failed", host)
- }
- }
- if foundZ {
- if err := label.Relabel(host, mountLabel, false); err != nil {
- return specs.Mount{}, errors.Wrapf(err, "relabeling %q failed", host)
- }
- }
- if rootProp == "" {
- options = append(options, "private")
- }
- return specs.Mount{
- Destination: container,
- Type: "bind",
- Source: host,
- Options: options,
- }, nil
- }
- // Bind mount volumes specified for this particular Run() invocation
- for _, i := range optionMounts {
- logrus.Debugf("setting up mounted volume at %q", i.Destination)
- mount, err := parseMount(i.Source, i.Destination, append(i.Options, "rbind"))
- if err != nil {
- return nil, err
- }
- mounts = append(mounts, mount)
- }
- // Bind mount volumes given by the user when the container was created
- for _, i := range volumeMounts {
- var options []string
- spliti := strings.Split(i, ":")
- if len(spliti) > 2 {
- options = strings.Split(spliti[2], ",")
- }
- options = append(options, "rbind")
- mount, err := parseMount(spliti[0], spliti[1], options)
- if err != nil {
- return nil, err
- }
- mounts = append(mounts, mount)
- }
- return mounts, nil
-}
-
-// addNetworkConfig copies files from host and sets them up to bind mount into container
-func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string) (string, error) {
- stat, err := os.Stat(hostPath)
- if err != nil {
- return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID)
- }
- contents, err := ioutil.ReadFile(hostPath)
- if err != nil {
- return "", errors.Wrapf(err, "unable to read %s", hostPath)
- }
-
- search := resolvconf.GetSearchDomains(contents)
- nameservers := resolvconf.GetNameservers(contents, types.IP)
- options := resolvconf.GetOptions(contents)
-
- if len(dnsSearch) > 0 {
- search = dnsSearch
- }
- if len(dnsServers) != 0 {
- dns, err := getDNSIP(dnsServers)
- if err != nil {
- return "", errors.Wrapf(err, "error getting dns servers")
- }
- nameservers = []string{}
- for _, server := range dns {
- nameservers = append(nameservers, server.String())
- }
- }
-
- if len(dnsOptions) != 0 {
- options = dnsOptions
- }
-
- cfile := filepath.Join(rdir, filepath.Base(hostPath))
- if _, err = resolvconf.Build(cfile, nameservers, search, options); err != nil {
- return "", errors.Wrapf(err, "error building resolv.conf for container %s", b.ContainerID)
- }
-
- uid := int(stat.Sys().(*syscall.Stat_t).Uid)
- gid := int(stat.Sys().(*syscall.Stat_t).Gid)
- if chownOpts != nil {
- uid = chownOpts.UID
- gid = chownOpts.GID
- }
- if err = os.Chown(cfile, uid, gid); err != nil {
- return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID)
- }
-
- if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
- return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
- }
-
- return cfile, nil
-}
-
-func getDNSIP(dnsServers []string) (dns []net.IP, err error) {
- for _, i := range dnsServers {
- result := net.ParseIP(i)
- if result == nil {
- return dns, errors.Errorf("invalid IP address %s", i)
- }
- dns = append(dns, result)
- }
- return dns, nil
-}
-
-// generateHosts creates a containers hosts file
-func (b *Builder) generateHosts(rdir, hostname string, addHosts []string, chownOpts *idtools.IDPair) (string, error) {
- hostPath := "/etc/hosts"
- stat, err := os.Stat(hostPath)
- if err != nil {
- return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID)
- }
-
- hosts := bytes.NewBufferString("# Generated by Buildah\n")
- orig, err := ioutil.ReadFile(hostPath)
- if err != nil {
- return "", errors.Wrapf(err, "unable to read %s", hostPath)
- }
- hosts.Write(orig)
- for _, host := range addHosts {
- // verify the host format
- values := strings.SplitN(host, ":", 2)
- if len(values) != 2 {
- return "", errors.Errorf("unable to parse host entry %q: incorrect format", host)
- }
- if values[0] == "" {
- return "", errors.Errorf("hostname in host entry %q is empty", host)
- }
- if values[1] == "" {
- return "", errors.Errorf("IP address in host entry %q is empty", host)
- }
- hosts.Write([]byte(fmt.Sprintf("%s\t%s\n", values[1], values[0])))
- }
-
- if hostname != "" {
- hosts.Write([]byte(fmt.Sprintf("127.0.0.1 %s\n", hostname)))
- hosts.Write([]byte(fmt.Sprintf("::1 %s\n", hostname)))
- }
- cfile := filepath.Join(rdir, filepath.Base(hostPath))
- if err = ioutils.AtomicWriteFile(cfile, hosts.Bytes(), stat.Mode().Perm()); err != nil {
- return "", errors.Wrapf(err, "error writing /etc/hosts into the container")
- }
- uid := int(stat.Sys().(*syscall.Stat_t).Uid)
- gid := int(stat.Sys().(*syscall.Stat_t).Gid)
- if chownOpts != nil {
- uid = chownOpts.UID
- gid = chownOpts.GID
- }
- if err = os.Chown(cfile, uid, gid); err != nil {
- return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID)
- }
- if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
- return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
- }
-
- return cfile, nil
-}
-
-func setupMaskedPaths(g *generate.Generator) {
- for _, mp := range []string{
- "/proc/acpi",
- "/proc/kcore",
- "/proc/keys",
- "/proc/latency_stats",
- "/proc/timer_list",
- "/proc/timer_stats",
- "/proc/sched_debug",
- "/proc/scsi",
- "/sys/firmware",
- } {
- g.AddLinuxMaskedPaths(mp)
- }
-}
-
-func setupReadOnlyPaths(g *generate.Generator) {
- for _, rp := range []string{
- "/proc/asound",
- "/proc/bus",
- "/proc/fs",
- "/proc/irq",
- "/proc/sys",
- "/proc/sysrq-trigger",
- } {
- g.AddLinuxReadonlyPaths(rp)
- }
-}
-
-func setupCapAdd(g *generate.Generator, caps ...string) error {
- for _, cap := range caps {
- if err := g.AddProcessCapabilityBounding(cap); err != nil {
- return errors.Wrapf(err, "error adding %q to the bounding capability set", cap)
- }
- if err := g.AddProcessCapabilityEffective(cap); err != nil {
- return errors.Wrapf(err, "error adding %q to the effective capability set", cap)
- }
- if err := g.AddProcessCapabilityInheritable(cap); err != nil {
- return errors.Wrapf(err, "error adding %q to the inheritable capability set", cap)
- }
- if err := g.AddProcessCapabilityPermitted(cap); err != nil {
- return errors.Wrapf(err, "error adding %q to the permitted capability set", cap)
- }
- if err := g.AddProcessCapabilityAmbient(cap); err != nil {
- return errors.Wrapf(err, "error adding %q to the ambient capability set", cap)
- }
- }
- return nil
-}
-
-func setupCapDrop(g *generate.Generator, caps ...string) error {
- for _, cap := range caps {
- if err := g.DropProcessCapabilityBounding(cap); err != nil {
- return errors.Wrapf(err, "error removing %q from the bounding capability set", cap)
- }
- if err := g.DropProcessCapabilityEffective(cap); err != nil {
- return errors.Wrapf(err, "error removing %q from the effective capability set", cap)
- }
- if err := g.DropProcessCapabilityInheritable(cap); err != nil {
- return errors.Wrapf(err, "error removing %q from the inheritable capability set", cap)
- }
- if err := g.DropProcessCapabilityPermitted(cap); err != nil {
- return errors.Wrapf(err, "error removing %q from the permitted capability set", cap)
- }
- if err := g.DropProcessCapabilityAmbient(cap); err != nil {
- return errors.Wrapf(err, "error removing %q from the ambient capability set", cap)
- }
- }
- return nil
-}
-
-func setupCapabilities(g *generate.Generator, firstAdds, firstDrops, secondAdds, secondDrops []string) error {
- g.ClearProcessCapabilities()
- if err := setupCapAdd(g, util.DefaultCapabilities...); err != nil {
- return err
- }
- if err := setupCapAdd(g, firstAdds...); err != nil {
- return err
- }
- if err := setupCapDrop(g, firstDrops...); err != nil {
- return err
- }
- if err := setupCapAdd(g, secondAdds...); err != nil {
- return err
- }
- return setupCapDrop(g, secondDrops...)
-}
-
-func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) {
- switch terminalPolicy {
- case DefaultTerminal:
- onTerminal := terminal.IsTerminal(unix.Stdin) && terminal.IsTerminal(unix.Stdout) && terminal.IsTerminal(unix.Stderr)
- if onTerminal {
- logrus.Debugf("stdio is a terminal, defaulting to using a terminal")
- } else {
- logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal")
- }
- g.SetProcessTerminal(onTerminal)
- case WithTerminal:
- g.SetProcessTerminal(true)
- case WithoutTerminal:
- g.SetProcessTerminal(false)
- }
- if terminalSize != nil {
- g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height)
- }
-}
-
-func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, configureUTS bool, err error) {
- // Set namespace options in the container configuration.
- configureUserns := false
- specifiedNetwork := false
- for _, namespaceOption := range namespaceOptions {
- switch namespaceOption.Name {
- case string(specs.UserNamespace):
- configureUserns = false
- if !namespaceOption.Host && namespaceOption.Path == "" {
- configureUserns = true
- }
- case string(specs.NetworkNamespace):
- specifiedNetwork = true
- configureNetwork = false
- if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) {
- if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) {
- configureNetworks = strings.Split(namespaceOption.Path, ",")
- namespaceOption.Path = ""
- }
- configureNetwork = (policy != NetworkDisabled)
- }
- case string(specs.UTSNamespace):
- configureUTS = false
- if !namespaceOption.Host && namespaceOption.Path == "" {
- configureUTS = true
- }
- }
- if namespaceOption.Host {
- if err := g.RemoveLinuxNamespace(namespaceOption.Name); err != nil {
- return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", namespaceOption.Name)
- }
- } else if err := g.AddOrReplaceLinuxNamespace(namespaceOption.Name, namespaceOption.Path); err != nil {
- if namespaceOption.Path == "" {
- return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", namespaceOption.Name)
- }
- return false, nil, false, errors.Wrapf(err, "error adding %q namespace %q for run", namespaceOption.Name, namespaceOption.Path)
- }
- }
-
- // If we've got mappings, we're going to have to create a user namespace.
- if len(idmapOptions.UIDMap) > 0 || len(idmapOptions.GIDMap) > 0 || configureUserns {
- if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil {
- return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace))
- }
- hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("")
- if err != nil {
- return false, nil, false, err
- }
- for _, m := range idmapOptions.UIDMap {
- g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size)
- }
- if len(idmapOptions.UIDMap) == 0 {
- for _, m := range hostUidmap {
- g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size)
- }
- }
- for _, m := range idmapOptions.GIDMap {
- g.AddLinuxGIDMapping(m.HostID, m.ContainerID, m.Size)
- }
- if len(idmapOptions.GIDMap) == 0 {
- for _, m := range hostGidmap {
- g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size)
- }
- }
- if !specifiedNetwork {
- if err := g.AddOrReplaceLinuxNamespace(specs.NetworkNamespace, ""); err != nil {
- return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.NetworkNamespace))
- }
- configureNetwork = (policy != NetworkDisabled)
- }
- } else {
- if err := g.RemoveLinuxNamespace(specs.UserNamespace); err != nil {
- return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.UserNamespace))
- }
- if !specifiedNetwork {
- if err := g.RemoveLinuxNamespace(specs.NetworkNamespace); err != nil {
- return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.NetworkNamespace))
- }
- }
- }
- if configureNetwork {
- for name, val := range util.DefaultNetworkSysctl {
- g.AddLinuxSysctl(name, val)
- }
- }
- return configureNetwork, configureNetworks, configureUTS, nil
-}
-
-// Search for a command that isn't given as an absolute path using the $PATH
-// under the rootfs. We can't resolve absolute symbolic links without
-// chroot()ing, which we may not be able to do, so just accept a link as a
-// valid resolution.
-func runLookupPath(g *generate.Generator, command []string) []string {
- // Look for the configured $PATH.
- spec := g.Config
- envPath := ""
- for i := range spec.Process.Env {
- if strings.HasPrefix(spec.Process.Env[i], "PATH=") {
- envPath = spec.Process.Env[i]
- }
- }
- // If there is no configured $PATH, supply one.
- if envPath == "" {
- defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
- envPath = "PATH=" + defaultPath
- g.AddProcessEnv("PATH", defaultPath)
- }
- // No command, nothing to do.
- if len(command) == 0 {
- return command
- }
- // Command is already an absolute path, use it as-is.
- if filepath.IsAbs(command[0]) {
- return command
- }
- // For each element in the PATH,
- for _, pathEntry := range filepath.SplitList(envPath[5:]) {
- // if it's the empty string, it's ".", which is the Cwd,
- if pathEntry == "" {
- pathEntry = spec.Process.Cwd
- }
- // build the absolute path which it might be,
- candidate := filepath.Join(pathEntry, command[0])
- // check if it's there,
- if fi, err := os.Lstat(filepath.Join(spec.Root.Path, candidate)); fi != nil && err == nil {
- // and if it's not a directory, and either a symlink or executable,
- if !fi.IsDir() && ((fi.Mode()&os.ModeSymlink != 0) || (fi.Mode()&0111 != 0)) {
- // use that.
- return append([]string{candidate}, command[1:]...)
- }
- }
- }
- return command
-}
-
-func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, options RunOptions) error {
- // Set the user UID/GID/supplemental group list/capabilities lists.
- user, err := b.user(mountPoint, options.User)
- if err != nil {
- return err
- }
- if err := setupCapabilities(g, b.AddCapabilities, b.DropCapabilities, options.AddCapabilities, options.DropCapabilities); err != nil {
- return err
- }
- g.SetProcessUID(user.UID)
- g.SetProcessGID(user.GID)
- for _, gid := range user.AdditionalGids {
- g.AddProcessAdditionalGid(gid)
- }
-
- // Remove capabilities if not running as root except Bounding set
- if user.UID != 0 {
- bounding := g.Config.Process.Capabilities.Bounding
- g.ClearProcessCapabilities()
- g.Config.Process.Capabilities.Bounding = bounding
- }
-
- return nil
-}
-
-func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions) {
- g.ClearProcessEnv()
- if b.CommonBuildOpts.HTTPProxy {
- for _, envSpec := range []string{
- "http_proxy",
- "HTTP_PROXY",
- "https_proxy",
- "HTTPS_PROXY",
- "ftp_proxy",
- "FTP_PROXY",
- "no_proxy",
- "NO_PROXY",
- } {
- envVal := os.Getenv(envSpec)
- if envVal != "" {
- g.AddProcessEnv(envSpec, envVal)
- }
- }
- }
-
- for _, envSpec := range append(b.Env(), options.Env...) {
- env := strings.SplitN(envSpec, "=", 2)
- if len(env) > 1 {
- g.AddProcessEnv(env[0], env[1])
- }
- }
-
- for src, dest := range b.Args {
- g.AddProcessEnv(src, dest)
- }
-}
-
-func (b *Builder) configureNamespaces(g *generate.Generator, options RunOptions) (bool, []string, error) {
- defaultNamespaceOptions, err := DefaultNamespaceOptions()
- if err != nil {
- return false, nil, err
- }
-
- namespaceOptions := defaultNamespaceOptions
- namespaceOptions.AddOrReplace(b.NamespaceOptions...)
- namespaceOptions.AddOrReplace(options.NamespaceOptions...)
-
- networkPolicy := options.ConfigureNetwork
- if networkPolicy == NetworkDefault {
- networkPolicy = b.ConfigureNetwork
- }
-
- configureNetwork, configureNetworks, configureUTS, err := setupNamespaces(g, namespaceOptions, b.IDMappingOptions, networkPolicy)
- if err != nil {
- return false, nil, err
- }
-
- if configureUTS {
- if options.Hostname != "" {
- g.SetHostname(options.Hostname)
- } else if b.Hostname() != "" {
- g.SetHostname(b.Hostname())
- } else {
- g.SetHostname(stringid.TruncateID(b.ContainerID))
- }
- } else {
- g.SetHostname("")
- }
-
- found := false
- spec := g.Config
- for i := range spec.Process.Env {
- if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") {
- found = true
- break
- }
- }
- if !found {
- spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOSTNAME=%s", spec.Hostname))
- }
-
- return configureNetwork, configureNetworks, nil
-}
-
-// Run runs the specified command in the container's root filesystem.
-func (b *Builder) Run(command []string, options RunOptions) error {
- p, err := ioutil.TempDir("", Package)
- if err != nil {
- return errors.Wrapf(err, "run: error creating temporary directory under %q", os.TempDir())
- }
- // On some hosts like AH, /tmp is a symlink and we need an
- // absolute path.
- path, err := filepath.EvalSymlinks(p)
- if err != nil {
- return errors.Wrapf(err, "run: error evaluating %q for symbolic links", p)
- }
- 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)
- }
- }()
-
- gp, err := generate.New("linux")
- if err != nil {
- return errors.Wrapf(err, "error generating new 'linux' runtime spec")
- }
- g := &gp
-
- isolation := options.Isolation
- if isolation == IsolationDefault {
- isolation = b.Isolation
- if isolation == IsolationDefault {
- isolation = IsolationOCI
- }
- }
- if err := checkAndOverrideIsolationOptions(isolation, &options); err != nil {
- return err
- }
-
- b.configureEnvironment(g, options)
-
- if b.CommonBuildOpts == nil {
- return errors.Errorf("Invalid format on container you must recreate the container")
- }
-
- if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil {
- return err
- }
-
- if options.WorkingDir != "" {
- g.SetProcessCwd(options.WorkingDir)
- } else if b.WorkDir() != "" {
- g.SetProcessCwd(b.WorkDir())
- }
- setupSelinux(g, b.ProcessLabel, b.MountLabel)
- mountPoint, err := b.Mount(b.MountLabel)
- if err != nil {
- return errors.Wrapf(err, "error mounting container %q", b.ContainerID)
- }
- defer func() {
- if err := b.Unmount(); err != nil {
- logrus.Errorf("error unmounting container: %v", err)
- }
- }()
- g.SetRootPath(mountPoint)
- if len(command) > 0 {
- command = runLookupPath(g, command)
- g.SetProcessArgs(command)
- } else {
- g.SetProcessArgs(nil)
- }
-
- setupMaskedPaths(g)
- setupReadOnlyPaths(g)
-
- setupTerminal(g, options.Terminal, options.TerminalSize)
-
- configureNetwork, configureNetworks, err := b.configureNamespaces(g, options)
- if err != nil {
- return err
- }
-
- if err := b.configureUIDGID(g, mountPoint, options); err != nil {
- return err
- }
-
- g.SetProcessApparmorProfile(b.CommonBuildOpts.ApparmorProfile)
-
- // Now grab the spec from the generator. Set the generator to nil so that future contributors
- // will quickly be able to tell that they're supposed to be modifying the spec directly from here.
- spec := g.Config
- g = nil
-
- logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd))
- 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)
- }
-
- // Set the seccomp configuration using the specified profile name. Some syscalls are
- // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot),
- // so we sorted out the capabilities lists first.
- if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil {
- return err
- }
-
- // Figure out who owns files that will appear to be owned by UID/GID 0 in the container.
- rootUID, rootGID, err := util.GetHostRootIDs(spec)
- if err != nil {
- return err
- }
- rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}
-
- bindFiles := make(map[string]string)
- namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...)
- volumes := b.Volumes()
-
- if !contains(volumes, "/etc/hosts") {
- hostFile, err := b.generateHosts(path, spec.Hostname, b.CommonBuildOpts.AddHost, rootIDPair)
- if err != nil {
- return err
- }
- bindFiles["/etc/hosts"] = hostFile
- }
-
- if !contains(volumes, "/etc/resolv.conf") {
- resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair, b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions)
- if err != nil {
- return err
- }
- bindFiles["/etc/resolv.conf"] = resolvFile
- }
-
- err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions)
- if err != nil {
- return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID)
- }
-
- if options.CNIConfigDir == "" {
- options.CNIConfigDir = b.CNIConfigDir
- if b.CNIConfigDir == "" {
- options.CNIConfigDir = util.DefaultCNIConfigDir
- }
- }
- if options.CNIPluginPath == "" {
- options.CNIPluginPath = b.CNIPluginPath
- if b.CNIPluginPath == "" {
- options.CNIPluginPath = util.DefaultCNIPluginPath
- }
- }
-
- switch isolation {
- case IsolationOCI:
- var moreCreateArgs []string
- if options.NoPivot {
- moreCreateArgs = []string{"--no-pivot"}
- } else {
- moreCreateArgs = nil
- }
- err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path))
- case IsolationChroot:
- err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr)
- case IsolationOCIRootless:
- moreCreateArgs := []string{"--no-new-keyring"}
- if options.NoPivot {
- moreCreateArgs = append(moreCreateArgs, "--no-pivot")
- }
- if err := setupRootlessSpecChanges(spec, path, rootUID, rootGID); err != nil {
- return err
- }
- err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path))
- default:
- err = errors.Errorf("don't know how to run this command")
- }
- return err
-}
-
-func contains(volumes []string, v string) bool {
- for _, i := range volumes {
- if i == v {
- return true
- }
- }
- return false
-}
-
-func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) error {
- switch isolation {
- case IsolationOCIRootless:
- if ns := options.NamespaceOptions.Find(string(specs.IPCNamespace)); ns == nil || ns.Host {
- logrus.Debugf("Forcing use of an IPC namespace.")
- }
- options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.IPCNamespace)})
- _, err := exec.LookPath("slirp4netns")
- hostNetworking := err != nil
- networkNamespacePath := ""
- if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil {
- hostNetworking = ns.Host
- networkNamespacePath = ns.Path
- if !hostNetworking && networkNamespacePath != "" && !filepath.IsAbs(networkNamespacePath) {
- logrus.Debugf("Disabling network namespace configuration.")
- networkNamespacePath = ""
- }
- }
- options.NamespaceOptions.AddOrReplace(NamespaceOption{
- Name: string(specs.NetworkNamespace),
- Host: hostNetworking,
- Path: networkNamespacePath,
- })
- if ns := options.NamespaceOptions.Find(string(specs.PIDNamespace)); ns == nil || ns.Host {
- logrus.Debugf("Forcing use of a PID namespace.")
- }
- options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.PIDNamespace), Host: false})
- if ns := options.NamespaceOptions.Find(string(specs.UserNamespace)); ns == nil || ns.Host {
- logrus.Debugf("Forcing use of a user namespace.")
- }
- options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UserNamespace)})
- if ns := options.NamespaceOptions.Find(string(specs.UTSNamespace)); ns != nil && !ns.Host {
- logrus.Debugf("Disabling UTS namespace.")
- }
- options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UTSNamespace), Host: true})
- case IsolationOCI:
- pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace))
- userns := options.NamespaceOptions.Find(string(specs.UserNamespace))
- if (pidns == nil || pidns.Host) && (userns != nil && !userns.Host) {
- return fmt.Errorf("not allowed to mix host PID namespace with container user namespace")
- }
- }
- return nil
-}
-
-func setupRootlessSpecChanges(spec *specs.Spec, bundleDir string, rootUID, rootGID uint32) error {
- spec.Hostname = ""
- spec.Process.User.AdditionalGids = nil
- spec.Linux.Resources = nil
-
- emptyDir := filepath.Join(bundleDir, "empty")
- if err := os.Mkdir(emptyDir, 0); err != nil {
- return errors.Wrapf(err, "error creating %q", emptyDir)
- }
-
- // Replace /sys with a read-only bind mount.
- mounts := []specs.Mount{
- {
- Source: "/dev",
- Destination: "/dev",
- Type: "tmpfs",
- Options: []string{"private", "strictatime", "noexec", "nosuid", "mode=755", "size=65536k"},
- },
- {
- Source: "mqueue",
- Destination: "/dev/mqueue",
- Type: "mqueue",
- Options: []string{"private", "nodev", "noexec", "nosuid"},
- },
- {
- Source: "pts",
- Destination: "/dev/pts",
- Type: "devpts",
- Options: []string{"private", "noexec", "nosuid", "newinstance", "ptmxmode=0666", "mode=0620"},
- },
- {
- Source: "shm",
- Destination: "/dev/shm",
- Type: "tmpfs",
- Options: []string{"private", "nodev", "noexec", "nosuid", "mode=1777", "size=65536k"},
- },
- {
- Source: "/proc",
- Destination: "/proc",
- Type: "proc",
- Options: []string{"private", "nodev", "noexec", "nosuid"},
- },
- {
- Source: "/sys",
- Destination: "/sys",
- Type: "bind",
- Options: []string{bind.NoBindOption, "rbind", "private", "nodev", "noexec", "nosuid", "ro"},
- },
- }
- // Cover up /sys/fs/cgroup and /sys/fs/selinux, if they exist in our source for /sys.
- if _, err := os.Stat("/sys/fs/cgroup"); err == nil {
- spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/cgroup")
- }
- if _, err := os.Stat("/sys/fs/selinux"); err == nil {
- spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/selinux")
- }
- // Keep anything that isn't under /dev, /proc, or /sys.
- for i := range spec.Mounts {
- if spec.Mounts[i].Destination == "/dev" || strings.HasPrefix(spec.Mounts[i].Destination, "/dev/") ||
- spec.Mounts[i].Destination == "/proc" || strings.HasPrefix(spec.Mounts[i].Destination, "/proc/") ||
- spec.Mounts[i].Destination == "/sys" || strings.HasPrefix(spec.Mounts[i].Destination, "/sys/") {
- continue
- }
- mounts = append(mounts, spec.Mounts[i])
- }
- spec.Mounts = mounts
- return nil
-}
-
-type runUsingRuntimeSubprocOptions struct {
- Options RunOptions
- Spec *specs.Spec
- RootPath string
- BundlePath string
- ConfigureNetwork bool
- ConfigureNetworks []string
- MoreCreateArgs []string
- ContainerName string
- Isolation Isolation
-}
-
-func (b *Builder) runUsingRuntimeSubproc(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
- var confwg sync.WaitGroup
- config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{
- Options: options,
- Spec: spec,
- RootPath: rootPath,
- BundlePath: bundlePath,
- ConfigureNetwork: configureNetwork,
- ConfigureNetworks: configureNetworks,
- MoreCreateArgs: moreCreateArgs,
- ContainerName: containerName,
- Isolation: isolation,
- })
- if conferr != nil {
- return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand)
- }
- cmd := reexec.Command(runUsingRuntimeCommand)
- cmd.Dir = bundlePath
- cmd.Stdin = options.Stdin
- if cmd.Stdin == nil {
- cmd.Stdin = os.Stdin
- }
- cmd.Stdout = options.Stdout
- if cmd.Stdout == nil {
- cmd.Stdout = os.Stdout
- }
- cmd.Stderr = options.Stderr
- if cmd.Stderr == nil {
- cmd.Stderr = os.Stderr
- }
- cmd.Env = append(os.Environ(), fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel()))
- preader, pwriter, err := os.Pipe()
- if err != nil {
- return errors.Wrapf(err, "error creating configuration pipe")
- }
- confwg.Add(1)
- go func() {
- _, conferr = io.Copy(pwriter, bytes.NewReader(config))
- if conferr != nil {
- conferr = errors.Wrapf(conferr, "error while copying configuration down pipe to child process")
- }
- confwg.Done()
- }()
- cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...)
- defer preader.Close()
- defer pwriter.Close()
- err = cmd.Run()
- if err != nil {
- err = errors.Wrapf(err, "error while running runtime")
- }
- confwg.Wait()
- if err == nil {
- return conferr
- }
- if conferr != nil {
- logrus.Debugf("%v", conferr)
- }
- return err
-}
-
-func init() {
- reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain)
-}
-
-func runUsingRuntimeMain() {
- var options runUsingRuntimeSubprocOptions
- // Set logging.
- if level := os.Getenv("LOGLEVEL"); level != "" {
- if ll, err := strconv.Atoi(level); err == nil {
- logrus.SetLevel(logrus.Level(ll))
- }
- }
- // Unpack our configuration.
- confPipe := os.NewFile(3, "confpipe")
- if confPipe == nil {
- fmt.Fprintf(os.Stderr, "error reading options pipe\n")
- os.Exit(1)
- }
- defer confPipe.Close()
- if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
- fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
- os.Exit(1)
- }
- // Set ourselves up to read the container's exit status. We're doing this in a child process
- // so that we won't mess with the setting in a caller of the library. This stubs to OS specific
- // calls
- if err := setChildProcess(); err != nil {
- os.Exit(1)
- }
- // Run the container, start to finish.
- status, err := runUsingRuntime(options.Isolation, options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName)
- if err != nil {
- fmt.Fprintf(os.Stderr, "error running container: %v\n", err)
- os.Exit(1)
- }
- // Pass the container's exit status back to the caller by exiting with the same status.
- if status.Exited() {
- os.Exit(status.ExitStatus())
- } else if status.Signaled() {
- fmt.Fprintf(os.Stderr, "container exited on %s\n", status.Signal())
- os.Exit(1)
- }
- os.Exit(1)
-}
-
-func runUsingRuntime(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
- // Lock the caller to a single OS-level thread.
- runtime.LockOSThread()
-
- // Set up bind mounts for things that a namespaced user might not be able to get to directly.
- unmountAll, err := bind.SetupIntermediateMountNamespace(spec, bundlePath)
- if unmountAll != nil {
- defer func() {
- if err := unmountAll(); err != nil {
- logrus.Error(err)
- }
- }()
- }
- if err != nil {
- return 1, err
- }
-
- // Write the runtime configuration.
- specbytes, err := json.Marshal(spec)
- if err != nil {
- return 1, errors.Wrapf(err, "error encoding configuration %#v as json", spec)
- }
- if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0600); err != nil {
- return 1, errors.Wrapf(err, "error storing runtime configuration in %q", filepath.Join(bundlePath, "config.json"))
- }
-
- logrus.Debugf("config = %v", string(specbytes))
-
- // Decide which runtime to use.
- runtime := options.Runtime
- if runtime == "" {
- runtime = util.Runtime()
- }
-
- // Default to just passing down our stdio.
- getCreateStdio := func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
- return os.Stdin, os.Stdout, os.Stderr
- }
-
- // Figure out how we're doing stdio handling, and create pipes and sockets.
- var stdio sync.WaitGroup
- var consoleListener *net.UnixListener
- var errorFds, closeBeforeReadingErrorFds []int
- stdioPipe := make([][]int, 3)
- copyConsole := false
- copyPipes := false
- finishCopy := make([]int, 2)
- if err = unix.Pipe(finishCopy); err != nil {
- return 1, errors.Wrapf(err, "error creating pipe for notifying to stop stdio")
- }
- finishedCopy := make(chan struct{})
- if spec.Process != nil {
- if spec.Process.Terminal {
- copyConsole = true
- // Create a listening socket for accepting the container's terminal's PTY master.
- socketPath := filepath.Join(bundlePath, "console.sock")
- consoleListener, err = net.ListenUnix("unix", &net.UnixAddr{Name: socketPath, Net: "unix"})
- if err != nil {
- return 1, errors.Wrapf(err, "error creating socket %q to receive terminal descriptor", consoleListener.Addr())
- }
- // Add console socket arguments.
- moreCreateArgs = append(moreCreateArgs, "--console-socket", socketPath)
- } else {
- copyPipes = true
- // Figure out who should own the pipes.
- uid, gid, err := util.GetHostRootIDs(spec)
- if err != nil {
- return 1, err
- }
- // Create stdio pipes.
- if stdioPipe, err = runMakeStdioPipe(int(uid), int(gid)); err != nil {
- return 1, err
- }
- errorFds = []int{stdioPipe[unix.Stdout][0], stdioPipe[unix.Stderr][0]}
- closeBeforeReadingErrorFds = []int{stdioPipe[unix.Stdout][1], stdioPipe[unix.Stderr][1]}
- // Set stdio to our pipes.
- getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
- stdin := os.NewFile(uintptr(stdioPipe[unix.Stdin][0]), "/dev/stdin")
- stdout := os.NewFile(uintptr(stdioPipe[unix.Stdout][1]), "/dev/stdout")
- stderr := os.NewFile(uintptr(stdioPipe[unix.Stderr][1]), "/dev/stderr")
- return stdin, stdout, stderr
- }
- }
- } else {
- if options.Quiet {
- // Discard stdout.
- getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
- return os.Stdin, nil, os.Stderr
- }
- }
- }
-
- // Build the commands that we'll execute.
- pidFile := filepath.Join(bundlePath, "pid")
- args := append(append(append(options.Args, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs...), containerName)
- create := exec.Command(runtime, args...)
- create.Dir = bundlePath
- stdin, stdout, stderr := getCreateStdio()
- create.Stdin, create.Stdout, create.Stderr = stdin, stdout, stderr
- if create.SysProcAttr == nil {
- create.SysProcAttr = &syscall.SysProcAttr{}
- }
-
- args = append(options.Args, "start", containerName)
- start := exec.Command(runtime, args...)
- start.Dir = bundlePath
- start.Stderr = os.Stderr
-
- args = append(options.Args, "kill", containerName)
- kill := exec.Command(runtime, args...)
- kill.Dir = bundlePath
- kill.Stderr = os.Stderr
-
- args = append(options.Args, "delete", containerName)
- del := exec.Command(runtime, args...)
- del.Dir = bundlePath
- del.Stderr = os.Stderr
-
- // Actually create the container.
- logrus.Debugf("Running %q", create.Args)
- err = create.Run()
- if err != nil {
- return 1, errors.Wrapf(err, "error creating container for %v: %s", spec.Process.Args, runCollectOutput(errorFds, closeBeforeReadingErrorFds))
- }
- defer func() {
- err2 := del.Run()
- if err2 != nil {
- if err == nil {
- err = errors.Wrapf(err2, "error deleting container")
- } else {
- logrus.Infof("error deleting container: %v", err2)
- }
- }
- }()
-
- // Make sure we read the container's exit status when it exits.
- pidValue, err := ioutil.ReadFile(pidFile)
- if err != nil {
- return 1, errors.Wrapf(err, "error reading pid from %q", pidFile)
- }
- pid, err := strconv.Atoi(strings.TrimSpace(string(pidValue)))
- if err != nil {
- return 1, errors.Wrapf(err, "error parsing pid %s as a number", string(pidValue))
- }
- var reaping sync.WaitGroup
- reaping.Add(1)
- go func() {
- defer reaping.Done()
- var err error
- _, err = unix.Wait4(pid, &wstatus, 0, nil)
- if err != nil {
- wstatus = 0
- logrus.Errorf("error waiting for container child process %d: %v\n", pid, err)
- }
- }()
-
- if configureNetwork {
- teardown, err := runConfigureNetwork(isolation, options, configureNetworks, pid, containerName, spec.Process.Args)
- if teardown != nil {
- defer teardown()
- }
- if err != nil {
- return 1, err
- }
- }
-
- if copyPipes {
- // We don't need the ends of the pipes that belong to the container.
- stdin.Close()
- if stdout != nil {
- stdout.Close()
- }
- stderr.Close()
- }
-
- // Handle stdio for the container in the background.
- stdio.Add(1)
- go runCopyStdio(&stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec)
-
- // Start the container.
- logrus.Debugf("Running %q", start.Args)
- err = start.Run()
- if err != nil {
- return 1, errors.Wrapf(err, "error starting container")
- }
- stopped := false
- defer func() {
- if !stopped {
- err2 := kill.Run()
- if err2 != nil {
- if err == nil {
- err = errors.Wrapf(err2, "error stopping container")
- } else {
- logrus.Infof("error stopping container: %v", err2)
- }
- }
- }
- }()
-
- // Wait for the container to exit.
- for {
- now := time.Now()
- var state specs.State
- args = append(options.Args, "state", containerName)
- stat := exec.Command(runtime, args...)
- stat.Dir = bundlePath
- stat.Stderr = os.Stderr
- stateOutput, stateErr := stat.Output()
- if stateErr != nil {
- return 1, errors.Wrapf(stateErr, "error reading container state")
- }
- if err = json.Unmarshal(stateOutput, &state); err != nil {
- return 1, errors.Wrapf(stateErr, "error parsing container state %q", string(stateOutput))
- }
- switch state.Status {
- case "running":
- case "stopped":
- stopped = true
- default:
- return 1, errors.Errorf("container status unexpectedly changed to %q", state.Status)
- }
- if stopped {
- break
- }
- select {
- case <-finishedCopy:
- stopped = true
- case <-time.After(time.Until(now.Add(100 * time.Millisecond))):
- continue
- }
- if stopped {
- break
- }
- }
-
- // Close the writing end of the stop-handling-stdio notification pipe.
- unix.Close(finishCopy[1])
- // Wait for the stdio copy goroutine to flush.
- stdio.Wait()
- // Wait until we finish reading the exit status.
- reaping.Wait()
-
- return wstatus, nil
-}
-
-func runCollectOutput(fds, closeBeforeReadingFds []int) string {
- for _, fd := range closeBeforeReadingFds {
- unix.Close(fd)
- }
- var b bytes.Buffer
- buf := make([]byte, 8192)
- for _, fd := range fds {
- nread, err := unix.Read(fd, buf)
- if err != nil {
- if errno, isErrno := err.(syscall.Errno); isErrno {
- switch errno {
- default:
- logrus.Errorf("error reading from pipe %d: %v", fd, err)
- case syscall.EINTR, syscall.EAGAIN:
- }
- } else {
- logrus.Errorf("unable to wait for data from pipe %d: %v", fd, err)
- }
- continue
- }
- for nread > 0 {
- r := buf[:nread]
- if nwritten, err := b.Write(r); err != nil || nwritten != len(r) {
- if nwritten != len(r) {
- logrus.Errorf("error buffering data from pipe %d: %v", fd, err)
- break
- }
- }
- nread, err = unix.Read(fd, buf)
- if err != nil {
- if errno, isErrno := err.(syscall.Errno); isErrno {
- switch errno {
- default:
- logrus.Errorf("error reading from pipe %d: %v", fd, err)
- case syscall.EINTR, syscall.EAGAIN:
- }
- } else {
- logrus.Errorf("unable to wait for data from pipe %d: %v", fd, err)
- }
- break
- }
- }
- }
- return b.String()
-}
-func setupRootlessNetwork(pid int) (teardown func(), err error) {
- slirp4netns, err := exec.LookPath("slirp4netns")
- if err != nil {
- return nil, errors.Wrapf(err, "cannot find slirp4netns")
- }
-
- rootlessSlirpSyncR, rootlessSlirpSyncW, err := os.Pipe()
- if err != nil {
- return nil, errors.Wrapf(err, "cannot create slirp4netns sync pipe")
- }
- defer rootlessSlirpSyncR.Close()
-
- // Be sure there are no fds inherited to slirp4netns except the sync pipe
- files, err := ioutil.ReadDir("/proc/self/fd")
- if err != nil {
- return nil, errors.Wrapf(err, "cannot list open fds")
- }
- for _, f := range files {
- fd, err := strconv.Atoi(f.Name())
- if err != nil {
- return nil, errors.Wrapf(err, "cannot parse fd")
- }
- if fd == int(rootlessSlirpSyncW.Fd()) {
- continue
- }
- unix.CloseOnExec(fd)
- }
-
- cmd := exec.Command(slirp4netns, "--mtu", "65520", "-r", "3", "-c", fmt.Sprintf("%d", pid), "tap0")
- cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil
- cmd.ExtraFiles = []*os.File{rootlessSlirpSyncW}
-
- err = cmd.Start()
- rootlessSlirpSyncW.Close()
- if err != nil {
- return nil, errors.Wrapf(err, "cannot start slirp4netns")
- }
-
- b := make([]byte, 1)
- for {
- if err := rootlessSlirpSyncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
- return nil, errors.Wrapf(err, "error setting slirp4netns pipe timeout")
- }
- if _, err := rootlessSlirpSyncR.Read(b); err == nil {
- break
- } else {
- if os.IsTimeout(err) {
- // Check if the process is still running.
- var status syscall.WaitStatus
- _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "failed to read slirp4netns process status")
- }
- if status.Exited() || status.Signaled() {
- return nil, errors.New("slirp4netns failed")
- }
-
- continue
- }
- return nil, errors.Wrapf(err, "failed to read from slirp4netns sync pipe")
- }
- }
-
- return func() {
- cmd.Process.Kill()
- cmd.Wait()
- }, nil
-}
-
-func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) {
- var netconf, undo []*libcni.NetworkConfigList
-
- if isolation == IsolationOCIRootless {
- if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil && !ns.Host && ns.Path == "" {
- return setupRootlessNetwork(pid)
- }
- }
- // Scan for CNI configuration files.
- confdir := options.CNIConfigDir
- files, err := libcni.ConfFiles(confdir, []string{".conf"})
- if err != nil {
- return nil, errors.Wrapf(err, "error finding CNI networking configuration files named *.conf in directory %q", confdir)
- }
- lists, err := libcni.ConfFiles(confdir, []string{".conflist"})
- if err != nil {
- return nil, errors.Wrapf(err, "error finding CNI networking configuration list files named *.conflist in directory %q", confdir)
- }
- logrus.Debugf("CNI network configuration file list: %#v", append(files, lists...))
- // Read the CNI configuration files.
- for _, file := range files {
- nc, err := libcni.ConfFromFile(file)
- if err != nil {
- return nil, errors.Wrapf(err, "error loading networking configuration from file %q for %v", file, command)
- }
- if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !util.StringInSlice(nc.Network.Name, configureNetworks)) {
- if nc.Network.Name == "" {
- logrus.Debugf("configuration in %q has no name, skipping it", file)
- } else {
- logrus.Debugf("configuration in %q has name %q, skipping it", file, nc.Network.Name)
- }
- continue
- }
- cl, err := libcni.ConfListFromConf(nc)
- if err != nil {
- return nil, errors.Wrapf(err, "error converting networking configuration from file %q for %v", file, command)
- }
- logrus.Debugf("using network configuration from %q", file)
- netconf = append(netconf, cl)
- }
- for _, list := range lists {
- cl, err := libcni.ConfListFromFile(list)
- if err != nil {
- return nil, errors.Wrapf(err, "error loading networking configuration list from file %q for %v", list, command)
- }
- if len(configureNetworks) > 0 && (cl.Name == "" || !util.StringInSlice(cl.Name, configureNetworks)) {
- if cl.Name == "" {
- logrus.Debugf("configuration list in %q has no name, skipping it", list)
- } else {
- logrus.Debugf("configuration list in %q has name %q, skipping it", list, cl.Name)
- }
- continue
- }
- logrus.Debugf("using network configuration list from %q", list)
- netconf = append(netconf, cl)
- }
- // Make sure we can access the container's network namespace,
- // even after it exits, to successfully tear down the
- // interfaces. Ensure this by opening a handle to the network
- // namespace, and using our copy to both configure and
- // deconfigure it.
- netns := fmt.Sprintf("/proc/%d/ns/net", pid)
- netFD, err := unix.Open(netns, unix.O_RDONLY, 0)
- if err != nil {
- return nil, errors.Wrapf(err, "error opening network namespace for %v", command)
- }
- mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD)
- // Build our search path for the plugins.
- pluginPaths := strings.Split(options.CNIPluginPath, string(os.PathListSeparator))
- cni := libcni.CNIConfig{Path: pluginPaths}
- // Configure the interfaces.
- rtconf := make(map[*libcni.NetworkConfigList]*libcni.RuntimeConf)
- teardown = func() {
- for _, nc := range undo {
- if err = cni.DelNetworkList(context.Background(), nc, rtconf[nc]); err != nil {
- logrus.Errorf("error cleaning up network %v for %v: %v", rtconf[nc].IfName, command, err)
- }
- }
- unix.Close(netFD)
- }
- for i, nc := range netconf {
- // Build the runtime config for use with this network configuration.
- rtconf[nc] = &libcni.RuntimeConf{
- ContainerID: containerName,
- NetNS: mynetns,
- IfName: fmt.Sprintf("if%d", i),
- Args: [][2]string{},
- CapabilityArgs: map[string]interface{}{},
- }
- // Bring it up.
- _, err := cni.AddNetworkList(context.Background(), nc, rtconf[nc])
- if err != nil {
- return teardown, errors.Wrapf(err, "error configuring network list %v for %v", rtconf[nc].IfName, command)
- }
- // Add it to the list of networks to take down when the container process exits.
- undo = append([]*libcni.NetworkConfigList{nc}, undo...)
- }
- return teardown, nil
-}
-
-func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) {
- defer func() {
- unix.Close(finishCopy[0])
- if copyPipes {
- unix.Close(stdioPipe[unix.Stdin][1])
- unix.Close(stdioPipe[unix.Stdout][0])
- unix.Close(stdioPipe[unix.Stderr][0])
- }
- stdio.Done()
- finishedCopy <- struct{}{}
- }()
- // Map describing where data on an incoming descriptor should go.
- relayMap := make(map[int]int)
- // Map describing incoming and outgoing descriptors.
- readDesc := make(map[int]string)
- writeDesc := make(map[int]string)
- // Buffers.
- relayBuffer := make(map[int]*bytes.Buffer)
- // Set up the terminal descriptor or pipes for polling.
- if copyConsole {
- // Accept a connection over our listening socket.
- fd, err := runAcceptTerminal(consoleListener, spec.Process.ConsoleSize)
- if err != nil {
- logrus.Errorf("%v", err)
- return
- }
- terminalFD := fd
- // Input from our stdin, output from the terminal descriptor.
- relayMap[unix.Stdin] = terminalFD
- readDesc[unix.Stdin] = "stdin"
- relayBuffer[terminalFD] = new(bytes.Buffer)
- writeDesc[terminalFD] = "container terminal input"
- relayMap[terminalFD] = unix.Stdout
- readDesc[terminalFD] = "container terminal output"
- relayBuffer[unix.Stdout] = new(bytes.Buffer)
- writeDesc[unix.Stdout] = "output"
- // Set our terminal's mode to raw, to pass handling of special
- // terminal input to the terminal in the container.
- if terminal.IsTerminal(unix.Stdin) {
- if state, err := terminal.MakeRaw(unix.Stdin); err != nil {
- logrus.Warnf("error setting terminal state: %v", err)
- } else {
- defer func() {
- if err = terminal.Restore(unix.Stdin, state); err != nil {
- logrus.Errorf("unable to restore terminal state: %v", err)
- }
- }()
- }
- }
- }
- if copyPipes {
- // Input from our stdin, output from the stdout and stderr pipes.
- relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1]
- readDesc[unix.Stdin] = "stdin"
- relayBuffer[stdioPipe[unix.Stdin][1]] = new(bytes.Buffer)
- writeDesc[stdioPipe[unix.Stdin][1]] = "container stdin"
- relayMap[stdioPipe[unix.Stdout][0]] = unix.Stdout
- readDesc[stdioPipe[unix.Stdout][0]] = "container stdout"
- relayBuffer[unix.Stdout] = new(bytes.Buffer)
- writeDesc[unix.Stdout] = "stdout"
- relayMap[stdioPipe[unix.Stderr][0]] = unix.Stderr
- readDesc[stdioPipe[unix.Stderr][0]] = "container stderr"
- relayBuffer[unix.Stderr] = new(bytes.Buffer)
- writeDesc[unix.Stderr] = "stderr"
- }
- // Set our reading descriptors to non-blocking.
- for rfd, wfd := range relayMap {
- if err := unix.SetNonblock(rfd, true); err != nil {
- logrus.Errorf("error setting %s to nonblocking: %v", readDesc[rfd], err)
- return
- }
- if err := unix.SetNonblock(wfd, false); err != nil {
- logrus.Errorf("error setting descriptor %d (%s) blocking: %v", wfd, writeDesc[wfd], err)
- }
- }
- // Pass data back and forth.
- pollTimeout := -1
- for len(relayMap) > 0 {
- // Start building the list of descriptors to poll.
- pollFds := make([]unix.PollFd, 0, len(relayMap)+1)
- // Poll for a notification that we should stop handling stdio.
- pollFds = append(pollFds, unix.PollFd{Fd: int32(finishCopy[0]), Events: unix.POLLIN | unix.POLLHUP})
- // Poll on our reading descriptors.
- for rfd := range relayMap {
- pollFds = append(pollFds, unix.PollFd{Fd: int32(rfd), Events: unix.POLLIN | unix.POLLHUP})
- }
- buf := make([]byte, 8192)
- // Wait for new data from any input descriptor, or a notification that we're done.
- _, err := unix.Poll(pollFds, pollTimeout)
- if !util.LogIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) {
- return
- }
- removes := make(map[int]struct{})
- for _, pollFd := range pollFds {
- // If this descriptor's just been closed from the other end, mark it for
- // removal from the set that we're checking for.
- if pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
- removes[int(pollFd.Fd)] = struct{}{}
- }
- // If the descriptor was closed elsewhere, remove it from our list.
- if pollFd.Revents&unix.POLLNVAL != 0 {
- logrus.Debugf("error polling descriptor %s: closed?", readDesc[int(pollFd.Fd)])
- removes[int(pollFd.Fd)] = struct{}{}
- }
- // If the POLLIN flag isn't set, then there's no data to be read from this descriptor.
- if pollFd.Revents&unix.POLLIN == 0 {
- // If we're using pipes and it's our stdin and it's closed, close the writing
- // end of the corresponding pipe.
- if copyPipes && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 {
- unix.Close(stdioPipe[unix.Stdin][1])
- stdioPipe[unix.Stdin][1] = -1
- }
- continue
- }
- // Read whatever there is to be read.
- readFD := int(pollFd.Fd)
- writeFD, needToRelay := relayMap[readFD]
- if needToRelay {
- n, err := unix.Read(readFD, buf)
- if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to read %s data: %v", readDesc[readFD], err)) {
- return
- }
- // If it's zero-length on our stdin and we're
- // using pipes, it's an EOF, so close the stdin
- // pipe's writing end.
- if n == 0 && copyPipes && int(pollFd.Fd) == unix.Stdin {
- unix.Close(stdioPipe[unix.Stdin][1])
- stdioPipe[unix.Stdin][1] = -1
- }
- if n > 0 {
- // Buffer the data in case we get blocked on where they need to go.
- nwritten, err := relayBuffer[writeFD].Write(buf[:n])
- if err != nil {
- logrus.Debugf("buffer: %v", err)
- continue
- }
- if nwritten != n {
- logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", n, nwritten)
- continue
- }
- // If this is the last of the data we'll be able to read from this
- // descriptor, read all that there is to read.
- for pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
- nr, err := unix.Read(readFD, buf)
- util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err))
- if nr <= 0 {
- break
- }
- nwritten, err := relayBuffer[writeFD].Write(buf[:nr])
- if err != nil {
- logrus.Debugf("buffer: %v", err)
- break
- }
- if nwritten != nr {
- logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten)
- break
- }
- }
- }
- }
- }
- // Try to drain the output buffers. Set the default timeout
- // for the next poll() to 100ms if we still have data to write.
- pollTimeout = -1
- for writeFD := range relayBuffer {
- if relayBuffer[writeFD].Len() > 0 {
- n, err := unix.Write(writeFD, relayBuffer[writeFD].Bytes())
- if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to write %s data: %v", writeDesc[writeFD], err)) {
- return
- }
- if n > 0 {
- relayBuffer[writeFD].Next(n)
- }
- }
- if relayBuffer[writeFD].Len() > 0 {
- pollTimeout = 100
- }
- }
- // Remove any descriptors which we don't need to poll any more from the poll descriptor list.
- for remove := range removes {
- delete(relayMap, remove)
- }
- // If the we-can-return pipe had anything for us, we're done.
- for _, pollFd := range pollFds {
- if int(pollFd.Fd) == finishCopy[0] && pollFd.Revents != 0 {
- // The pipe is closed, indicating that we can stop now.
- return
- }
- }
- }
-}
-
-func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Box) (int, error) {
- defer consoleListener.Close()
- c, err := consoleListener.AcceptUnix()
- if err != nil {
- return -1, errors.Wrapf(err, "error accepting socket descriptor connection")
- }
- defer c.Close()
- // Expect a control message over our new connection.
- b := make([]byte, 8192)
- oob := make([]byte, 8192)
- n, oobn, _, _, err := c.ReadMsgUnix(b, oob)
- if err != nil {
- return -1, errors.Wrapf(err, "error reading socket descriptor")
- }
- if n > 0 {
- logrus.Debugf("socket descriptor is for %q", string(b[:n]))
- }
- if oobn > len(oob) {
- return -1, errors.Errorf("too much out-of-bounds data (%d bytes)", oobn)
- }
- // Parse the control message.
- scm, err := unix.ParseSocketControlMessage(oob[:oobn])
- if err != nil {
- return -1, errors.Wrapf(err, "error parsing out-of-bound data as a socket control message")
- }
- logrus.Debugf("control messages: %v", scm)
- // Expect to get a descriptor.
- terminalFD := -1
- for i := range scm {
- fds, err := unix.ParseUnixRights(&scm[i])
- if err != nil {
- return -1, errors.Wrapf(err, "error parsing unix rights control message: %v", &scm[i])
- }
- logrus.Debugf("fds: %v", fds)
- if len(fds) == 0 {
- continue
- }
- terminalFD = fds[0]
- break
- }
- if terminalFD == -1 {
- return -1, errors.Errorf("unable to read terminal descriptor")
- }
- // Set the pseudoterminal's size to the configured size, or our own.
- winsize := &unix.Winsize{}
- if terminalSize != nil {
- // Use configured sizes.
- winsize.Row = uint16(terminalSize.Height)
- winsize.Col = uint16(terminalSize.Width)
- } else {
- if terminal.IsTerminal(unix.Stdin) {
- // Use the size of our terminal.
- if winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ); err != nil {
- logrus.Warnf("error reading size of controlling terminal: %v", err)
- winsize.Row = 0
- winsize.Col = 0
- }
- }
- }
- if winsize.Row != 0 && winsize.Col != 0 {
- if err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize); err != nil {
- logrus.Warnf("error setting size of container pseudoterminal: %v", err)
- }
- // FIXME - if we're connected to a terminal, we should
- // be passing the updated terminal size down when we
- // receive a SIGWINCH.
- }
- return terminalFD, nil
-}
-
-// Create pipes to use for relaying stdio.
-func runMakeStdioPipe(uid, gid int) ([][]int, error) {
- stdioPipe := make([][]int, 3)
- for i := range stdioPipe {
- stdioPipe[i] = make([]int, 2)
- if err := unix.Pipe(stdioPipe[i]); err != nil {
- return nil, errors.Wrapf(err, "error creating pipe for container FD %d", i)
- }
- }
- if err := unix.Fchown(stdioPipe[unix.Stdin][0], uid, gid); err != nil {
- return nil, errors.Wrapf(err, "error setting owner of stdin pipe descriptor")
- }
- if err := unix.Fchown(stdioPipe[unix.Stdout][1], uid, gid); err != nil {
- return nil, errors.Wrapf(err, "error setting owner of stdout pipe descriptor")
- }
- if err := unix.Fchown(stdioPipe[unix.Stderr][1], uid, gid); err != nil {
- return nil, errors.Wrapf(err, "error setting owner of stderr pipe descriptor")
- }
- return stdioPipe, nil
-}
diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go
index a7519a092..1acf655eb 100644
--- a/vendor/github.com/containers/buildah/run_linux.go
+++ b/vendor/github.com/containers/buildah/run_linux.go
@@ -3,9 +3,44 @@
package buildah
import (
+ "bytes"
+ "context"
+ "encoding/json"
"fmt"
- "golang.org/x/sys/unix"
+ "io"
+ "io/ioutil"
+ "net"
"os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/buildah/bind"
+ "github.com/containers/buildah/chroot"
+ "github.com/containers/buildah/pkg/secrets"
+ "github.com/containers/buildah/pkg/unshare"
+ "github.com/containers/buildah/util"
+ "github.com/containers/storage/pkg/idtools"
+ "github.com/containers/storage/pkg/ioutils"
+ "github.com/containers/storage/pkg/reexec"
+ "github.com/containers/storage/pkg/stringid"
+ "github.com/docker/go-units"
+ "github.com/docker/libnetwork/resolvconf"
+ "github.com/docker/libnetwork/types"
+ "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+ "github.com/opencontainers/selinux/go-selinux/label"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
+ "golang.org/x/sys/unix"
)
func setChildProcess() error {
@@ -15,3 +50,1988 @@ func setChildProcess() error {
}
return nil
}
+
+// Run runs the specified command in the container's root filesystem.
+func (b *Builder) Run(command []string, options RunOptions) error {
+ p, err := ioutil.TempDir("", Package)
+ if err != nil {
+ return errors.Wrapf(err, "run: error creating temporary directory under %q", os.TempDir())
+ }
+ // On some hosts like AH, /tmp is a symlink and we need an
+ // absolute path.
+ path, err := filepath.EvalSymlinks(p)
+ if err != nil {
+ return errors.Wrapf(err, "run: error evaluating %q for symbolic links", p)
+ }
+ 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)
+ }
+ }()
+
+ gp, err := generate.New("linux")
+ if err != nil {
+ return errors.Wrapf(err, "error generating new 'linux' runtime spec")
+ }
+ g := &gp
+
+ isolation := options.Isolation
+ if isolation == IsolationDefault {
+ isolation = b.Isolation
+ if isolation == IsolationDefault {
+ isolation = IsolationOCI
+ }
+ }
+ if err := checkAndOverrideIsolationOptions(isolation, &options); err != nil {
+ return err
+ }
+
+ b.configureEnvironment(g, options)
+
+ if b.CommonBuildOpts == nil {
+ return errors.Errorf("Invalid format on container you must recreate the container")
+ }
+
+ if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil {
+ return err
+ }
+
+ if options.WorkingDir != "" {
+ g.SetProcessCwd(options.WorkingDir)
+ } else if b.WorkDir() != "" {
+ g.SetProcessCwd(b.WorkDir())
+ }
+ setupSelinux(g, b.ProcessLabel, b.MountLabel)
+ mountPoint, err := b.Mount(b.MountLabel)
+ if err != nil {
+ return errors.Wrapf(err, "error mounting container %q", b.ContainerID)
+ }
+ defer func() {
+ if err := b.Unmount(); err != nil {
+ logrus.Errorf("error unmounting container: %v", err)
+ }
+ }()
+ g.SetRootPath(mountPoint)
+ if len(command) > 0 {
+ command = runLookupPath(g, command)
+ g.SetProcessArgs(command)
+ } else {
+ g.SetProcessArgs(nil)
+ }
+
+ setupMaskedPaths(g)
+ setupReadOnlyPaths(g)
+
+ setupTerminal(g, options.Terminal, options.TerminalSize)
+
+ configureNetwork, configureNetworks, err := b.configureNamespaces(g, options)
+ if err != nil {
+ return err
+ }
+
+ if err := b.configureUIDGID(g, mountPoint, options); err != nil {
+ return err
+ }
+
+ g.SetProcessApparmorProfile(b.CommonBuildOpts.ApparmorProfile)
+
+ // Now grab the spec from the generator. Set the generator to nil so that future contributors
+ // will quickly be able to tell that they're supposed to be modifying the spec directly from here.
+ spec := g.Config
+ g = nil
+
+ logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd))
+ if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil && !os.IsExist(err) {
+ return errors.Wrapf(err, "error ensuring working directory %q exists", spec.Process.Cwd)
+ }
+
+ // Set the seccomp configuration using the specified profile name. Some syscalls are
+ // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot),
+ // so we sorted out the capabilities lists first.
+ if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil {
+ return err
+ }
+
+ // Figure out who owns files that will appear to be owned by UID/GID 0 in the container.
+ rootUID, rootGID, err := util.GetHostRootIDs(spec)
+ if err != nil {
+ return err
+ }
+ rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}
+
+ bindFiles := make(map[string]string)
+ namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...)
+ volumes := b.Volumes()
+
+ if !contains(volumes, "/etc/hosts") {
+ hostFile, err := b.generateHosts(path, spec.Hostname, b.CommonBuildOpts.AddHost, rootIDPair)
+ if err != nil {
+ return err
+ }
+ bindFiles["/etc/hosts"] = hostFile
+ }
+
+ if !contains(volumes, "/etc/resolv.conf") {
+ resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair, b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions)
+ if err != nil {
+ return err
+ }
+ bindFiles["/etc/resolv.conf"] = resolvFile
+ }
+
+ err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions)
+ if err != nil {
+ return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID)
+ }
+
+ if options.CNIConfigDir == "" {
+ options.CNIConfigDir = b.CNIConfigDir
+ if b.CNIConfigDir == "" {
+ options.CNIConfigDir = util.DefaultCNIConfigDir
+ }
+ }
+ if options.CNIPluginPath == "" {
+ options.CNIPluginPath = b.CNIPluginPath
+ if b.CNIPluginPath == "" {
+ options.CNIPluginPath = util.DefaultCNIPluginPath
+ }
+ }
+
+ switch isolation {
+ case IsolationOCI:
+ var moreCreateArgs []string
+ if options.NoPivot {
+ moreCreateArgs = []string{"--no-pivot"}
+ } else {
+ moreCreateArgs = nil
+ }
+ err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path))
+ case IsolationChroot:
+ err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr)
+ case IsolationOCIRootless:
+ moreCreateArgs := []string{"--no-new-keyring"}
+ if options.NoPivot {
+ moreCreateArgs = append(moreCreateArgs, "--no-pivot")
+ }
+ if err := setupRootlessSpecChanges(spec, path, rootUID, rootGID); err != nil {
+ return err
+ }
+ err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path))
+ default:
+ err = errors.Errorf("don't know how to run this command")
+ }
+ return err
+}
+
+func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) error {
+ // Resources - CPU
+ if commonOpts.CPUPeriod != 0 {
+ g.SetLinuxResourcesCPUPeriod(commonOpts.CPUPeriod)
+ }
+ if commonOpts.CPUQuota != 0 {
+ g.SetLinuxResourcesCPUQuota(commonOpts.CPUQuota)
+ }
+ if commonOpts.CPUShares != 0 {
+ g.SetLinuxResourcesCPUShares(commonOpts.CPUShares)
+ }
+ if commonOpts.CPUSetCPUs != "" {
+ g.SetLinuxResourcesCPUCpus(commonOpts.CPUSetCPUs)
+ }
+ if commonOpts.CPUSetMems != "" {
+ g.SetLinuxResourcesCPUMems(commonOpts.CPUSetMems)
+ }
+
+ // Resources - Memory
+ if commonOpts.Memory != 0 {
+ g.SetLinuxResourcesMemoryLimit(commonOpts.Memory)
+ }
+ if commonOpts.MemorySwap != 0 {
+ g.SetLinuxResourcesMemorySwap(commonOpts.MemorySwap)
+ }
+
+ // cgroup membership
+ if commonOpts.CgroupParent != "" {
+ g.SetLinuxCgroupsPath(commonOpts.CgroupParent)
+ }
+
+ // Other process resource limits
+ if err := addRlimits(commonOpts.Ulimit, g); err != nil {
+ return err
+ }
+
+ logrus.Debugf("Resources: %#v", commonOpts)
+ return nil
+}
+
+func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string, rootUID, rootGID int) ([]specs.Mount, error) {
+ var mounts []specs.Mount
+ hostOwner := idtools.IDPair{UID: rootUID, GID: rootGID}
+ // Add temporary copies of the contents of volume locations at the
+ // volume locations, unless we already have something there.
+ for _, volume := range builtinVolumes {
+ subdir := digest.Canonical.FromString(volume).Hex()
+ volumePath := filepath.Join(containerDir, "buildah-volumes", subdir)
+ srcPath := filepath.Join(mountPoint, volume)
+ initializeVolume := false
+ // If we need to, initialize the volume path's initial contents.
+ if _, err := os.Stat(volumePath); err != nil {
+ if !os.IsNotExist(err) {
+ return nil, errors.Wrapf(err, "failed to stat %q for volume %q", volumePath, volume)
+ }
+ logrus.Debugf("setting up built-in volume at %q", volumePath)
+ if err = os.MkdirAll(volumePath, 0755); err != nil {
+ return nil, errors.Wrapf(err, "error creating directory %q for volume %q", volumePath, volume)
+ }
+ if err = label.Relabel(volumePath, mountLabel, false); err != nil {
+ return nil, errors.Wrapf(err, "error relabeling directory %q for volume %q", volumePath, volume)
+ }
+ initializeVolume = true
+ }
+ stat, err := os.Stat(srcPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume)
+ }
+ if err = idtools.MkdirAllAndChownNew(srcPath, 0755, hostOwner); err != nil {
+ return nil, errors.Wrapf(err, "error creating directory %q for volume %q", srcPath, volume)
+ }
+ if stat, err = os.Stat(srcPath); err != nil {
+ return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume)
+ }
+ }
+ if initializeVolume {
+ if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil {
+ return nil, errors.Wrapf(err, "failed to chmod %q for volume %q", volumePath, volume)
+ }
+ if err = os.Chown(volumePath, int(stat.Sys().(*syscall.Stat_t).Uid), int(stat.Sys().(*syscall.Stat_t).Gid)); err != nil {
+ return nil, errors.Wrapf(err, "error chowning directory %q for volume %q", volumePath, volume)
+ }
+ if err = copyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(errors.Cause(err)) {
+ return nil, errors.Wrapf(err, "error populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath)
+ }
+ }
+ // Add the bind mount.
+ mounts = append(mounts, specs.Mount{
+ Source: volumePath,
+ Destination: volume,
+ Type: "bind",
+ Options: []string{"bind"},
+ })
+ }
+ return mounts, nil
+}
+
+func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error {
+ // Start building a new list of mounts.
+ var mounts []specs.Mount
+ haveMount := func(destination string) bool {
+ for _, mount := range mounts {
+ if mount.Destination == destination {
+ // Already have something to mount there.
+ return true
+ }
+ }
+ return false
+ }
+
+ ipc := namespaceOptions.Find(string(specs.IPCNamespace))
+ hostIPC := ipc == nil || ipc.Host
+ net := namespaceOptions.Find(string(specs.NetworkNamespace))
+ hostNetwork := net == nil || net.Host
+ user := namespaceOptions.Find(string(specs.UserNamespace))
+ hostUser := user == nil || user.Host
+
+ // Copy mounts from the generated list.
+ mountCgroups := true
+ specMounts := []specs.Mount{}
+ for _, specMount := range spec.Mounts {
+ // Override some of the mounts from the generated list if we're doing different things with namespaces.
+ if specMount.Destination == "/dev/shm" {
+ specMount.Options = []string{"nosuid", "noexec", "nodev", "mode=1777", "size=" + shmSize}
+ if hostIPC && !hostUser {
+ if _, err := os.Stat("/dev/shm"); err != nil && os.IsNotExist(err) {
+ logrus.Debugf("/dev/shm is not present, not binding into container")
+ continue
+ }
+ specMount = specs.Mount{
+ Source: "/dev/shm",
+ Type: "bind",
+ Destination: "/dev/shm",
+ Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"},
+ }
+ }
+ }
+ if specMount.Destination == "/dev/mqueue" {
+ if hostIPC && !hostUser {
+ if _, err := os.Stat("/dev/mqueue"); err != nil && os.IsNotExist(err) {
+ logrus.Debugf("/dev/mqueue is not present, not binding into container")
+ continue
+ }
+ specMount = specs.Mount{
+ Source: "/dev/mqueue",
+ Type: "bind",
+ Destination: "/dev/mqueue",
+ Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"},
+ }
+ }
+ }
+ if specMount.Destination == "/sys" {
+ if hostNetwork && !hostUser {
+ mountCgroups = false
+ if _, err := os.Stat("/sys"); err != nil && os.IsNotExist(err) {
+ logrus.Debugf("/sys is not present, not binding into container")
+ continue
+ }
+ specMount = specs.Mount{
+ Source: "/sys",
+ Type: "bind",
+ Destination: "/sys",
+ Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev", "ro"},
+ }
+ }
+ }
+ specMounts = append(specMounts, specMount)
+ }
+
+ // Add a mount for the cgroups filesystem, unless we're already
+ // recursively bind mounting all of /sys, in which case we shouldn't
+ // bother with it.
+ sysfsMount := []specs.Mount{}
+ if mountCgroups {
+ sysfsMount = []specs.Mount{{
+ Destination: "/sys/fs/cgroup",
+ Type: "cgroup",
+ Source: "cgroup",
+ Options: []string{bind.NoBindOption, "nosuid", "noexec", "nodev", "relatime", "ro"},
+ }}
+ }
+
+ // Get the list of files we need to bind into the container.
+ bindFileMounts, err := runSetupBoundFiles(bundlePath, bindFiles)
+ if err != nil {
+ return err
+ }
+
+ // After this point we need to know the per-container persistent storage directory.
+ cdir, err := b.store.ContainerDirectory(b.ContainerID)
+ if err != nil {
+ return errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID)
+ }
+
+ // Figure out which UID and GID to tell the secrets package to use
+ // for files that it creates.
+ rootUID, rootGID, err := util.GetHostRootIDs(spec)
+ if err != nil {
+ return err
+ }
+
+ // Get the list of secrets mounts.
+ secretMounts := secrets.SecretMountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, cdir, int(rootUID), int(rootGID), unshare.IsRootless())
+
+ // Add temporary copies of the contents of volume locations at the
+ // volume locations, unless we already have something there.
+ copyWithTar := b.copyWithTar(nil, nil)
+ builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID))
+ if err != nil {
+ return err
+ }
+
+ // Get the list of explicitly-specified volume mounts.
+ volumes, err := runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts, optionMounts)
+ if err != nil {
+ return err
+ }
+
+ // Add them all, in the preferred order, except where they conflict with something that was previously added.
+ for _, mount := range append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...) {
+ if haveMount(mount.Destination) {
+ // Already mounting something there, no need to bother with this one.
+ continue
+ }
+ // Add the mount.
+ mounts = append(mounts, mount)
+ }
+
+ // Set the list in the spec.
+ spec.Mounts = mounts
+ return nil
+}
+
+// addNetworkConfig copies files from host and sets them up to bind mount into container
+func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string) (string, error) {
+ stat, err := os.Stat(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID)
+ }
+ contents, err := ioutil.ReadFile(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to read %s", hostPath)
+ }
+
+ search := resolvconf.GetSearchDomains(contents)
+ nameservers := resolvconf.GetNameservers(contents, types.IP)
+ options := resolvconf.GetOptions(contents)
+
+ if len(dnsSearch) > 0 {
+ search = dnsSearch
+ }
+ if len(dnsServers) != 0 {
+ dns, err := getDNSIP(dnsServers)
+ if err != nil {
+ return "", errors.Wrapf(err, "error getting dns servers")
+ }
+ nameservers = []string{}
+ for _, server := range dns {
+ nameservers = append(nameservers, server.String())
+ }
+ }
+
+ if len(dnsOptions) != 0 {
+ options = dnsOptions
+ }
+
+ cfile := filepath.Join(rdir, filepath.Base(hostPath))
+ if _, err = resolvconf.Build(cfile, nameservers, search, options); err != nil {
+ return "", errors.Wrapf(err, "error building resolv.conf for container %s", b.ContainerID)
+ }
+
+ uid := int(stat.Sys().(*syscall.Stat_t).Uid)
+ gid := int(stat.Sys().(*syscall.Stat_t).Gid)
+ if chownOpts != nil {
+ uid = chownOpts.UID
+ gid = chownOpts.GID
+ }
+ if err = os.Chown(cfile, uid, gid); err != nil {
+ return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID)
+ }
+
+ if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
+ return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
+ }
+
+ return cfile, nil
+}
+
+// generateHosts creates a containers hosts file
+func (b *Builder) generateHosts(rdir, hostname string, addHosts []string, chownOpts *idtools.IDPair) (string, error) {
+ hostPath := "/etc/hosts"
+ stat, err := os.Stat(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID)
+ }
+
+ hosts := bytes.NewBufferString("# Generated by Buildah\n")
+ orig, err := ioutil.ReadFile(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to read %s", hostPath)
+ }
+ hosts.Write(orig)
+ for _, host := range addHosts {
+ // verify the host format
+ values := strings.SplitN(host, ":", 2)
+ if len(values) != 2 {
+ return "", errors.Errorf("unable to parse host entry %q: incorrect format", host)
+ }
+ if values[0] == "" {
+ return "", errors.Errorf("hostname in host entry %q is empty", host)
+ }
+ if values[1] == "" {
+ return "", errors.Errorf("IP address in host entry %q is empty", host)
+ }
+ hosts.Write([]byte(fmt.Sprintf("%s\t%s\n", values[1], values[0])))
+ }
+
+ if hostname != "" {
+ hosts.Write([]byte(fmt.Sprintf("127.0.0.1 %s\n", hostname)))
+ hosts.Write([]byte(fmt.Sprintf("::1 %s\n", hostname)))
+ }
+ cfile := filepath.Join(rdir, filepath.Base(hostPath))
+ if err = ioutils.AtomicWriteFile(cfile, hosts.Bytes(), stat.Mode().Perm()); err != nil {
+ return "", errors.Wrapf(err, "error writing /etc/hosts into the container")
+ }
+ uid := int(stat.Sys().(*syscall.Stat_t).Uid)
+ gid := int(stat.Sys().(*syscall.Stat_t).Gid)
+ if chownOpts != nil {
+ uid = chownOpts.UID
+ gid = chownOpts.GID
+ }
+ if err = os.Chown(cfile, uid, gid); err != nil {
+ return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID)
+ }
+ if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
+ return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
+ }
+
+ return cfile, nil
+}
+
+func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) {
+ switch terminalPolicy {
+ case DefaultTerminal:
+ onTerminal := terminal.IsTerminal(unix.Stdin) && terminal.IsTerminal(unix.Stdout) && terminal.IsTerminal(unix.Stderr)
+ if onTerminal {
+ logrus.Debugf("stdio is a terminal, defaulting to using a terminal")
+ } else {
+ logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal")
+ }
+ g.SetProcessTerminal(onTerminal)
+ case WithTerminal:
+ g.SetProcessTerminal(true)
+ case WithoutTerminal:
+ g.SetProcessTerminal(false)
+ }
+ if terminalSize != nil {
+ g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height)
+ }
+}
+
+func runUsingRuntime(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
+ // Lock the caller to a single OS-level thread.
+ runtime.LockOSThread()
+
+ // Set up bind mounts for things that a namespaced user might not be able to get to directly.
+ unmountAll, err := bind.SetupIntermediateMountNamespace(spec, bundlePath)
+ if unmountAll != nil {
+ defer func() {
+ if err := unmountAll(); err != nil {
+ logrus.Error(err)
+ }
+ }()
+ }
+ if err != nil {
+ return 1, err
+ }
+
+ // Write the runtime configuration.
+ specbytes, err := json.Marshal(spec)
+ if err != nil {
+ return 1, errors.Wrapf(err, "error encoding configuration %#v as json", spec)
+ }
+ if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0600); err != nil {
+ return 1, errors.Wrapf(err, "error storing runtime configuration in %q", filepath.Join(bundlePath, "config.json"))
+ }
+
+ logrus.Debugf("config = %v", string(specbytes))
+
+ // Decide which runtime to use.
+ runtime := options.Runtime
+ if runtime == "" {
+ runtime = util.Runtime()
+ }
+
+ // Default to just passing down our stdio.
+ getCreateStdio := func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
+ return os.Stdin, os.Stdout, os.Stderr
+ }
+
+ // Figure out how we're doing stdio handling, and create pipes and sockets.
+ var stdio sync.WaitGroup
+ var consoleListener *net.UnixListener
+ var errorFds, closeBeforeReadingErrorFds []int
+ stdioPipe := make([][]int, 3)
+ copyConsole := false
+ copyPipes := false
+ finishCopy := make([]int, 2)
+ if err = unix.Pipe(finishCopy); err != nil {
+ return 1, errors.Wrapf(err, "error creating pipe for notifying to stop stdio")
+ }
+ finishedCopy := make(chan struct{})
+ if spec.Process != nil {
+ if spec.Process.Terminal {
+ copyConsole = true
+ // Create a listening socket for accepting the container's terminal's PTY master.
+ socketPath := filepath.Join(bundlePath, "console.sock")
+ consoleListener, err = net.ListenUnix("unix", &net.UnixAddr{Name: socketPath, Net: "unix"})
+ if err != nil {
+ return 1, errors.Wrapf(err, "error creating socket %q to receive terminal descriptor", consoleListener.Addr())
+ }
+ // Add console socket arguments.
+ moreCreateArgs = append(moreCreateArgs, "--console-socket", socketPath)
+ } else {
+ copyPipes = true
+ // Figure out who should own the pipes.
+ uid, gid, err := util.GetHostRootIDs(spec)
+ if err != nil {
+ return 1, err
+ }
+ // Create stdio pipes.
+ if stdioPipe, err = runMakeStdioPipe(int(uid), int(gid)); err != nil {
+ return 1, err
+ }
+ errorFds = []int{stdioPipe[unix.Stdout][0], stdioPipe[unix.Stderr][0]}
+ closeBeforeReadingErrorFds = []int{stdioPipe[unix.Stdout][1], stdioPipe[unix.Stderr][1]}
+ // Set stdio to our pipes.
+ getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
+ stdin := os.NewFile(uintptr(stdioPipe[unix.Stdin][0]), "/dev/stdin")
+ stdout := os.NewFile(uintptr(stdioPipe[unix.Stdout][1]), "/dev/stdout")
+ stderr := os.NewFile(uintptr(stdioPipe[unix.Stderr][1]), "/dev/stderr")
+ return stdin, stdout, stderr
+ }
+ }
+ } else {
+ if options.Quiet {
+ // Discard stdout.
+ getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) {
+ return os.Stdin, nil, os.Stderr
+ }
+ }
+ }
+
+ // Build the commands that we'll execute.
+ pidFile := filepath.Join(bundlePath, "pid")
+ args := append(append(append(options.Args, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs...), containerName)
+ create := exec.Command(runtime, args...)
+ create.Dir = bundlePath
+ stdin, stdout, stderr := getCreateStdio()
+ create.Stdin, create.Stdout, create.Stderr = stdin, stdout, stderr
+ if create.SysProcAttr == nil {
+ create.SysProcAttr = &syscall.SysProcAttr{}
+ }
+
+ args = append(options.Args, "start", containerName)
+ start := exec.Command(runtime, args...)
+ start.Dir = bundlePath
+ start.Stderr = os.Stderr
+
+ args = append(options.Args, "kill", containerName)
+ kill := exec.Command(runtime, args...)
+ kill.Dir = bundlePath
+ kill.Stderr = os.Stderr
+
+ args = append(options.Args, "delete", containerName)
+ del := exec.Command(runtime, args...)
+ del.Dir = bundlePath
+ del.Stderr = os.Stderr
+
+ // Actually create the container.
+ logrus.Debugf("Running %q", create.Args)
+ err = create.Run()
+ if err != nil {
+ return 1, errors.Wrapf(err, "error creating container for %v: %s", spec.Process.Args, runCollectOutput(errorFds, closeBeforeReadingErrorFds))
+ }
+ defer func() {
+ err2 := del.Run()
+ if err2 != nil {
+ if err == nil {
+ err = errors.Wrapf(err2, "error deleting container")
+ } else {
+ logrus.Infof("error deleting container: %v", err2)
+ }
+ }
+ }()
+
+ // Make sure we read the container's exit status when it exits.
+ pidValue, err := ioutil.ReadFile(pidFile)
+ if err != nil {
+ return 1, errors.Wrapf(err, "error reading pid from %q", pidFile)
+ }
+ pid, err := strconv.Atoi(strings.TrimSpace(string(pidValue)))
+ if err != nil {
+ return 1, errors.Wrapf(err, "error parsing pid %s as a number", string(pidValue))
+ }
+ var reaping sync.WaitGroup
+ reaping.Add(1)
+ go func() {
+ defer reaping.Done()
+ var err error
+ _, err = unix.Wait4(pid, &wstatus, 0, nil)
+ if err != nil {
+ wstatus = 0
+ logrus.Errorf("error waiting for container child process %d: %v\n", pid, err)
+ }
+ }()
+
+ if configureNetwork {
+ teardown, err := runConfigureNetwork(isolation, options, configureNetworks, pid, containerName, spec.Process.Args)
+ if teardown != nil {
+ defer teardown()
+ }
+ if err != nil {
+ return 1, err
+ }
+ }
+
+ if copyPipes {
+ // We don't need the ends of the pipes that belong to the container.
+ stdin.Close()
+ if stdout != nil {
+ stdout.Close()
+ }
+ stderr.Close()
+ }
+
+ // Handle stdio for the container in the background.
+ stdio.Add(1)
+ go runCopyStdio(&stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec)
+
+ // Start the container.
+ logrus.Debugf("Running %q", start.Args)
+ err = start.Run()
+ if err != nil {
+ return 1, errors.Wrapf(err, "error starting container")
+ }
+ stopped := false
+ defer func() {
+ if !stopped {
+ err2 := kill.Run()
+ if err2 != nil {
+ if err == nil {
+ err = errors.Wrapf(err2, "error stopping container")
+ } else {
+ logrus.Infof("error stopping container: %v", err2)
+ }
+ }
+ }
+ }()
+
+ // Wait for the container to exit.
+ for {
+ now := time.Now()
+ var state specs.State
+ args = append(options.Args, "state", containerName)
+ stat := exec.Command(runtime, args...)
+ stat.Dir = bundlePath
+ stat.Stderr = os.Stderr
+ stateOutput, stateErr := stat.Output()
+ if stateErr != nil {
+ return 1, errors.Wrapf(stateErr, "error reading container state")
+ }
+ if err = json.Unmarshal(stateOutput, &state); err != nil {
+ return 1, errors.Wrapf(stateErr, "error parsing container state %q", string(stateOutput))
+ }
+ switch state.Status {
+ case "running":
+ case "stopped":
+ stopped = true
+ default:
+ return 1, errors.Errorf("container status unexpectedly changed to %q", state.Status)
+ }
+ if stopped {
+ break
+ }
+ select {
+ case <-finishedCopy:
+ stopped = true
+ case <-time.After(time.Until(now.Add(100 * time.Millisecond))):
+ continue
+ }
+ if stopped {
+ break
+ }
+ }
+
+ // Close the writing end of the stop-handling-stdio notification pipe.
+ unix.Close(finishCopy[1])
+ // Wait for the stdio copy goroutine to flush.
+ stdio.Wait()
+ // Wait until we finish reading the exit status.
+ reaping.Wait()
+
+ return wstatus, nil
+}
+
+func runCollectOutput(fds, closeBeforeReadingFds []int) string {
+ for _, fd := range closeBeforeReadingFds {
+ unix.Close(fd)
+ }
+ var b bytes.Buffer
+ buf := make([]byte, 8192)
+ for _, fd := range fds {
+ nread, err := unix.Read(fd, buf)
+ if err != nil {
+ if errno, isErrno := err.(syscall.Errno); isErrno {
+ switch errno {
+ default:
+ logrus.Errorf("error reading from pipe %d: %v", fd, err)
+ case syscall.EINTR, syscall.EAGAIN:
+ }
+ } else {
+ logrus.Errorf("unable to wait for data from pipe %d: %v", fd, err)
+ }
+ continue
+ }
+ for nread > 0 {
+ r := buf[:nread]
+ if nwritten, err := b.Write(r); err != nil || nwritten != len(r) {
+ if nwritten != len(r) {
+ logrus.Errorf("error buffering data from pipe %d: %v", fd, err)
+ break
+ }
+ }
+ nread, err = unix.Read(fd, buf)
+ if err != nil {
+ if errno, isErrno := err.(syscall.Errno); isErrno {
+ switch errno {
+ default:
+ logrus.Errorf("error reading from pipe %d: %v", fd, err)
+ case syscall.EINTR, syscall.EAGAIN:
+ }
+ } else {
+ logrus.Errorf("unable to wait for data from pipe %d: %v", fd, err)
+ }
+ break
+ }
+ }
+ }
+ return b.String()
+}
+
+func setupRootlessNetwork(pid int) (teardown func(), err error) {
+ slirp4netns, err := exec.LookPath("slirp4netns")
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot find slirp4netns")
+ }
+
+ rootlessSlirpSyncR, rootlessSlirpSyncW, err := os.Pipe()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot create slirp4netns sync pipe")
+ }
+ defer rootlessSlirpSyncR.Close()
+
+ // Be sure there are no fds inherited to slirp4netns except the sync pipe
+ files, err := ioutil.ReadDir("/proc/self/fd")
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot list open fds")
+ }
+ for _, f := range files {
+ fd, err := strconv.Atoi(f.Name())
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot parse fd")
+ }
+ if fd == int(rootlessSlirpSyncW.Fd()) {
+ continue
+ }
+ unix.CloseOnExec(fd)
+ }
+
+ cmd := exec.Command(slirp4netns, "--mtu", "65520", "-r", "3", "-c", fmt.Sprintf("%d", pid), "tap0")
+ cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil
+ cmd.ExtraFiles = []*os.File{rootlessSlirpSyncW}
+
+ err = cmd.Start()
+ rootlessSlirpSyncW.Close()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot start slirp4netns")
+ }
+
+ b := make([]byte, 1)
+ for {
+ if err := rootlessSlirpSyncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
+ return nil, errors.Wrapf(err, "error setting slirp4netns pipe timeout")
+ }
+ if _, err := rootlessSlirpSyncR.Read(b); err == nil {
+ break
+ } else {
+ if os.IsTimeout(err) {
+ // Check if the process is still running.
+ var status syscall.WaitStatus
+ _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to read slirp4netns process status")
+ }
+ if status.Exited() || status.Signaled() {
+ return nil, errors.New("slirp4netns failed")
+ }
+
+ continue
+ }
+ return nil, errors.Wrapf(err, "failed to read from slirp4netns sync pipe")
+ }
+ }
+
+ return func() {
+ cmd.Process.Kill()
+ cmd.Wait()
+ }, nil
+}
+
+func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) {
+ var netconf, undo []*libcni.NetworkConfigList
+
+ if isolation == IsolationOCIRootless {
+ if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil && !ns.Host && ns.Path == "" {
+ return setupRootlessNetwork(pid)
+ }
+ }
+ // Scan for CNI configuration files.
+ confdir := options.CNIConfigDir
+ files, err := libcni.ConfFiles(confdir, []string{".conf"})
+ if err != nil {
+ return nil, errors.Wrapf(err, "error finding CNI networking configuration files named *.conf in directory %q", confdir)
+ }
+ lists, err := libcni.ConfFiles(confdir, []string{".conflist"})
+ if err != nil {
+ return nil, errors.Wrapf(err, "error finding CNI networking configuration list files named *.conflist in directory %q", confdir)
+ }
+ logrus.Debugf("CNI network configuration file list: %#v", append(files, lists...))
+ // Read the CNI configuration files.
+ for _, file := range files {
+ nc, err := libcni.ConfFromFile(file)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error loading networking configuration from file %q for %v", file, command)
+ }
+ if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !util.StringInSlice(nc.Network.Name, configureNetworks)) {
+ if nc.Network.Name == "" {
+ logrus.Debugf("configuration in %q has no name, skipping it", file)
+ } else {
+ logrus.Debugf("configuration in %q has name %q, skipping it", file, nc.Network.Name)
+ }
+ continue
+ }
+ cl, err := libcni.ConfListFromConf(nc)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error converting networking configuration from file %q for %v", file, command)
+ }
+ logrus.Debugf("using network configuration from %q", file)
+ netconf = append(netconf, cl)
+ }
+ for _, list := range lists {
+ cl, err := libcni.ConfListFromFile(list)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error loading networking configuration list from file %q for %v", list, command)
+ }
+ if len(configureNetworks) > 0 && (cl.Name == "" || !util.StringInSlice(cl.Name, configureNetworks)) {
+ if cl.Name == "" {
+ logrus.Debugf("configuration list in %q has no name, skipping it", list)
+ } else {
+ logrus.Debugf("configuration list in %q has name %q, skipping it", list, cl.Name)
+ }
+ continue
+ }
+ logrus.Debugf("using network configuration list from %q", list)
+ netconf = append(netconf, cl)
+ }
+ // Make sure we can access the container's network namespace,
+ // even after it exits, to successfully tear down the
+ // interfaces. Ensure this by opening a handle to the network
+ // namespace, and using our copy to both configure and
+ // deconfigure it.
+ netns := fmt.Sprintf("/proc/%d/ns/net", pid)
+ netFD, err := unix.Open(netns, unix.O_RDONLY, 0)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error opening network namespace for %v", command)
+ }
+ mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD)
+ // Build our search path for the plugins.
+ pluginPaths := strings.Split(options.CNIPluginPath, string(os.PathListSeparator))
+ cni := libcni.CNIConfig{Path: pluginPaths}
+ // Configure the interfaces.
+ rtconf := make(map[*libcni.NetworkConfigList]*libcni.RuntimeConf)
+ teardown = func() {
+ for _, nc := range undo {
+ if err = cni.DelNetworkList(context.Background(), nc, rtconf[nc]); err != nil {
+ logrus.Errorf("error cleaning up network %v for %v: %v", rtconf[nc].IfName, command, err)
+ }
+ }
+ unix.Close(netFD)
+ }
+ for i, nc := range netconf {
+ // Build the runtime config for use with this network configuration.
+ rtconf[nc] = &libcni.RuntimeConf{
+ ContainerID: containerName,
+ NetNS: mynetns,
+ IfName: fmt.Sprintf("if%d", i),
+ Args: [][2]string{},
+ CapabilityArgs: map[string]interface{}{},
+ }
+ // Bring it up.
+ _, err := cni.AddNetworkList(context.Background(), nc, rtconf[nc])
+ if err != nil {
+ return teardown, errors.Wrapf(err, "error configuring network list %v for %v", rtconf[nc].IfName, command)
+ }
+ // Add it to the list of networks to take down when the container process exits.
+ undo = append([]*libcni.NetworkConfigList{nc}, undo...)
+ }
+ return teardown, nil
+}
+
+func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) {
+ defer func() {
+ unix.Close(finishCopy[0])
+ if copyPipes {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ unix.Close(stdioPipe[unix.Stdout][0])
+ unix.Close(stdioPipe[unix.Stderr][0])
+ }
+ stdio.Done()
+ finishedCopy <- struct{}{}
+ }()
+ // Map describing where data on an incoming descriptor should go.
+ relayMap := make(map[int]int)
+ // Map describing incoming and outgoing descriptors.
+ readDesc := make(map[int]string)
+ writeDesc := make(map[int]string)
+ // Buffers.
+ relayBuffer := make(map[int]*bytes.Buffer)
+ // Set up the terminal descriptor or pipes for polling.
+ if copyConsole {
+ // Accept a connection over our listening socket.
+ fd, err := runAcceptTerminal(consoleListener, spec.Process.ConsoleSize)
+ if err != nil {
+ logrus.Errorf("%v", err)
+ return
+ }
+ terminalFD := fd
+ // Input from our stdin, output from the terminal descriptor.
+ relayMap[unix.Stdin] = terminalFD
+ readDesc[unix.Stdin] = "stdin"
+ relayBuffer[terminalFD] = new(bytes.Buffer)
+ writeDesc[terminalFD] = "container terminal input"
+ relayMap[terminalFD] = unix.Stdout
+ readDesc[terminalFD] = "container terminal output"
+ relayBuffer[unix.Stdout] = new(bytes.Buffer)
+ writeDesc[unix.Stdout] = "output"
+ // Set our terminal's mode to raw, to pass handling of special
+ // terminal input to the terminal in the container.
+ if terminal.IsTerminal(unix.Stdin) {
+ if state, err := terminal.MakeRaw(unix.Stdin); err != nil {
+ logrus.Warnf("error setting terminal state: %v", err)
+ } else {
+ defer func() {
+ if err = terminal.Restore(unix.Stdin, state); err != nil {
+ logrus.Errorf("unable to restore terminal state: %v", err)
+ }
+ }()
+ }
+ }
+ }
+ if copyPipes {
+ // Input from our stdin, output from the stdout and stderr pipes.
+ relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1]
+ readDesc[unix.Stdin] = "stdin"
+ relayBuffer[stdioPipe[unix.Stdin][1]] = new(bytes.Buffer)
+ writeDesc[stdioPipe[unix.Stdin][1]] = "container stdin"
+ relayMap[stdioPipe[unix.Stdout][0]] = unix.Stdout
+ readDesc[stdioPipe[unix.Stdout][0]] = "container stdout"
+ relayBuffer[unix.Stdout] = new(bytes.Buffer)
+ writeDesc[unix.Stdout] = "stdout"
+ relayMap[stdioPipe[unix.Stderr][0]] = unix.Stderr
+ readDesc[stdioPipe[unix.Stderr][0]] = "container stderr"
+ relayBuffer[unix.Stderr] = new(bytes.Buffer)
+ writeDesc[unix.Stderr] = "stderr"
+ }
+ // Set our reading descriptors to non-blocking.
+ for rfd, wfd := range relayMap {
+ if err := unix.SetNonblock(rfd, true); err != nil {
+ logrus.Errorf("error setting %s to nonblocking: %v", readDesc[rfd], err)
+ return
+ }
+ if err := unix.SetNonblock(wfd, false); err != nil {
+ logrus.Errorf("error setting descriptor %d (%s) blocking: %v", wfd, writeDesc[wfd], err)
+ }
+ }
+ // Pass data back and forth.
+ pollTimeout := -1
+ for len(relayMap) > 0 {
+ // Start building the list of descriptors to poll.
+ pollFds := make([]unix.PollFd, 0, len(relayMap)+1)
+ // Poll for a notification that we should stop handling stdio.
+ pollFds = append(pollFds, unix.PollFd{Fd: int32(finishCopy[0]), Events: unix.POLLIN | unix.POLLHUP})
+ // Poll on our reading descriptors.
+ for rfd := range relayMap {
+ pollFds = append(pollFds, unix.PollFd{Fd: int32(rfd), Events: unix.POLLIN | unix.POLLHUP})
+ }
+ buf := make([]byte, 8192)
+ // Wait for new data from any input descriptor, or a notification that we're done.
+ _, err := unix.Poll(pollFds, pollTimeout)
+ if !util.LogIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) {
+ return
+ }
+ removes := make(map[int]struct{})
+ for _, pollFd := range pollFds {
+ // If this descriptor's just been closed from the other end, mark it for
+ // removal from the set that we're checking for.
+ if pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
+ removes[int(pollFd.Fd)] = struct{}{}
+ }
+ // If the descriptor was closed elsewhere, remove it from our list.
+ if pollFd.Revents&unix.POLLNVAL != 0 {
+ logrus.Debugf("error polling descriptor %s: closed?", readDesc[int(pollFd.Fd)])
+ removes[int(pollFd.Fd)] = struct{}{}
+ }
+ // If the POLLIN flag isn't set, then there's no data to be read from this descriptor.
+ if pollFd.Revents&unix.POLLIN == 0 {
+ // If we're using pipes and it's our stdin and it's closed, close the writing
+ // end of the corresponding pipe.
+ if copyPipes && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ stdioPipe[unix.Stdin][1] = -1
+ }
+ continue
+ }
+ // Read whatever there is to be read.
+ readFD := int(pollFd.Fd)
+ writeFD, needToRelay := relayMap[readFD]
+ if needToRelay {
+ n, err := unix.Read(readFD, buf)
+ if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to read %s data: %v", readDesc[readFD], err)) {
+ return
+ }
+ // If it's zero-length on our stdin and we're
+ // using pipes, it's an EOF, so close the stdin
+ // pipe's writing end.
+ if n == 0 && copyPipes && int(pollFd.Fd) == unix.Stdin {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ stdioPipe[unix.Stdin][1] = -1
+ }
+ if n > 0 {
+ // Buffer the data in case we get blocked on where they need to go.
+ nwritten, err := relayBuffer[writeFD].Write(buf[:n])
+ if err != nil {
+ logrus.Debugf("buffer: %v", err)
+ continue
+ }
+ if nwritten != n {
+ logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", n, nwritten)
+ continue
+ }
+ // If this is the last of the data we'll be able to read from this
+ // descriptor, read all that there is to read.
+ for pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
+ nr, err := unix.Read(readFD, buf)
+ util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err))
+ if nr <= 0 {
+ break
+ }
+ nwritten, err := relayBuffer[writeFD].Write(buf[:nr])
+ if err != nil {
+ logrus.Debugf("buffer: %v", err)
+ break
+ }
+ if nwritten != nr {
+ logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten)
+ break
+ }
+ }
+ }
+ }
+ }
+ // Try to drain the output buffers. Set the default timeout
+ // for the next poll() to 100ms if we still have data to write.
+ pollTimeout = -1
+ for writeFD := range relayBuffer {
+ if relayBuffer[writeFD].Len() > 0 {
+ n, err := unix.Write(writeFD, relayBuffer[writeFD].Bytes())
+ if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to write %s data: %v", writeDesc[writeFD], err)) {
+ return
+ }
+ if n > 0 {
+ relayBuffer[writeFD].Next(n)
+ }
+ }
+ if relayBuffer[writeFD].Len() > 0 {
+ pollTimeout = 100
+ }
+ }
+ // Remove any descriptors which we don't need to poll any more from the poll descriptor list.
+ for remove := range removes {
+ delete(relayMap, remove)
+ }
+ // If the we-can-return pipe had anything for us, we're done.
+ for _, pollFd := range pollFds {
+ if int(pollFd.Fd) == finishCopy[0] && pollFd.Revents != 0 {
+ // The pipe is closed, indicating that we can stop now.
+ return
+ }
+ }
+ }
+}
+
+func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Box) (int, error) {
+ defer consoleListener.Close()
+ c, err := consoleListener.AcceptUnix()
+ if err != nil {
+ return -1, errors.Wrapf(err, "error accepting socket descriptor connection")
+ }
+ defer c.Close()
+ // Expect a control message over our new connection.
+ b := make([]byte, 8192)
+ oob := make([]byte, 8192)
+ n, oobn, _, _, err := c.ReadMsgUnix(b, oob)
+ if err != nil {
+ return -1, errors.Wrapf(err, "error reading socket descriptor")
+ }
+ if n > 0 {
+ logrus.Debugf("socket descriptor is for %q", string(b[:n]))
+ }
+ if oobn > len(oob) {
+ return -1, errors.Errorf("too much out-of-bounds data (%d bytes)", oobn)
+ }
+ // Parse the control message.
+ scm, err := unix.ParseSocketControlMessage(oob[:oobn])
+ if err != nil {
+ return -1, errors.Wrapf(err, "error parsing out-of-bound data as a socket control message")
+ }
+ logrus.Debugf("control messages: %v", scm)
+ // Expect to get a descriptor.
+ terminalFD := -1
+ for i := range scm {
+ fds, err := unix.ParseUnixRights(&scm[i])
+ if err != nil {
+ return -1, errors.Wrapf(err, "error parsing unix rights control message: %v", &scm[i])
+ }
+ logrus.Debugf("fds: %v", fds)
+ if len(fds) == 0 {
+ continue
+ }
+ terminalFD = fds[0]
+ break
+ }
+ if terminalFD == -1 {
+ return -1, errors.Errorf("unable to read terminal descriptor")
+ }
+ // Set the pseudoterminal's size to the configured size, or our own.
+ winsize := &unix.Winsize{}
+ if terminalSize != nil {
+ // Use configured sizes.
+ winsize.Row = uint16(terminalSize.Height)
+ winsize.Col = uint16(terminalSize.Width)
+ } else {
+ if terminal.IsTerminal(unix.Stdin) {
+ // Use the size of our terminal.
+ if winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ); err != nil {
+ logrus.Warnf("error reading size of controlling terminal: %v", err)
+ winsize.Row = 0
+ winsize.Col = 0
+ }
+ }
+ }
+ if winsize.Row != 0 && winsize.Col != 0 {
+ if err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize); err != nil {
+ logrus.Warnf("error setting size of container pseudoterminal: %v", err)
+ }
+ // FIXME - if we're connected to a terminal, we should
+ // be passing the updated terminal size down when we
+ // receive a SIGWINCH.
+ }
+ return terminalFD, nil
+}
+
+// Create pipes to use for relaying stdio.
+func runMakeStdioPipe(uid, gid int) ([][]int, error) {
+ stdioPipe := make([][]int, 3)
+ for i := range stdioPipe {
+ stdioPipe[i] = make([]int, 2)
+ if err := unix.Pipe(stdioPipe[i]); err != nil {
+ return nil, errors.Wrapf(err, "error creating pipe for container FD %d", i)
+ }
+ }
+ if err := unix.Fchown(stdioPipe[unix.Stdin][0], uid, gid); err != nil {
+ return nil, errors.Wrapf(err, "error setting owner of stdin pipe descriptor")
+ }
+ if err := unix.Fchown(stdioPipe[unix.Stdout][1], uid, gid); err != nil {
+ return nil, errors.Wrapf(err, "error setting owner of stdout pipe descriptor")
+ }
+ if err := unix.Fchown(stdioPipe[unix.Stderr][1], uid, gid); err != nil {
+ return nil, errors.Wrapf(err, "error setting owner of stderr pipe descriptor")
+ }
+ return stdioPipe, nil
+}
+
+func runUsingRuntimeMain() {
+ var options runUsingRuntimeSubprocOptions
+ // Set logging.
+ if level := os.Getenv("LOGLEVEL"); level != "" {
+ if ll, err := strconv.Atoi(level); err == nil {
+ logrus.SetLevel(logrus.Level(ll))
+ }
+ }
+ // Unpack our configuration.
+ confPipe := os.NewFile(3, "confpipe")
+ if confPipe == nil {
+ fmt.Fprintf(os.Stderr, "error reading options pipe\n")
+ os.Exit(1)
+ }
+ defer confPipe.Close()
+ if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
+ fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
+ os.Exit(1)
+ }
+ // Set ourselves up to read the container's exit status. We're doing this in a child process
+ // so that we won't mess with the setting in a caller of the library. This stubs to OS specific
+ // calls
+ if err := setChildProcess(); err != nil {
+ os.Exit(1)
+ }
+ // Run the container, start to finish.
+ status, err := runUsingRuntime(options.Isolation, options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error running container: %v\n", err)
+ os.Exit(1)
+ }
+ // Pass the container's exit status back to the caller by exiting with the same status.
+ if status.Exited() {
+ os.Exit(status.ExitStatus())
+ } else if status.Signaled() {
+ fmt.Fprintf(os.Stderr, "container exited on %s\n", status.Signal())
+ os.Exit(1)
+ }
+ os.Exit(1)
+}
+
+func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, configureUTS bool, err error) {
+ // Set namespace options in the container configuration.
+ configureUserns := false
+ specifiedNetwork := false
+ for _, namespaceOption := range namespaceOptions {
+ switch namespaceOption.Name {
+ case string(specs.UserNamespace):
+ configureUserns = false
+ if !namespaceOption.Host && namespaceOption.Path == "" {
+ configureUserns = true
+ }
+ case string(specs.NetworkNamespace):
+ specifiedNetwork = true
+ configureNetwork = false
+ if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) {
+ if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) {
+ configureNetworks = strings.Split(namespaceOption.Path, ",")
+ namespaceOption.Path = ""
+ }
+ configureNetwork = (policy != NetworkDisabled)
+ }
+ case string(specs.UTSNamespace):
+ configureUTS = false
+ if !namespaceOption.Host && namespaceOption.Path == "" {
+ configureUTS = true
+ }
+ }
+ if namespaceOption.Host {
+ if err := g.RemoveLinuxNamespace(namespaceOption.Name); err != nil {
+ return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", namespaceOption.Name)
+ }
+ } else if err := g.AddOrReplaceLinuxNamespace(namespaceOption.Name, namespaceOption.Path); err != nil {
+ if namespaceOption.Path == "" {
+ return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", namespaceOption.Name)
+ }
+ return false, nil, false, errors.Wrapf(err, "error adding %q namespace %q for run", namespaceOption.Name, namespaceOption.Path)
+ }
+ }
+
+ // If we've got mappings, we're going to have to create a user namespace.
+ if len(idmapOptions.UIDMap) > 0 || len(idmapOptions.GIDMap) > 0 || configureUserns {
+ if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil {
+ return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace))
+ }
+ hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("")
+ if err != nil {
+ return false, nil, false, err
+ }
+ for _, m := range idmapOptions.UIDMap {
+ g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size)
+ }
+ if len(idmapOptions.UIDMap) == 0 {
+ for _, m := range hostUidmap {
+ g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size)
+ }
+ }
+ for _, m := range idmapOptions.GIDMap {
+ g.AddLinuxGIDMapping(m.HostID, m.ContainerID, m.Size)
+ }
+ if len(idmapOptions.GIDMap) == 0 {
+ for _, m := range hostGidmap {
+ g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size)
+ }
+ }
+ if !specifiedNetwork {
+ if err := g.AddOrReplaceLinuxNamespace(specs.NetworkNamespace, ""); err != nil {
+ return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.NetworkNamespace))
+ }
+ configureNetwork = (policy != NetworkDisabled)
+ }
+ } else {
+ if err := g.RemoveLinuxNamespace(specs.UserNamespace); err != nil {
+ return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.UserNamespace))
+ }
+ if !specifiedNetwork {
+ if err := g.RemoveLinuxNamespace(specs.NetworkNamespace); err != nil {
+ return false, nil, false, errors.Wrapf(err, "error removing %q namespace for run", string(specs.NetworkNamespace))
+ }
+ }
+ }
+ if configureNetwork {
+ for name, val := range util.DefaultNetworkSysctl {
+ g.AddLinuxSysctl(name, val)
+ }
+ }
+ return configureNetwork, configureNetworks, configureUTS, nil
+}
+
+func (b *Builder) configureNamespaces(g *generate.Generator, options RunOptions) (bool, []string, error) {
+ defaultNamespaceOptions, err := DefaultNamespaceOptions()
+ if err != nil {
+ return false, nil, err
+ }
+
+ namespaceOptions := defaultNamespaceOptions
+ namespaceOptions.AddOrReplace(b.NamespaceOptions...)
+ namespaceOptions.AddOrReplace(options.NamespaceOptions...)
+
+ networkPolicy := options.ConfigureNetwork
+ if networkPolicy == NetworkDefault {
+ networkPolicy = b.ConfigureNetwork
+ }
+
+ configureNetwork, configureNetworks, configureUTS, err := setupNamespaces(g, namespaceOptions, b.IDMappingOptions, networkPolicy)
+ if err != nil {
+ return false, nil, err
+ }
+
+ if configureUTS {
+ if options.Hostname != "" {
+ g.SetHostname(options.Hostname)
+ } else if b.Hostname() != "" {
+ g.SetHostname(b.Hostname())
+ } else {
+ g.SetHostname(stringid.TruncateID(b.ContainerID))
+ }
+ } else {
+ g.SetHostname("")
+ }
+
+ found := false
+ spec := g.Config
+ for i := range spec.Process.Env {
+ if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") {
+ found = true
+ break
+ }
+ }
+ if !found {
+ spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOSTNAME=%s", spec.Hostname))
+ }
+
+ return configureNetwork, configureNetworks, nil
+}
+
+func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount, err error) {
+ for dest, src := range bindFiles {
+ options := []string{"rbind"}
+ if strings.HasPrefix(src, bundlePath) {
+ options = append(options, bind.NoBindOption)
+ }
+ mounts = append(mounts, specs.Mount{
+ Source: src,
+ Destination: dest,
+ Type: "bind",
+ Options: options,
+ })
+ }
+ return mounts, nil
+}
+
+func addRlimits(ulimit []string, g *generate.Generator) error {
+ var (
+ ul *units.Ulimit
+ err error
+ )
+
+ for _, u := range ulimit {
+ if ul, err = units.ParseUlimit(u); err != nil {
+ return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
+ }
+
+ g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft))
+ }
+ return nil
+}
+
+func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount) ([]specs.Mount, error) {
+ var mounts []specs.Mount
+
+ parseMount := func(host, container string, options []string) (specs.Mount, error) {
+ var foundrw, foundro, foundz, foundZ bool
+ var rootProp string
+ for _, opt := range options {
+ switch opt {
+ case "rw":
+ foundrw = true
+ case "ro":
+ foundro = true
+ case "z":
+ foundz = true
+ case "Z":
+ foundZ = true
+ case "private", "rprivate", "slave", "rslave", "shared", "rshared":
+ rootProp = opt
+ }
+ }
+ if !foundrw && !foundro {
+ options = append(options, "rw")
+ }
+ if foundz {
+ if err := label.Relabel(host, mountLabel, true); err != nil {
+ return specs.Mount{}, errors.Wrapf(err, "relabeling %q failed", host)
+ }
+ }
+ if foundZ {
+ if err := label.Relabel(host, mountLabel, false); err != nil {
+ return specs.Mount{}, errors.Wrapf(err, "relabeling %q failed", host)
+ }
+ }
+ if rootProp == "" {
+ options = append(options, "private")
+ }
+ return specs.Mount{
+ Destination: container,
+ Type: "bind",
+ Source: host,
+ Options: options,
+ }, nil
+ }
+ // Bind mount volumes specified for this particular Run() invocation
+ for _, i := range optionMounts {
+ logrus.Debugf("setting up mounted volume at %q", i.Destination)
+ mount, err := parseMount(i.Source, i.Destination, append(i.Options, "rbind"))
+ if err != nil {
+ return nil, err
+ }
+ mounts = append(mounts, mount)
+ }
+ // Bind mount volumes given by the user when the container was created
+ for _, i := range volumeMounts {
+ var options []string
+ spliti := strings.Split(i, ":")
+ if len(spliti) > 2 {
+ options = strings.Split(spliti[2], ",")
+ }
+ options = append(options, "rbind")
+ mount, err := parseMount(spliti[0], spliti[1], options)
+ if err != nil {
+ return nil, err
+ }
+ mounts = append(mounts, mount)
+ }
+ return mounts, nil
+}
+
+func setupMaskedPaths(g *generate.Generator) {
+ for _, mp := range []string{
+ "/proc/acpi",
+ "/proc/kcore",
+ "/proc/keys",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ "/proc/scsi",
+ "/sys/firmware",
+ } {
+ g.AddLinuxMaskedPaths(mp)
+ }
+}
+
+func setupReadOnlyPaths(g *generate.Generator) {
+ for _, rp := range []string{
+ "/proc/asound",
+ "/proc/bus",
+ "/proc/fs",
+ "/proc/irq",
+ "/proc/sys",
+ "/proc/sysrq-trigger",
+ } {
+ g.AddLinuxReadonlyPaths(rp)
+ }
+}
+
+func setupCapAdd(g *generate.Generator, caps ...string) error {
+ for _, cap := range caps {
+ if err := g.AddProcessCapabilityBounding(cap); err != nil {
+ return errors.Wrapf(err, "error adding %q to the bounding capability set", cap)
+ }
+ if err := g.AddProcessCapabilityEffective(cap); err != nil {
+ return errors.Wrapf(err, "error adding %q to the effective capability set", cap)
+ }
+ if err := g.AddProcessCapabilityInheritable(cap); err != nil {
+ return errors.Wrapf(err, "error adding %q to the inheritable capability set", cap)
+ }
+ if err := g.AddProcessCapabilityPermitted(cap); err != nil {
+ return errors.Wrapf(err, "error adding %q to the permitted capability set", cap)
+ }
+ if err := g.AddProcessCapabilityAmbient(cap); err != nil {
+ return errors.Wrapf(err, "error adding %q to the ambient capability set", cap)
+ }
+ }
+ return nil
+}
+
+func setupCapDrop(g *generate.Generator, caps ...string) error {
+ for _, cap := range caps {
+ if err := g.DropProcessCapabilityBounding(cap); err != nil {
+ return errors.Wrapf(err, "error removing %q from the bounding capability set", cap)
+ }
+ if err := g.DropProcessCapabilityEffective(cap); err != nil {
+ return errors.Wrapf(err, "error removing %q from the effective capability set", cap)
+ }
+ if err := g.DropProcessCapabilityInheritable(cap); err != nil {
+ return errors.Wrapf(err, "error removing %q from the inheritable capability set", cap)
+ }
+ if err := g.DropProcessCapabilityPermitted(cap); err != nil {
+ return errors.Wrapf(err, "error removing %q from the permitted capability set", cap)
+ }
+ if err := g.DropProcessCapabilityAmbient(cap); err != nil {
+ return errors.Wrapf(err, "error removing %q from the ambient capability set", cap)
+ }
+ }
+ return nil
+}
+
+func setupCapabilities(g *generate.Generator, firstAdds, firstDrops, secondAdds, secondDrops []string) error {
+ g.ClearProcessCapabilities()
+ if err := setupCapAdd(g, util.DefaultCapabilities...); err != nil {
+ return err
+ }
+ if err := setupCapAdd(g, firstAdds...); err != nil {
+ return err
+ }
+ if err := setupCapDrop(g, firstDrops...); err != nil {
+ return err
+ }
+ if err := setupCapAdd(g, secondAdds...); err != nil {
+ return err
+ }
+ return setupCapDrop(g, secondDrops...)
+}
+
+// Search for a command that isn't given as an absolute path using the $PATH
+// under the rootfs. We can't resolve absolute symbolic links without
+// chroot()ing, which we may not be able to do, so just accept a link as a
+// valid resolution.
+func runLookupPath(g *generate.Generator, command []string) []string {
+ // Look for the configured $PATH.
+ spec := g.Config
+ envPath := ""
+ for i := range spec.Process.Env {
+ if strings.HasPrefix(spec.Process.Env[i], "PATH=") {
+ envPath = spec.Process.Env[i]
+ }
+ }
+ // If there is no configured $PATH, supply one.
+ if envPath == "" {
+ defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
+ envPath = "PATH=" + defaultPath
+ g.AddProcessEnv("PATH", defaultPath)
+ }
+ // No command, nothing to do.
+ if len(command) == 0 {
+ return command
+ }
+ // Command is already an absolute path, use it as-is.
+ if filepath.IsAbs(command[0]) {
+ return command
+ }
+ // For each element in the PATH,
+ for _, pathEntry := range filepath.SplitList(envPath[5:]) {
+ // if it's the empty string, it's ".", which is the Cwd,
+ if pathEntry == "" {
+ pathEntry = spec.Process.Cwd
+ }
+ // build the absolute path which it might be,
+ candidate := filepath.Join(pathEntry, command[0])
+ // check if it's there,
+ if fi, err := os.Lstat(filepath.Join(spec.Root.Path, candidate)); fi != nil && err == nil {
+ // and if it's not a directory, and either a symlink or executable,
+ if !fi.IsDir() && ((fi.Mode()&os.ModeSymlink != 0) || (fi.Mode()&0111 != 0)) {
+ // use that.
+ return append([]string{candidate}, command[1:]...)
+ }
+ }
+ }
+ return command
+}
+
+func getDNSIP(dnsServers []string) (dns []net.IP, err error) {
+ for _, i := range dnsServers {
+ result := net.ParseIP(i)
+ if result == nil {
+ return dns, errors.Errorf("invalid IP address %s", i)
+ }
+ dns = append(dns, result)
+ }
+ return dns, nil
+}
+
+func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, options RunOptions) error {
+ // Set the user UID/GID/supplemental group list/capabilities lists.
+ user, err := b.user(mountPoint, options.User)
+ if err != nil {
+ return err
+ }
+ if err := setupCapabilities(g, b.AddCapabilities, b.DropCapabilities, options.AddCapabilities, options.DropCapabilities); err != nil {
+ return err
+ }
+ g.SetProcessUID(user.UID)
+ g.SetProcessGID(user.GID)
+ for _, gid := range user.AdditionalGids {
+ g.AddProcessAdditionalGid(gid)
+ }
+
+ // Remove capabilities if not running as root except Bounding set
+ if user.UID != 0 {
+ bounding := g.Config.Process.Capabilities.Bounding
+ g.ClearProcessCapabilities()
+ g.Config.Process.Capabilities.Bounding = bounding
+ }
+
+ return nil
+}
+
+func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions) {
+ g.ClearProcessEnv()
+ if b.CommonBuildOpts.HTTPProxy {
+ for _, envSpec := range []string{
+ "http_proxy",
+ "HTTP_PROXY",
+ "https_proxy",
+ "HTTPS_PROXY",
+ "ftp_proxy",
+ "FTP_PROXY",
+ "no_proxy",
+ "NO_PROXY",
+ } {
+ envVal := os.Getenv(envSpec)
+ if envVal != "" {
+ g.AddProcessEnv(envSpec, envVal)
+ }
+ }
+ }
+
+ for _, envSpec := range append(b.Env(), options.Env...) {
+ env := strings.SplitN(envSpec, "=", 2)
+ if len(env) > 1 {
+ g.AddProcessEnv(env[0], env[1])
+ }
+ }
+
+ for src, dest := range b.Args {
+ g.AddProcessEnv(src, dest)
+ }
+}
+
+func setupRootlessSpecChanges(spec *specs.Spec, bundleDir string, rootUID, rootGID uint32) error {
+ spec.Hostname = ""
+ spec.Process.User.AdditionalGids = nil
+ spec.Linux.Resources = nil
+
+ emptyDir := filepath.Join(bundleDir, "empty")
+ if err := os.Mkdir(emptyDir, 0); err != nil {
+ return errors.Wrapf(err, "error creating %q", emptyDir)
+ }
+
+ // Replace /sys with a read-only bind mount.
+ mounts := []specs.Mount{
+ {
+ Source: "/dev",
+ Destination: "/dev",
+ Type: "tmpfs",
+ Options: []string{"private", "strictatime", "noexec", "nosuid", "mode=755", "size=65536k"},
+ },
+ {
+ Source: "mqueue",
+ Destination: "/dev/mqueue",
+ Type: "mqueue",
+ Options: []string{"private", "nodev", "noexec", "nosuid"},
+ },
+ {
+ Source: "pts",
+ Destination: "/dev/pts",
+ Type: "devpts",
+ Options: []string{"private", "noexec", "nosuid", "newinstance", "ptmxmode=0666", "mode=0620"},
+ },
+ {
+ Source: "shm",
+ Destination: "/dev/shm",
+ Type: "tmpfs",
+ Options: []string{"private", "nodev", "noexec", "nosuid", "mode=1777", "size=65536k"},
+ },
+ {
+ Source: "/proc",
+ Destination: "/proc",
+ Type: "proc",
+ Options: []string{"private", "nodev", "noexec", "nosuid"},
+ },
+ {
+ Source: "/sys",
+ Destination: "/sys",
+ Type: "bind",
+ Options: []string{bind.NoBindOption, "rbind", "private", "nodev", "noexec", "nosuid", "ro"},
+ },
+ }
+ // Cover up /sys/fs/cgroup and /sys/fs/selinux, if they exist in our source for /sys.
+ if _, err := os.Stat("/sys/fs/cgroup"); err == nil {
+ spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/cgroup")
+ }
+ if _, err := os.Stat("/sys/fs/selinux"); err == nil {
+ spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/selinux")
+ }
+ // Keep anything that isn't under /dev, /proc, or /sys.
+ for i := range spec.Mounts {
+ if spec.Mounts[i].Destination == "/dev" || strings.HasPrefix(spec.Mounts[i].Destination, "/dev/") ||
+ spec.Mounts[i].Destination == "/proc" || strings.HasPrefix(spec.Mounts[i].Destination, "/proc/") ||
+ spec.Mounts[i].Destination == "/sys" || strings.HasPrefix(spec.Mounts[i].Destination, "/sys/") {
+ continue
+ }
+ mounts = append(mounts, spec.Mounts[i])
+ }
+ spec.Mounts = mounts
+ return nil
+}
+
+func (b *Builder) runUsingRuntimeSubproc(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
+ var confwg sync.WaitGroup
+ config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{
+ Options: options,
+ Spec: spec,
+ RootPath: rootPath,
+ BundlePath: bundlePath,
+ ConfigureNetwork: configureNetwork,
+ ConfigureNetworks: configureNetworks,
+ MoreCreateArgs: moreCreateArgs,
+ ContainerName: containerName,
+ Isolation: isolation,
+ })
+ if conferr != nil {
+ return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand)
+ }
+ cmd := reexec.Command(runUsingRuntimeCommand)
+ cmd.Dir = bundlePath
+ cmd.Stdin = options.Stdin
+ if cmd.Stdin == nil {
+ cmd.Stdin = os.Stdin
+ }
+ cmd.Stdout = options.Stdout
+ if cmd.Stdout == nil {
+ cmd.Stdout = os.Stdout
+ }
+ cmd.Stderr = options.Stderr
+ if cmd.Stderr == nil {
+ cmd.Stderr = os.Stderr
+ }
+ cmd.Env = append(os.Environ(), fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel()))
+ preader, pwriter, err := os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "error creating configuration pipe")
+ }
+ confwg.Add(1)
+ go func() {
+ _, conferr = io.Copy(pwriter, bytes.NewReader(config))
+ if conferr != nil {
+ conferr = errors.Wrapf(conferr, "error while copying configuration down pipe to child process")
+ }
+ confwg.Done()
+ }()
+ cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...)
+ defer preader.Close()
+ defer pwriter.Close()
+ err = cmd.Run()
+ if err != nil {
+ err = errors.Wrapf(err, "error while running runtime")
+ }
+ confwg.Wait()
+ if err == nil {
+ return conferr
+ }
+ if conferr != nil {
+ logrus.Debugf("%v", conferr)
+ }
+ return err
+}
+
+func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) error {
+ switch isolation {
+ case IsolationOCIRootless:
+ if ns := options.NamespaceOptions.Find(string(specs.IPCNamespace)); ns == nil || ns.Host {
+ logrus.Debugf("Forcing use of an IPC namespace.")
+ }
+ options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.IPCNamespace)})
+ _, err := exec.LookPath("slirp4netns")
+ hostNetworking := err != nil
+ networkNamespacePath := ""
+ if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil {
+ hostNetworking = ns.Host
+ networkNamespacePath = ns.Path
+ if !hostNetworking && networkNamespacePath != "" && !filepath.IsAbs(networkNamespacePath) {
+ logrus.Debugf("Disabling network namespace configuration.")
+ networkNamespacePath = ""
+ }
+ }
+ options.NamespaceOptions.AddOrReplace(NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ Host: hostNetworking,
+ Path: networkNamespacePath,
+ })
+ if ns := options.NamespaceOptions.Find(string(specs.PIDNamespace)); ns == nil || ns.Host {
+ logrus.Debugf("Forcing use of a PID namespace.")
+ }
+ options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.PIDNamespace), Host: false})
+ if ns := options.NamespaceOptions.Find(string(specs.UserNamespace)); ns == nil || ns.Host {
+ logrus.Debugf("Forcing use of a user namespace.")
+ }
+ options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UserNamespace)})
+ if ns := options.NamespaceOptions.Find(string(specs.UTSNamespace)); ns != nil && !ns.Host {
+ logrus.Debugf("Disabling UTS namespace.")
+ }
+ options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UTSNamespace), Host: true})
+ case IsolationOCI:
+ pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace))
+ userns := options.NamespaceOptions.Find(string(specs.UserNamespace))
+ if (pidns == nil || pidns.Host) && (userns != nil && !userns.Host) {
+ return fmt.Errorf("not allowed to mix host PID namespace with container user namespace")
+ }
+ }
+ return nil
+}
+
+// DefaultNamespaceOptions returns the default namespace settings from the
+// runtime-tools generator library.
+func DefaultNamespaceOptions() (NamespaceOptions, error) {
+ options := NamespaceOptions{
+ {Name: string(specs.CgroupNamespace), Host: true},
+ {Name: string(specs.IPCNamespace), Host: true},
+ {Name: string(specs.MountNamespace), Host: true},
+ {Name: string(specs.NetworkNamespace), Host: true},
+ {Name: string(specs.PIDNamespace), Host: true},
+ {Name: string(specs.UserNamespace), Host: true},
+ {Name: string(specs.UTSNamespace), Host: true},
+ }
+ g, err := generate.New("linux")
+ if err != nil {
+ return options, errors.Wrapf(err, "error generating new 'linux' runtime spec")
+ }
+ spec := g.Config
+ if spec.Linux != nil {
+ for _, ns := range spec.Linux.Namespaces {
+ options.AddOrReplace(NamespaceOption{
+ Name: string(ns.Type),
+ Path: ns.Path,
+ })
+ }
+ }
+ return options, nil
+}
+
+func contains(volumes []string, v string) bool {
+ for _, i := range volumes {
+ if i == v {
+ return true
+ }
+ }
+ return false
+}
+
+type runUsingRuntimeSubprocOptions struct {
+ Options RunOptions
+ Spec *specs.Spec
+ RootPath string
+ BundlePath string
+ ConfigureNetwork bool
+ ConfigureNetworks []string
+ MoreCreateArgs []string
+ ContainerName string
+ Isolation Isolation
+}
+
+func init() {
+ reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain)
+}
diff --git a/vendor/github.com/containers/buildah/run_unsupport.go b/vendor/github.com/containers/buildah/run_unsupport.go
deleted file mode 100644
index 4824a0c4e..000000000
--- a/vendor/github.com/containers/buildah/run_unsupport.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// +build !linux
-
-package buildah
-
-import (
- "github.com/pkg/errors"
-)
-
-func setChildProcess() error {
- return errors.New("function not supported on non-linux systems")
-}
diff --git a/vendor/github.com/containers/buildah/run_unsupported.go b/vendor/github.com/containers/buildah/run_unsupported.go
new file mode 100644
index 000000000..d8ff46cf6
--- /dev/null
+++ b/vendor/github.com/containers/buildah/run_unsupported.go
@@ -0,0 +1,20 @@
+// +build !linux
+
+package buildah
+
+import (
+ "github.com/pkg/errors"
+)
+
+func setChildProcess() error {
+ return errors.New("function not supported on non-linux systems")
+}
+
+func runUsingRuntimeMain() {}
+
+func (b *Builder) Run(command []string, options RunOptions) error {
+ return errors.New("function not supported on non-linux systems")
+}
+func DefaultNamespaceOptions() (NamespaceOptions, error) {
+ return NamespaceOptions{}, errors.New("function not supported on non-linux systems")
+}
diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go
index 698d79a81..629d9748c 100644
--- a/vendor/github.com/containers/buildah/util/util.go
+++ b/vendor/github.com/containers/buildah/util/util.go
@@ -197,7 +197,7 @@ func FindImage(store storage.Store, firstRegistry string, systemContext *types.S
break
}
if ref == nil || img == nil {
- return nil, nil, errors.Wrapf(err, "error locating image with name %q", image)
+ return nil, nil, errors.Wrapf(err, "error locating image with name %q (%v)", image, names)
}
return ref, img, nil
}
diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go
index dda172574..e8f8bd5a7 100644
--- a/vendor/github.com/containers/storage/drivers/driver.go
+++ b/vendor/github.com/containers/storage/drivers/driver.go
@@ -40,6 +40,7 @@ var (
type CreateOpts struct {
MountLabel string
StorageOpt map[string]string
+ *idtools.IDMappings
}
// MountOpts contains optional arguments for LayerStope.Mount() methods.
diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go
index 5d667d8c6..ef83b6c87 100644
--- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go
+++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go
@@ -474,10 +474,22 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
dir := d.dir(id)
- rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
+ uidMaps := d.uidMaps
+ gidMaps := d.gidMaps
+
+ if opts != nil && opts.IDMappings != nil {
+ uidMaps = opts.IDMappings.UIDs()
+ gidMaps = opts.IDMappings.GIDs()
+ }
+
+ rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return err
}
+ // Make the link directory if it does not exist
+ if err := idtools.MkdirAllAs(path.Join(d.home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
+ return err
+ }
if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil {
return err
}
@@ -690,9 +702,17 @@ func (d *Driver) recreateSymlinks() error {
if err != nil {
return fmt.Errorf("error reading driver home directory %q: %v", d.home, err)
}
+ // This makes the link directory if it doesn't exist
+ rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
+ if err != nil {
+ return err
+ }
+ if err := idtools.MkdirAllAs(path.Join(d.home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
+ return err
+ }
for _, dir := range dirs {
- // Skip over the linkDir
- if dir.Name() == linkDir || dir.Mode().IsRegular() {
+ // Skip over the linkDir and anything that is not a directory
+ if dir.Name() == linkDir || !dir.Mode().IsDir() {
continue
}
// Read the "link" file under each layer to get the name of the symlink
diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go
index 5941ccc17..9e256858c 100644
--- a/vendor/github.com/containers/storage/drivers/vfs/driver.go
+++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go
@@ -123,8 +123,13 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, ro bool
return fmt.Errorf("--storage-opt is not supported for vfs")
}
+ idMappings := d.idMappings
+ if opts != nil && opts.IDMappings != nil {
+ idMappings = opts.IDMappings
+ }
+
dir := d.dir(id)
- rootIDs := d.idMappings.RootPair()
+ rootIDs := idMappings.RootPair()
if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0700, rootIDs); err != nil {
return err
}
diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go
index 110e737b2..7bec0aea6 100644
--- a/vendor/github.com/containers/storage/layers.go
+++ b/vendor/github.com/containers/storage/layers.go
@@ -614,6 +614,7 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
opts := drivers.CreateOpts{
MountLabel: mountLabel,
StorageOpt: options,
+ IDMappings: idMappings,
}
if moreOptions.TemplateLayer != "" {
if err = r.driver.CreateFromTemplate(id, moreOptions.TemplateLayer, templateIDMappings, parent, parentMappings, &opts, writeable); err != nil {
diff --git a/vendor/github.com/openshift/imagebuilder/vendor.conf b/vendor/github.com/openshift/imagebuilder/vendor.conf
index 39b216feb..e437b79c3 100644
--- a/vendor/github.com/openshift/imagebuilder/vendor.conf
+++ b/vendor/github.com/openshift/imagebuilder/vendor.conf
@@ -1,12 +1,11 @@
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
-github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d
+github.com/containers/storage v1.2
github.com/docker/docker b68221c37ee597950364788204546f9c9d0e46a1
github.com/docker/go-connections 97c2040d34dfae1d1b1275fa3a78dbdd2f41cf7e
github.com/docker/go-units 2fb04c6466a548a03cb009c5569ee1ab1e35398e
github.com/fsouza/go-dockerclient openshift-4.0 https://github.com/openshift/go-dockerclient.git
github.com/gogo/protobuf c5a62797aee0054613cc578653a16c6237fef080
github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998
-github.com/golang/protobuf v1.3.0
github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e
github.com/Microsoft/go-winio 1a8911d1ed007260465c3bfbbc785ac6915a0bb8
github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512
@@ -14,8 +13,8 @@ github.com/opencontainers/go-digest ac19fd6e7483ff933754af248d80be865e543d22
github.com/opencontainers/image-spec 243ea084a44451d27322fed02b682d99e2af3ba9
github.com/opencontainers/runc 923a8f8a9a07aceada5fc48c4d37e905d9b019b5
github.com/pkg/errors 27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7
+github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac
github.com/sirupsen/logrus d7b6bf5e4d26448fd977d07d745a2a66097ddecb
golang.org/x/crypto ff983b9c42bc9fbf91556e191cc8efb585c16908
golang.org/x/net 45ffb0cd1ba084b73e26dee67e667e1be5acce83
-golang.org/x/sync 37e7f081c4d4c64e13b10787722085407fe5d15f
golang.org/x/sys 7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba
diff --git a/vendor/github.com/varlink/go/varlink/bridge_windows.go b/vendor/github.com/varlink/go/varlink/bridge_windows.go
index 220ae3156..751224ec8 100644
--- a/vendor/github.com/varlink/go/varlink/bridge_windows.go
+++ b/vendor/github.com/varlink/go/varlink/bridge_windows.go
@@ -44,8 +44,8 @@ func NewBridge(bridge string) (*Connection, error) {
}
c.conn = PipeCon{nil, cmd, &r, &w}
c.address = ""
- c.reader = bufio.NewReader(r)
- c.writer = bufio.NewWriter(w)
+ c.Reader = bufio.NewReader(r)
+ c.Writer = bufio.NewWriter(w)
err = cmd.Start()
if err != nil {
diff --git a/vendor/github.com/varlink/go/varlink/call.go b/vendor/github.com/varlink/go/varlink/call.go
index d6e046f1d..0eaf24aca 100644
--- a/vendor/github.com/varlink/go/varlink/call.go
+++ b/vendor/github.com/varlink/go/varlink/call.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "net"
"strings"
)
@@ -14,36 +15,38 @@ import (
type Call struct {
*bufio.Reader
*bufio.Writer
- in *serviceCall
+ Conn *net.Conn
+ Request *[]byte
+ In *serviceCall
Continues bool
Upgrade bool
}
// WantsMore indicates if the calling client accepts more than one reply to this method call.
func (c *Call) WantsMore() bool {
- return c.in.More
+ return c.In.More
}
// WantsUpgrade indicates that the calling client wants the connection to be upgraded.
func (c *Call) WantsUpgrade() bool {
- return c.in.Upgrade
+ return c.In.Upgrade
}
// IsOneway indicate that the calling client does not expect a reply.
func (c *Call) IsOneway() bool {
- return c.in.Oneway
+ return c.In.Oneway
}
// GetParameters retrieves the method call parameters.
func (c *Call) GetParameters(p interface{}) error {
- if c.in.Parameters == nil {
+ if c.In.Parameters == nil {
return fmt.Errorf("empty parameters")
}
- return json.Unmarshal(*c.in.Parameters, p)
+ return json.Unmarshal(*c.In.Parameters, p)
}
func (c *Call) sendMessage(r *serviceReply) error {
- if c.in.Oneway {
+ if c.In.Oneway {
return nil
}
@@ -75,7 +78,7 @@ func (c *Call) Reply(parameters interface{}) error {
})
}
- if !c.in.More {
+ if !c.In.More {
return fmt.Errorf("call did not set more, it does not expect continues")
}
diff --git a/vendor/github.com/varlink/go/varlink/service.go b/vendor/github.com/varlink/go/varlink/service.go
index abccffe6a..bf13aa1de 100644
--- a/vendor/github.com/varlink/go/varlink/service.go
+++ b/vendor/github.com/varlink/go/varlink/service.go
@@ -74,7 +74,7 @@ func (s *Service) getInterfaceDescription(c Call, name string) error {
return c.replyGetInterfaceDescription(description)
}
-func (s *Service) handleMessage(reader *bufio.Reader, writer *bufio.Writer, request []byte) error {
+func (s *Service) HandleMessage(conn *net.Conn, reader *bufio.Reader, writer *bufio.Writer, request []byte) error {
var in serviceCall
err := json.Unmarshal(request, &in)
@@ -84,9 +84,11 @@ func (s *Service) handleMessage(reader *bufio.Reader, writer *bufio.Writer, requ
}
c := Call{
- Reader: reader,
- Writer: writer,
- in: &in,
+ Conn: conn,
+ Reader: reader,
+ Writer: writer,
+ In: &in,
+ Request: &request,
}
r := strings.LastIndex(in.Method, ".")
@@ -131,7 +133,7 @@ func (s *Service) handleConnection(conn net.Conn, wg *sync.WaitGroup) {
break
}
- err = s.handleMessage(reader, writer, request[:len(request)-1])
+ err = s.HandleMessage(&conn, reader, writer, request[:len(request)-1])
if err != nil {
// FIXME: report error
//fmt.Fprintf(os.Stderr, "handleMessage: %v", err)
@@ -179,25 +181,36 @@ func (s *Service) parseAddress(address string) error {
return nil
}
-func getListener(protocol string, address string) (net.Listener, error) {
+func (s *Service) GetListener() (*net.Listener, error) {
+ s.mutex.Lock()
+ l := s.listener
+ s.mutex.Unlock()
+ return &l, nil
+}
+
+func (s *Service) setListener() error {
l := activationListener()
if l == nil {
- if protocol == "unix" && address[0] != '@' {
- os.Remove(address)
+ if s.protocol == "unix" && s.address[0] != '@' {
+ os.Remove(s.address)
}
var err error
- l, err = net.Listen(protocol, address)
+ l, err = net.Listen(s.protocol, s.address)
if err != nil {
- return nil, err
+ return err
}
- if protocol == "unix" && address[0] != '@' {
+ if s.protocol == "unix" && s.address[0] != '@' {
l.(*net.UnixListener).SetUnlinkOnClose(true)
}
}
- return l, nil
+ s.mutex.Lock()
+ s.listener = l
+ s.mutex.Unlock()
+
+ return nil
}
func (s *Service) refreshTimeout(timeout time.Duration) error {
@@ -216,26 +229,84 @@ func (s *Service) refreshTimeout(timeout time.Duration) error {
}
// Listen starts a Service.
-func (s *Service) Listen(address string, timeout time.Duration) error {
- var wg sync.WaitGroup
- defer func() { s.teardown(); wg.Wait() }()
-
+func (s *Service) Bind(address string) error {
s.mutex.Lock()
if s.running {
s.mutex.Unlock()
- return fmt.Errorf("Listen(): already running")
+ return fmt.Errorf("Init(): already running")
}
s.mutex.Unlock()
s.parseAddress(address)
- l, err := getListener(s.protocol, s.address)
+ err := s.setListener()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Listen starts a Service.
+func (s *Service) Listen(address string, timeout time.Duration) error {
+ var wg sync.WaitGroup
+ defer func() { s.teardown(); wg.Wait() }()
+
+ err := s.Bind(address)
if err != nil {
return err
}
s.mutex.Lock()
- s.listener = l
+ s.running = true
+ l := s.listener
+ s.mutex.Unlock()
+
+ for s.running {
+ if timeout != 0 {
+ if err := s.refreshTimeout(timeout); err != nil {
+ return err
+ }
+ }
+ conn, err := l.Accept()
+ if err != nil {
+ if err.(net.Error).Timeout() {
+ s.mutex.Lock()
+ if s.conncounter == 0 {
+ s.mutex.Unlock()
+ return ServiceTimeoutError{}
+ }
+ s.mutex.Unlock()
+ continue
+ }
+ if !s.running {
+ return nil
+ }
+ return err
+ }
+ s.mutex.Lock()
+ s.conncounter++
+ s.mutex.Unlock()
+ wg.Add(1)
+ go s.handleConnection(conn, &wg)
+ }
+
+ return nil
+}
+
+// Listen starts a Service.
+func (s *Service) DoListen(timeout time.Duration) error {
+ var wg sync.WaitGroup
+ defer func() { s.teardown(); wg.Wait() }()
+
+ s.mutex.Lock()
+ l := s.listener
+ s.mutex.Unlock()
+
+ if l == nil {
+ return fmt.Errorf("No listener set")
+ }
+
+ s.mutex.Lock()
s.running = true
s.mutex.Unlock()
diff --git a/vendor/github.com/varlink/go/varlink/varlink_test.go b/vendor/github.com/varlink/go/varlink/varlink_test.go
index 9dd4ddc63..9e6d0a1f4 100644
--- a/vendor/github.com/varlink/go/varlink/varlink_test.go
+++ b/vendor/github.com/varlink/go/varlink/varlink_test.go
@@ -31,7 +31,7 @@ func TestService(t *testing.T) {
r := bufio.NewReader(&br)
var b bytes.Buffer
w := bufio.NewWriter(&b)
- if err := service.handleMessage(r, w, []byte{0}); err == nil {
+ if err := service.HandleMessage(nil, r, w, []byte{0}); err == nil {
t.Fatal("HandleMessage returned non-error")
}
})
@@ -42,7 +42,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"foo.GetInterfaceDescription" fdgdfg}`)
- if err := service.handleMessage(r, w, msg); err == nil {
+ if err := service.HandleMessage(nil, r, w, msg); err == nil {
t.Fatal("HandleMessage returned no error on invalid json")
}
})
@@ -53,7 +53,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"foo.GetInterfaceDescription"}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatal("HandleMessage returned error on wrong interface")
}
expect(t, `{"parameters":{"interface":"foo"},"error":"org.varlink.service.InterfaceNotFound"}`+"\000",
@@ -66,7 +66,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"InvalidMethod"}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatal("HandleMessage returned error on invalid method")
}
expect(t, `{"parameters":{"parameter":"method"},"error":"org.varlink.service.InvalidParameter"}`+"\000",
@@ -79,7 +79,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.WrongMethod"}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatal("HandleMessage returned error on wrong method")
}
expect(t, `{"parameters":{"method":"WrongMethod"},"error":"org.varlink.service.MethodNotFound"}`+"\000",
@@ -92,7 +92,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters": null}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"parameter":"parameters"},"error":"org.varlink.service.InvalidParameter"}`+"\000",
@@ -105,7 +105,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{}}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"parameter":"interface"},"error":"org.varlink.service.InvalidParameter"}`+"\000",
@@ -118,7 +118,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"foo"}}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"parameter":"interface"},"error":"org.varlink.service.InvalidParameter"}`+"\000",
@@ -131,7 +131,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"org.varlink.service"}}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"description":"# The Varlink Service Interface is provided by every varlink service. It\n# describes the service and the interfaces it implements.\ninterface org.varlink.service\n\n# Get a list of all the interfaces a service provides and information\n# about the implementation.\nmethod GetInfo() -\u003e (\n vendor: string,\n product: string,\n version: string,\n url: string,\n interfaces: []string\n)\n\n# Get the description of an interface that is implemented by this service.\nmethod GetInterfaceDescription(interface: string) -\u003e (description: string)\n\n# The requested interface was not found.\nerror InterfaceNotFound (interface: string)\n\n# The requested method was not found\nerror MethodNotFound (method: string)\n\n# The interface defines the requested method, but the service does not\n# implement it.\nerror MethodNotImplemented (method: string)\n\n# One of the passed parameters is invalid.\nerror InvalidParameter (parameter: string)"}}`+"\000",
@@ -144,7 +144,7 @@ func TestService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.varlink.service.GetInfo"}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"vendor":"Varlink","product":"Varlink Test","version":"1","url":"https://github.com/varlink/go/varlink","interfaces":["org.varlink.service"]}}`+"\000",
@@ -224,7 +224,7 @@ func TestMoreService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.example.test.Pingf"}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"parameters":{"method":"Pingf"},"error":"org.varlink.service.MethodNotImplemented"}`+"\000",
@@ -237,7 +237,7 @@ func TestMoreService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.example.test.PingError", "more" : true}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"error":"org.example.test.PingError"}`+"\000",
@@ -249,7 +249,7 @@ func TestMoreService(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
msg := []byte(`{"method":"org.example.test.Ping", "more" : true}`)
- if err := service.handleMessage(r, w, msg); err != nil {
+ if err := service.HandleMessage(nil, r, w, msg); err != nil {
t.Fatalf("HandleMessage returned error: %v", err)
}
expect(t, `{"continues":true}`+"\000"+`{"continues":true}`+"\000"+`{}`+"\000",