summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/checkpoint_restore.go151
-rw-r--r--pkg/adapter/client.go90
-rw-r--r--pkg/adapter/client_unix.go16
-rw-r--r--pkg/adapter/client_windows.go15
-rw-r--r--pkg/adapter/containers.go260
-rw-r--r--pkg/adapter/containers_remote.go328
-rw-r--r--pkg/adapter/errors.go8
-rw-r--r--pkg/adapter/info_remote.go14
-rw-r--r--pkg/adapter/pods.go321
-rw-r--r--pkg/adapter/pods_remote.go30
-rw-r--r--pkg/adapter/runtime.go49
-rw-r--r--pkg/adapter/runtime_remote.go81
-rw-r--r--pkg/adapter/sigproxy_linux.go6
-rw-r--r--pkg/adapter/terminal.go23
-rw-r--r--pkg/adapter/terminal_linux.go48
-rw-r--r--pkg/apparmor/apparmor_linux.go38
-rw-r--r--pkg/apparmor/apparmor_linux_test.go17
-rw-r--r--pkg/apparmor/apparmor_unsupported.go5
-rw-r--r--pkg/cgroups/blkio.go149
-rw-r--r--pkg/cgroups/cgroups.go493
-rw-r--r--pkg/cgroups/cgroups_supported.go27
-rw-r--r--pkg/cgroups/cgroups_unsupported.go8
-rw-r--r--pkg/cgroups/cpu.go123
-rw-r--r--pkg/cgroups/cpuset.go85
-rw-r--r--pkg/cgroups/memory.go66
-rw-r--r--pkg/cgroups/pids.go62
-rw-r--r--pkg/cgroups/systemd.go92
-rw-r--r--pkg/channelwriter/channelwriter.go34
-rw-r--r--pkg/ctime/ctime_linux.go1
-rw-r--r--pkg/errorhandling/errorhandling.go23
-rw-r--r--pkg/firewall/firewalld.go5
-rw-r--r--pkg/firewall/iptables.go10
-rw-r--r--pkg/hooks/0.1.0/hook.go2
-rw-r--r--pkg/hooks/1.0.0/when_test.go2
-rw-r--r--pkg/hooks/docs/oci-hooks.5.md2
-rw-r--r--pkg/hooks/exec/exec.go5
-rw-r--r--pkg/hooks/exec/runtimeconfigfilter.go12
-rw-r--r--pkg/hooks/hooks.go24
-rw-r--r--pkg/hooks/monitor.go49
-rw-r--r--pkg/hooks/monitor_test.go25
-rw-r--r--pkg/hooks/read.go11
-rw-r--r--pkg/inspect/inspect.go205
-rw-r--r--pkg/logs/logs.go12
-rw-r--r--pkg/namespaces/namespaces.go86
-rw-r--r--pkg/netns/netns_linux.go21
-rw-r--r--pkg/registries/registries.go16
-rw-r--r--pkg/rootless/rootless_linux.c118
-rw-r--r--pkg/rootless/rootless_linux.go178
-rw-r--r--pkg/rootless/rootless_unsupported.go19
-rw-r--r--pkg/spec/config_linux.go64
-rw-r--r--pkg/spec/config_linux_cgo.go34
-rw-r--r--pkg/spec/config_linux_nocgo.go11
-rw-r--r--pkg/spec/containerconfig.go5
-rw-r--r--pkg/spec/createconfig.go60
-rw-r--r--pkg/spec/parse.go6
-rw-r--r--pkg/spec/spec.go143
-rw-r--r--pkg/spec/storage.go56
-rw-r--r--pkg/sysinfo/sysinfo_linux.go2
-rw-r--r--pkg/sysinfo/sysinfo_solaris.go1
-rw-r--r--pkg/sysinfo/sysinfo_test.go2
-rw-r--r--pkg/systemdgen/systemdgen.go28
-rw-r--r--pkg/systemdgen/systemdgen_test.go18
-rw-r--r--pkg/tracing/tracing.go6
-rw-r--r--pkg/trust/trust.go2
-rw-r--r--pkg/util/mountOpts.go15
-rw-r--r--pkg/util/utils.go32
-rw-r--r--pkg/util/utils_darwin.go11
-rw-r--r--pkg/util/utils_linux.go54
-rw-r--r--pkg/util/utils_supported.go36
-rw-r--r--pkg/util/utils_unsupported.go12
-rw-r--r--pkg/util/utils_windows.go16
-rw-r--r--pkg/varlinkapi/attach.go21
-rw-r--r--pkg/varlinkapi/containers.go205
-rw-r--r--pkg/varlinkapi/images.go104
-rw-r--r--pkg/varlinkapi/system.go6
-rw-r--r--pkg/varlinkapi/transfers.go8
-rw-r--r--pkg/varlinkapi/util.go45
-rw-r--r--pkg/varlinkapi/virtwriter/virtwriter.go141
78 files changed, 3659 insertions, 950 deletions
diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/adapter/checkpoint_restore.go
new file mode 100644
index 000000000..1cac86d12
--- /dev/null
+++ b/pkg/adapter/checkpoint_restore.go
@@ -0,0 +1,151 @@
+// +build !remoteclient
+
+package adapter
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/errorhandling"
+ "github.com/containers/storage/pkg/archive"
+ jsoniter "github.com/json-iterator/go"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// Prefixing the checkpoint/restore related functions with 'cr'
+
+// crImportFromJSON imports the JSON files stored in the exported
+// checkpoint tarball
+func crImportFromJSON(filePath string, v interface{}) error {
+ jsonFile, err := os.Open(filePath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to open container definition %s for restore", filePath)
+ }
+ defer errorhandling.CloseQuiet(jsonFile)
+
+ content, err := ioutil.ReadAll(jsonFile)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to read container definition %s for restore", filePath)
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ if err = json.Unmarshal(content, v); err != nil {
+ return errors.Wrapf(err, "Failed to unmarshal container definition %s for restore", filePath)
+ }
+
+ return nil
+}
+
+// crImportCheckpoint it the function which imports the information
+// from checkpoint tarball and re-creates the container from that information
+func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
+ // First get the container definition from the
+ // tarball to a temporary directory
+ archiveFile, err := os.Open(input)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input)
+ }
+ defer errorhandling.CloseQuiet(archiveFile)
+ options := &archive.TarOptions{
+ // Here we only need the files config.dump and spec.dump
+ ExcludePatterns: []string{
+ "checkpoint",
+ "artifacts",
+ "ctr.log",
+ "rootfs-diff.tar",
+ "network.status",
+ },
+ }
+ dir, err := ioutil.TempDir("", "checkpoint")
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := os.RemoveAll(dir); err != nil {
+ logrus.Errorf("could not recursively remove %s: %q", dir, err)
+ }
+ }()
+ err = archive.Untar(archiveFile, dir, options)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input)
+ }
+
+ // Load spec.dump from temporary directory
+ dumpSpec := new(spec.Spec)
+ if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); err != nil {
+ return nil, err
+ }
+
+ // Load config.dump from temporary directory
+ config := new(libpod.ContainerConfig)
+ if err = crImportFromJSON(filepath.Join(dir, "config.dump"), config); err != nil {
+ return nil, err
+ }
+
+ // This should not happen as checkpoints with these options are not exported.
+ if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
+ return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
+ }
+
+ ctrID := config.ID
+ newName := false
+
+ // Check if the restored container gets a new name
+ if name != "" {
+ config.ID = ""
+ config.Name = name
+ newName = true
+ }
+
+ ctrName := config.Name
+
+ // The code to load the images is copied from create.go
+ // In create.go this only set if '--quiet' does not exist.
+ writer := os.Stderr
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Now create a new container from the just loaded information
+ container, err := runtime.RestoreContainer(ctx, dumpSpec, config)
+ if err != nil {
+ return nil, err
+ }
+
+ var containers []*libpod.Container
+ if container == nil {
+ return nil, nil
+ }
+
+ containerConfig := container.Config()
+ if containerConfig.Name != ctrName {
+ return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName)
+ }
+
+ if !newName {
+ // Only check ID for a restore with the same name.
+ // Using -n to request a new name for the restored container, will also create a new ID
+ if containerConfig.ID != ctrID {
+ return nil, errors.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID)
+ }
+ }
+
+ // Check if the ExitCommand points to the correct container ID
+ if containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1] != containerConfig.ID {
+ return nil, errors.Errorf("'ExitCommandID' uses ID %s instead of container ID %s", containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1], containerConfig.ID)
+ }
+
+ containers = append(containers, container)
+ return containers, nil
+}
diff --git a/pkg/adapter/client.go b/pkg/adapter/client.go
index 01914834f..694d9f961 100644
--- a/pkg/adapter/client.go
+++ b/pkg/adapter/client.go
@@ -6,42 +6,58 @@ import (
"fmt"
"os"
+ "github.com/containers/libpod/cmd/podman/remoteclientconfig"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/varlink/go/varlink"
)
var remoteEndpoint *Endpoint
func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) {
- if remoteEndpoint == nil {
- remoteEndpoint = &Endpoint{Unknown, ""}
- } else {
- return remoteEndpoint, nil
+ remoteConfigConnections, err := remoteclientconfig.ReadRemoteConfig(r.config)
+ if errors.Cause(err) != remoteclientconfig.ErrNoConfigationFile {
+ return nil, err
}
-
- // I'm leaving this here for now as a document of the birdge format. It can be removed later once the bridge
- // function is more flushed out.
- // bridge := `ssh -T root@192.168.122.1 "/usr/bin/varlink -A '/usr/bin/podman varlink \$VARLINK_ADDRESS' bridge"`
- if len(r.cmd.RemoteHost) > 0 {
- // The user has provided a remote host endpoint
+ // If the user defines an env variable for podman_varlink_bridge
+ // we use that as passed.
+ if bridge := os.Getenv("PODMAN_VARLINK_BRIDGE"); bridge != "" {
+ logrus.Debug("creating a varlink bridge based on env variable")
+ remoteEndpoint, err = newBridgeConnection(bridge, nil, r.cmd.LogLevel)
+ // if an environment variable for podman_varlink_address is defined,
+ // we used that as passed
+ } else if address := os.Getenv("PODMAN_VARLINK_ADDRESS"); address != "" {
+ logrus.Debug("creating a varlink address based on env variable: %s", address)
+ remoteEndpoint, err = newSocketConnection(address)
+ // if the user provides a remote host, we use it to configure a bridge connection
+ } else if len(r.cmd.RemoteHost) > 0 {
+ logrus.Debug("creating a varlink bridge based on user input")
if len(r.cmd.RemoteUserName) < 1 {
return nil, errors.New("you must provide a username when providing a remote host name")
}
- remoteEndpoint.Type = BridgeConnection
- remoteEndpoint.Connection = fmt.Sprintf(
- `ssh -T %s@%s /usr/bin/varlink -A \'/usr/bin/podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`,
- r.cmd.RemoteUserName, r.cmd.RemoteHost, r.cmd.LogLevel)
-
- } else if bridge := os.Getenv("PODMAN_VARLINK_BRIDGE"); bridge != "" {
- remoteEndpoint.Type = BridgeConnection
- remoteEndpoint.Connection = bridge
- } else {
- address := os.Getenv("PODMAN_VARLINK_ADDRESS")
- if address == "" {
- address = DefaultAddress
+ rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false}
+ remoteEndpoint, err = newBridgeConnection("", &rc, r.cmd.LogLevel)
+ // if the user has a config file with connections in it
+ } else if len(remoteConfigConnections.Connections) > 0 {
+ logrus.Debug("creating a varlink bridge based configuration file")
+ var rc *remoteclientconfig.RemoteConnection
+ if len(r.cmd.ConnectionName) > 0 {
+ rc, err = remoteConfigConnections.GetRemoteConnection(r.cmd.ConnectionName)
+ } else {
+ rc, err = remoteConfigConnections.GetDefault()
}
- remoteEndpoint.Type = DirectConnection
- remoteEndpoint.Connection = address
+ if err != nil {
+ return nil, err
+ }
+ if len(rc.Username) < 1 {
+ logrus.Debugf("Connection has no username, using current user %q", r.cmd.RemoteUserName)
+ rc.Username = r.cmd.RemoteUserName
+ }
+ remoteEndpoint, err = newBridgeConnection("", rc, r.cmd.LogLevel)
+ // last resort is to make a socket connection with the default varlink address for root user
+ } else {
+ logrus.Debug("creating a varlink address based default root address")
+ remoteEndpoint, err = newSocketConnection(DefaultAddress)
}
return
}
@@ -72,3 +88,29 @@ func (r RemoteRuntime) RefreshConnection() error {
r.Conn = newConn
return nil
}
+
+// newSocketConnection returns an endpoint for a uds based connection
+func newSocketConnection(address string) (*Endpoint, error) {
+ endpoint := Endpoint{
+ Type: DirectConnection,
+ Connection: address,
+ }
+ return &endpoint, nil
+}
+
+// newBridgeConnection creates a bridge type endpoint with username, destination, and log-level
+func newBridgeConnection(formattedBridge string, remoteConn *remoteclientconfig.RemoteConnection, logLevel string) (*Endpoint, error) {
+ endpoint := Endpoint{
+ Type: BridgeConnection,
+ }
+
+ if len(formattedBridge) < 1 && remoteConn == nil {
+ return nil, errors.New("bridge connections must either be created by string or remoteconnection")
+ }
+ if len(formattedBridge) > 0 {
+ endpoint.Connection = formattedBridge
+ return &endpoint, nil
+ }
+ endpoint.Connection = formatDefaultBridge(remoteConn, logLevel)
+ return &endpoint, nil
+}
diff --git a/pkg/adapter/client_unix.go b/pkg/adapter/client_unix.go
new file mode 100644
index 000000000..4781acd06
--- /dev/null
+++ b/pkg/adapter/client_unix.go
@@ -0,0 +1,16 @@
+// +build linux darwin
+// +build remoteclient
+
+package adapter
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/remoteclientconfig"
+)
+
+func formatDefaultBridge(remoteConn *remoteclientconfig.RemoteConnection, logLevel string) string {
+ return fmt.Sprintf(
+ `ssh -T %s@%s -- /usr/bin/varlink -A \'/usr/bin/podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`,
+ remoteConn.Username, remoteConn.Destination, logLevel)
+}
diff --git a/pkg/adapter/client_windows.go b/pkg/adapter/client_windows.go
new file mode 100644
index 000000000..31e5d9830
--- /dev/null
+++ b/pkg/adapter/client_windows.go
@@ -0,0 +1,15 @@
+// +build remoteclient
+
+package adapter
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/remoteclientconfig"
+)
+
+func formatDefaultBridge(remoteConn *remoteclientconfig.RemoteConnection, logLevel string) string {
+ return fmt.Sprintf(
+ `ssh -T %s@%s -- /usr/bin/varlink -A '/usr/bin/podman --log-level=%s varlink $VARLINK_ADDRESS' bridge`,
+ remoteConn.Username, remoteConn.Destination, logLevel)
+}
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index ff7b6377a..faaef3e60 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -6,6 +6,7 @@ import (
"bufio"
"context"
"fmt"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -15,9 +16,15 @@ import (
"syscall"
"time"
+ "github.com/containers/buildah"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/shared/parse"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/systemdgen"
"github.com/containers/psgo"
@@ -62,7 +69,7 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) {
var timeout *uint
if cli.Flags().Changed("timeout") || cli.Flags().Changed("time") {
- t := uint(cli.Timeout)
+ t := cli.Timeout
timeout = &t
}
@@ -88,14 +95,14 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa
}
pool.Add(shared.Job{
- c.ID(),
- func() error {
+ ID: c.ID(),
+ Fn: func() error {
err := c.StopWithTimeout(*timeout)
if err != nil {
- if errors.Cause(err) == libpod.ErrCtrStopped {
+ if errors.Cause(err) == define.ErrCtrStopped {
logrus.Debugf("Container %s is already stopped", c.ID())
return nil
- } else if cli.All && errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ } else if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
logrus.Debugf("Container %s is not running, could not stop", c.ID())
return nil
}
@@ -127,8 +134,8 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
c := c
pool.Add(shared.Job{
- c.ID(),
- func() error {
+ ID: c.ID(),
+ Fn: func() error {
return c.Kill(uint(signal))
},
})
@@ -156,12 +163,12 @@ func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitVa
ctr := c
pool.Add(shared.Job{
- ctr.ID(),
- func() error {
+ ID: ctr.ID(),
+ Fn: 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 {
+ if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
return nil
}
return err
@@ -186,12 +193,18 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
}
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
+ if cli.Storage {
+ for _, ctr := range cli.InputArgs {
+ if err := r.RemoveStorageContainer(ctr, cli.Force); err != nil {
+ failures[ctr] = err
+ }
+ ok = append(ok, ctr)
+ }
+ return ok, failures, nil
+ }
+
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
- // Force may be used to remove containers no longer found in the database
- if cli.Force && len(cli.InputArgs) > 0 && errors.Cause(err) == libpod.ErrNoSuchCtr {
- r.RemoveContainersFromStorage(cli.InputArgs)
- }
return ok, failures, err
}
@@ -200,8 +213,8 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
c := c
pool.Add(shared.Job{
- c.ID(),
- func() error {
+ ID: c.ID(),
+ Fn: func() error {
err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes)
if err != nil {
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
@@ -231,7 +244,7 @@ func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig
logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error())
continue
}
- if state == libpod.ContainerStateRunning {
+ if state == define.ContainerStateRunning {
logrus.Debugf("Error umounting container %s, is running", ctr.ID())
continue
}
@@ -272,13 +285,14 @@ func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.Wait
}
// Log logs one or more containers
-func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error {
+func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) error {
+
var wg sync.WaitGroup
options.WaitGroup = &wg
if len(c.InputArgs) > 1 {
options.Multi = true
}
- logChannel := make(chan *libpod.LogLine, int(c.Tail)*len(c.InputArgs)+1)
+ logChannel := make(chan *logs.LogLine, int(c.Tail)*len(c.InputArgs)+1)
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
if err != nil {
return err
@@ -328,7 +342,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
if err := ctr.Start(ctx, c.IsSet("pod")); err != nil {
// This means the command did not exist
exitCode = 127
- if strings.Index(err.Error(), "permission denied") > -1 {
+ if strings.Contains(err.Error(), "permission denied") {
exitCode = 126
}
return exitCode, err
@@ -366,22 +380,32 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
case "stdin":
inputStream = os.Stdin
default:
- return exitCode, errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
+ return exitCode, errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
}
}
}
+
+ config, err := r.Runtime.GetConfig()
+ if err != nil {
+ return exitCode, err
+ }
+ detachKeys := c.String("detach-keys")
+ if detachKeys == "" {
+ detachKeys = config.DetachKeys
+ }
+
// if the container was created as part of a pod, also start its dependencies, if any.
- if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil {
+ if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, detachKeys, c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil {
// We've manually detached from the container
// Do not perform cleanup, or wait for container exit code
// Just exit immediately
- if errors.Cause(err) == libpod.ErrDetach {
+ if errors.Cause(err) == define.ErrDetach {
exitCode = 0
return exitCode, nil
}
// This means the command did not exist
exitCode = 127
- if strings.Index(err.Error(), "permission denied") > -1 {
+ if strings.Contains(err.Error(), "permission denied") {
exitCode = 126
}
if c.IsSet("rm") {
@@ -393,13 +417,9 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
}
if ecode, err := ctr.Wait(); err != nil {
- if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
// The container may have been removed
// Go looking for an exit file
- config, err := r.Runtime.GetConfig()
- if err != nil {
- return exitCode, err
- }
ctrExitCode, err := ReadExitFile(config.TmpDir, ctr.ID())
if err != nil {
logrus.Errorf("Cannot get exit code: %v", err)
@@ -477,7 +497,7 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er
if err != nil {
return errors.Wrapf(err, "unable to determine state of %s", ctr.ID())
}
- if conState != libpod.ContainerStateRunning {
+ if conState != define.ContainerStateRunning {
return errors.Errorf("you can only attach to running containers")
}
@@ -486,19 +506,29 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er
inputStream = nil
}
// If the container is in a pod, also set to recursively start dependencies
- if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach {
+ if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
}
// Checkpoint one or more containers
-func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.ContainerCheckpointOptions) error {
+func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues) error {
var (
containers []*libpod.Container
err, lastError error
)
+ options := libpod.ContainerCheckpointOptions{
+ Keep: c.Keep,
+ KeepRunning: c.LeaveRunning,
+ TCPEstablished: c.TcpEstablished,
+ TargetFile: c.Export,
+ IgnoreRootfs: c.IgnoreRootfs,
+ }
+ if c.Export == "" && c.IgnoreRootfs {
+ return errors.Errorf("--ignore-rootfs can only be used with --export")
+ }
if c.All {
containers, err = r.Runtime.GetRunningContainers()
} else {
@@ -522,19 +552,29 @@ func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.
}
// Restore one or more containers
-func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.ContainerCheckpointOptions) error {
+func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) error {
var (
containers []*libpod.Container
err, lastError error
filterFuncs []libpod.ContainerFilter
)
+ options := libpod.ContainerCheckpointOptions{
+ Keep: c.Keep,
+ TCPEstablished: c.TcpEstablished,
+ TargetFile: c.Import,
+ Name: c.Name,
+ IgnoreRootfs: c.IgnoreRootfs,
+ }
+
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
state, _ := c.State()
- return state == libpod.ContainerStateExited
+ return state == define.ContainerStateExited
})
- if c.All {
+ if c.Import != "" {
+ containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
+ } else if c.All {
containers, err = r.GetContainers(filterFuncs...)
} else {
containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
@@ -587,7 +627,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
return exitCode, errors.Wrapf(err, "unable to get container state")
}
- ctrRunning := ctrState == libpod.ContainerStateRunning
+ ctrRunning := ctrState == define.ContainerStateRunning
if c.Attach {
inputStream := os.Stdin
@@ -598,7 +638,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
// attach to the container and also start it not already running
// If the container is in a pod, also set to recursively start dependencies
err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "")
- if errors.Cause(err) == libpod.ErrDetach {
+ if errors.Cause(err) == define.ErrDetach {
// User manually detached
// Exit cleanly immediately
exitCode = 0
@@ -614,7 +654,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
}
if ecode, err := ctr.Wait(); err != nil {
- if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
// The container may have been removed
// Go looking for an exit file
rtc, err := r.GetConfig()
@@ -713,7 +753,7 @@ func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.Unp
var filterFuncs []libpod.ContainerFilter
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
state, _ := c.State()
- return state == libpod.ContainerStatePaused
+ return state == define.ContainerStatePaused
})
ctrs, err = r.GetContainers(filterFuncs...)
} else {
@@ -884,13 +924,86 @@ func (r *LocalRuntime) execPS(c *libpod.Container, args []string) ([]string, err
}()
cmd := append([]string{"ps"}, args...)
- if err := c.Exec(false, false, []string{}, cmd, "", "", streams, 0); err != nil {
+ ec, err := c.Exec(false, false, []string{}, cmd, "", "", streams, 0, nil, "")
+ if err != nil {
return nil, err
+ } else if ec != 0 {
+ return nil, errors.Errorf("Runtime failed with exit status: %d and output: %s", ec, strings.Join(psOutput, " "))
}
return psOutput, nil
}
+// ExecContainer executes a command in the container
+func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
+ var (
+ ctr *Container
+ err error
+ cmd []string
+ )
+ // default invalid command exit code
+ ec := 125
+
+ if cli.Latest {
+ if ctr, err = r.GetLatestContainer(); err != nil {
+ return ec, err
+ }
+ cmd = cli.InputArgs[0:]
+ } else {
+ if ctr, err = r.LookupContainer(cli.InputArgs[0]); err != nil {
+ return ec, err
+ }
+ cmd = cli.InputArgs[1:]
+ }
+
+ if cli.PreserveFDs > 0 {
+ entries, err := ioutil.ReadDir("/proc/self/fd")
+ if err != nil {
+ return ec, errors.Wrapf(err, "unable to read /proc/self/fd")
+ }
+
+ m := make(map[int]bool)
+ for _, e := range entries {
+ i, err := strconv.Atoi(e.Name())
+ if err != nil {
+ return ec, errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name())
+ }
+ m[i] = true
+ }
+
+ for i := 3; i < 3+cli.PreserveFDs; i++ {
+ if _, found := m[i]; !found {
+ return ec, errors.New("invalid --preserve-fds=N specified. Not enough FDs available")
+ }
+ }
+ }
+
+ // Validate given environment variables
+ env := map[string]string{}
+ if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil {
+ return ec, errors.Wrapf(err, "unable to process environment variables")
+ }
+
+ // Build env slice of key=value strings for Exec
+ envs := []string{}
+ for k, v := range env {
+ envs = append(envs, fmt.Sprintf("%s=%s", k, v))
+ }
+
+ streams := new(libpod.AttachStreams)
+ streams.OutputStream = os.Stdout
+ streams.ErrorStream = os.Stderr
+ if cli.Interactive {
+ streams.InputStream = os.Stdin
+ streams.AttachInput = true
+ }
+ streams.AttachOutput = true
+ streams.AttachError = true
+
+ ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys)
+ return define.TranslateExecErrorToExitCode(ec, err), err
+}
+
// Prune removes stopped containers
func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) {
var (
@@ -910,7 +1023,7 @@ func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([
if c.PodID() != "" {
return false
}
- if state == libpod.ContainerStateStopped || state == libpod.ContainerStateExited {
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
return true
}
return false
@@ -1001,7 +1114,7 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
//Convert libpod containers to adapter Containers
for _, con := range containers {
- if state, _ := con.State(); state != libpod.ContainerStateRunning {
+ if state, _ := con.State(); state != define.ContainerStateRunning {
continue
}
portContainers = append(portContainers, &Container{con})
@@ -1017,16 +1130,75 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri
}
timeout := int(ctr.StopTimeout())
if c.StopTimeout >= 0 {
- timeout = int(c.StopTimeout)
+ timeout = c.StopTimeout
}
name := ctr.ID()
if c.Name {
name = ctr.Name()
}
- return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, ctr.Config().StaticDir, timeout)
+
+ config := ctr.Config()
+ conmonPidFile := config.ConmonPidFile
+ if conmonPidFile == "" {
+ return "", errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, conmonPidFile, timeout)
}
// GetNamespaces returns namespace information about a container for PS
func (r *LocalRuntime) GetNamespaces(container shared.PsContainerOutput) *shared.Namespace {
return shared.GetNamespaces(container.Pid)
}
+
+// Commit creates a local image from a container
+func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, container, imageName string) (string, error) {
+ var (
+ writer io.Writer
+ mimeType string
+ )
+ switch c.Format {
+ case "oci":
+ mimeType = buildah.OCIv1ImageManifest
+ if c.Flag("message").Changed {
+ return "", errors.Errorf("messages are only compatible with the docker image format (-f docker)")
+ }
+ case "docker":
+ mimeType = manifest.DockerV2Schema2MediaType
+ default:
+ return "", errors.Errorf("unrecognized image format %q", c.Format)
+ }
+ if !c.Quiet {
+ writer = os.Stderr
+ }
+ ctr, err := r.Runtime.LookupContainer(container)
+ if err != nil {
+ return "", errors.Wrapf(err, "error looking up container %q", container)
+ }
+
+ rtc, err := r.Runtime.GetConfig()
+ if err != nil {
+ return "", err
+ }
+
+ sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ coptions := buildah.CommitOptions{
+ SignaturePolicyPath: rtc.SignaturePolicyPath,
+ ReportWriter: writer,
+ SystemContext: sc,
+ PreferredManifestType: mimeType,
+ }
+ options := libpod.ContainerCommitOptions{
+ CommitOptions: coptions,
+ Pause: c.Pause,
+ IncludeVolumes: c.IncludeVolumes,
+ Message: c.Message,
+ Changes: c.Change,
+ Author: c.Author,
+ }
+ newImage, err := ctr.Commit(ctx, imageName, options)
+ if err != nil {
+ return "", err
+ }
+ return newImage.ID(), nil
+}
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go
index c34495b3d..5a26f537f 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -3,6 +3,7 @@
package adapter
import (
+ "bufio"
"context"
"encoding/json"
"fmt"
@@ -14,9 +15,11 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/shared/parse"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/inspect"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/docker/pkg/term"
@@ -29,12 +32,12 @@ import (
)
// Inspect returns an inspect struct from varlink
-func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) {
+func (c *Container) Inspect(size bool) (*libpod.InspectContainerData, error) {
reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size)
if err != nil {
return nil, err
}
- data := inspect.ContainerInspectData{}
+ data := libpod.InspectContainerData{}
if err := json.Unmarshal([]byte(reply), &data); err != nil {
return nil, err
}
@@ -240,11 +243,11 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa
for _, id := range ids {
if _, err := iopodman.StopContainer().Call(r.Conn, id, int64(cli.Timeout)); err != nil {
transError := TranslateError(err)
- if errors.Cause(transError) == libpod.ErrCtrStopped {
+ if errors.Cause(transError) == define.ErrCtrStopped {
ok = append(ok, id)
continue
}
- if errors.Cause(transError) == libpod.ErrCtrStateInvalid && cli.All {
+ if errors.Cause(transError) == define.ErrCtrStateInvalid && cli.All {
ok = append(ok, id)
continue
}
@@ -411,8 +414,8 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai
return bcs, nil
}
-// Logs one or more containers over a varlink connection
-func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error {
+// Log one or more containers over a varlink connection
+func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) error {
// GetContainersLogs
reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps)
if err != nil {
@@ -434,7 +437,7 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions)
if err != nil {
return errors.Wrapf(err, "unable to parse time of log %s", log.Time)
}
- logLine := libpod.LogLine{
+ logLine := logs.LogLine{
Device: log.Device,
ParseLogType: log.ParseLogType,
Time: lTime,
@@ -477,7 +480,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
}
func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
- return 0, libpod.ErrNotImplemented
+ return 0, define.ErrNotImplemented
}
// Ps lists containers based on criteria from user
@@ -492,6 +495,7 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
NoTrunc: &c.NoTrunct,
Pod: &c.Pod,
Quiet: &c.Quiet,
+ Size: &c.Size,
Sort: &c.Sort,
Sync: &c.Sync,
}
@@ -516,7 +520,7 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
RootFsSize: ctr.RootFsSize,
RwSize: ctr.RwSize,
}
- state, err := libpod.StringToContainerStatus(ctr.State)
+ state, err := define.StringToContainerStatus(ctr.State)
if err != nil {
return nil, err
}
@@ -552,92 +556,13 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
return psContainers, nil
}
-func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
- var (
- oldTermState *term.State
- )
- errChan := make(chan error)
- spec, err := r.Spec(cid)
- if err != nil {
- return nil, err
- }
- resize := make(chan remotecommand.TerminalSize, 5)
- 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 && 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 nil, errors.Wrapf(err, "unable to save terminal state")
- }
-
- logrus.SetFormatter(&RawTtyFormatter{})
- term.SetRawTerminal(os.Stdin.Fd())
-
- }
- // TODO add detach keys support
- _, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
- if err != nil {
- restoreTerminal(oldTermState)
- return nil, err
- }
-
- // These are the varlink sockets
- reader := r.Conn.Reader
- writer := r.Conn.Writer
-
- // These are the special writers that encode input from the client.
- varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin)
- varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize)
-
- go func() {
- // Read from the wire and direct to stdout or stderr
- err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil)
- defer restoreTerminal(oldTermState)
- errChan <- err
- }()
-
- go func() {
- for termResize := range resize {
- b, err := json.Marshal(termResize)
- if err != nil {
- defer restoreTerminal(oldTermState)
- errChan <- err
- }
- _, err = varlinkResizeWriter.Write(b)
- if err != nil {
- defer restoreTerminal(oldTermState)
- errChan <- err
- }
- }
- }()
-
- // Takes stdinput and sends it over the wire after being encoded
- go func() {
- if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil {
- defer restoreTerminal(oldTermState)
- errChan <- err
- }
-
- }()
- return errChan, nil
-
-}
-
// Attach to a remote terminal
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
ctr, err := r.LookupContainer(c.InputArgs[0])
if err != nil {
return nil
}
- if ctr.state.State != libpod.ContainerStateRunning {
+ if ctr.state.State != define.ContainerStateRunning {
return errors.New("you can only attach to running containers")
}
inputStream := os.Stdin
@@ -655,7 +580,14 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er
}
// Checkpoint one or more containers
-func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.ContainerCheckpointOptions) error {
+func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues) error {
+ if c.Export != "" {
+ return errors.New("the remote client does not support exporting checkpoints")
+ }
+ if c.IgnoreRootfs {
+ return errors.New("the remote client does not support --ignore-rootfs")
+ }
+
var lastError error
ids, err := iopodman.GetContainersByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
if err != nil {
@@ -670,7 +602,7 @@ func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.
if err != nil {
return err
}
- if ctr.state.State == libpod.ContainerStateRunning {
+ if ctr.state.State == define.ContainerStateRunning {
runningIds = append(runningIds, id)
}
}
@@ -678,7 +610,7 @@ func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.
}
for _, id := range ids {
- if _, err := iopodman.ContainerCheckpoint().Call(r.Conn, id, options.Keep, options.KeepRunning, options.TCPEstablished); err != nil {
+ if _, err := iopodman.ContainerCheckpoint().Call(r.Conn, id, c.Keep, c.Keep, c.TcpEstablished); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
@@ -691,7 +623,14 @@ func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.
}
// Restore one or more containers
-func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.ContainerCheckpointOptions) error {
+func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) error {
+ if c.Import != "" {
+ return errors.New("the remote client does not support importing checkpoints")
+ }
+ if c.IgnoreRootfs {
+ return errors.New("the remote client does not support --ignore-rootfs")
+ }
+
var lastError error
ids, err := iopodman.GetContainersByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
if err != nil {
@@ -706,7 +645,7 @@ func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.Contai
if err != nil {
return err
}
- if ctr.state.State != libpod.ContainerStateRunning {
+ if ctr.state.State != define.ContainerStateRunning {
exitedIDs = append(exitedIDs, id)
}
}
@@ -714,7 +653,7 @@ func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.Contai
}
for _, id := range ids {
- if _, err := iopodman.ContainerRestore().Call(r.Conn, id, options.Keep, options.TCPEstablished); err != nil {
+ if _, err := iopodman.ContainerRestore().Call(r.Conn, id, c.Keep, c.TcpEstablished); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
@@ -771,6 +710,49 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
return exitCode, finalErr
}
+func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
+ var (
+ oldTermState *term.State
+ )
+ spec, err := r.Spec(cid)
+ if err != nil {
+ return nil, err
+ }
+ resize := make(chan remotecommand.TerminalSize, 5)
+ 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 && spec.Process.Terminal {
+ cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
+ if err != nil {
+ return nil, err
+ }
+ defer cancel()
+ defer restoreTerminal(oldTermState)
+
+ logrus.SetFormatter(&RawTtyFormatter{})
+ term.SetRawTerminal(os.Stdin.Fd())
+ }
+
+ reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
+ if err != nil {
+ restoreTerminal(oldTermState)
+ return nil, err
+ }
+
+ // See if the server accepts the upgraded connection or returns an error
+ _, err = reply()
+
+ if err != nil {
+ restoreTerminal(oldTermState)
+ return nil, err
+ }
+
+ errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, nil)
+ return errChan, nil
+}
+
// PauseContainers pauses container(s) based on CLI inputs.
func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) {
var (
@@ -781,7 +763,7 @@ func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.Pause
)
if cli.All {
- filters := []string{libpod.ContainerStateRunning.String()}
+ filters := []string{define.ContainerStateRunning.String()}
ctrs, err = r.LookupContainersWithStatus(filters)
} else {
ctrs, err = r.LookupContainers(cli.InputArgs)
@@ -818,7 +800,7 @@ func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.Unp
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
if cli.All {
- filters := []string{libpod.ContainerStatePaused.String()}
+ filters := []string{define.ContainerStatePaused.String()}
ctrs, err = r.LookupContainersWithStatus(filters)
} else {
ctrs, err = r.LookupContainers(cli.InputArgs)
@@ -857,7 +839,7 @@ func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues)
}
restartContainers = append(restartContainers, lastCtr)
} else if c.Running {
- containers, err = r.LookupContainersWithStatus([]string{libpod.ContainerStateRunning.String()})
+ containers, err = r.LookupContainersWithStatus([]string{define.ContainerStateRunning.String()})
if err != nil {
return nil, nil, err
}
@@ -925,7 +907,7 @@ func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([
)
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
- filters := []string{libpod.ContainerStateExited.String()}
+ filters := []string{define.ContainerStateExited.String()}
ctrs, err = r.LookupContainersWithStatus(filters)
if err != nil {
return ok, failures, err
@@ -958,7 +940,7 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
containers, err = r.GetContainersByContext(false, c.Latest, c.InputArgs)
} else {
// we need to only use running containers if all
- filters := []string{libpod.ContainerStateRunning.String()}
+ filters := []string{define.ContainerStateRunning.String()}
containers, err = r.LookupContainersWithStatus(filters)
}
if err != nil {
@@ -986,3 +968,147 @@ func (r *LocalRuntime) GetNamespaces(container shared.PsContainerOutput) *shared
}
return &ns
}
+
+// Commit creates a local image from a container
+func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, container, imageName string) (string, error) {
+ var iid string
+ reply, err := iopodman.Commit().Send(r.Conn, varlink.More, container, imageName, c.Change, c.Author, c.Message, c.Pause, c.Format)
+ if err != nil {
+ return "", err
+ }
+ for {
+ responses, flags, err := reply()
+ if err != nil {
+ return "", err
+ }
+ for _, line := range responses.Logs {
+ fmt.Fprintln(os.Stderr, line)
+ }
+ iid = responses.Id
+ if flags&varlink.Continues == 0 {
+ break
+ }
+ }
+ return iid, nil
+}
+
+// ExecContainer executes a command in the container
+func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
+ var (
+ oldTermState *term.State
+ ec int = define.ExecErrorCodeGeneric
+ )
+ // default invalid command exit code
+ // Validate given environment variables
+ env := map[string]string{}
+ if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil {
+ return -1, errors.Wrapf(err, "Exec unable to process environment variables")
+ }
+
+ // Build env slice of key=value strings for Exec
+ envs := []string{}
+ for k, v := range env {
+ envs = append(envs, fmt.Sprintf("%s=%s", k, v))
+ }
+
+ resize := make(chan remotecommand.TerminalSize, 5)
+ 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 && cli.Tty {
+ cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
+ if err != nil {
+ return ec, err
+ }
+ defer cancel()
+ defer restoreTerminal(oldTermState)
+
+ logrus.SetFormatter(&RawTtyFormatter{})
+ term.SetRawTerminal(os.Stdin.Fd())
+ }
+
+ opts := iopodman.ExecOpts{
+ Name: cli.InputArgs[0],
+ Tty: cli.Tty,
+ Privileged: cli.Privileged,
+ Cmd: cli.InputArgs[1:],
+ User: &cli.User,
+ Workdir: &cli.Workdir,
+ Env: &envs,
+ DetachKeys: &cli.DetachKeys,
+ }
+
+ inputStream := os.Stdin
+ if !cli.Interactive {
+ inputStream = nil
+ }
+
+ reply, err := iopodman.ExecContainer().Send(r.Conn, varlink.Upgrade, opts)
+ if err != nil {
+ return ec, errors.Wrapf(err, "Exec failed to contact service for %s", cli.InputArgs)
+ }
+
+ _, err = reply()
+ if err != nil {
+ return ec, errors.Wrapf(err, "Exec operation failed for %s", cli.InputArgs)
+ }
+ ecChan := make(chan int, 1)
+ errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, inputStream, os.Stdout, oldTermState, resize, ecChan)
+
+ ec = <-ecChan
+ err = <-errChan
+
+ return ec, err
+}
+
+func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, stdin *os.File, stdout *os.File, oldTermState *term.State, resize chan remotecommand.TerminalSize, ecChan chan int) chan error {
+ errChan := make(chan error, 1)
+ // These are the special writers that encode input from the client.
+ varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin)
+ varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize)
+
+ go func() {
+ // Read from the wire and direct to stdout or stderr
+ err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil, ecChan)
+ defer restoreTerminal(oldTermState)
+ sendGenericError(ecChan)
+ errChan <- err
+ }()
+
+ go func() {
+ for termResize := range resize {
+ b, err := json.Marshal(termResize)
+ if err != nil {
+ defer restoreTerminal(oldTermState)
+ sendGenericError(ecChan)
+ errChan <- err
+ }
+ _, err = varlinkResizeWriter.Write(b)
+ if err != nil {
+ defer restoreTerminal(oldTermState)
+ sendGenericError(ecChan)
+ errChan <- err
+ }
+ }
+ }()
+
+ if stdin != nil {
+ // Takes stdinput and sends it over the wire after being encoded
+ go func() {
+ if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil {
+ defer restoreTerminal(oldTermState)
+ sendGenericError(ecChan)
+ errChan <- err
+ }
+
+ }()
+ }
+ return errChan
+}
+
+func sendGenericError(ecChan chan int) {
+ if ecChan != nil {
+ ecChan <- define.ExecErrorCodeGeneric
+ }
+}
diff --git a/pkg/adapter/errors.go b/pkg/adapter/errors.go
index 7fbbabd93..ede3d4b1a 100644
--- a/pkg/adapter/errors.go
+++ b/pkg/adapter/errors.go
@@ -4,7 +4,7 @@ package adapter
import (
iopodman "github.com/containers/libpod/cmd/podman/varlink"
- "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/pkg/errors"
)
@@ -21,11 +21,11 @@ func TranslateMapErrors(failures map[string]error) map[string]error {
func TranslateError(err error) error {
switch err.(type) {
case *iopodman.ContainerNotFound:
- return errors.Wrap(libpod.ErrNoSuchCtr, err.Error())
+ return errors.Wrap(define.ErrNoSuchCtr, err.Error())
case *iopodman.ErrCtrStopped:
- return errors.Wrap(libpod.ErrCtrStopped, err.Error())
+ return errors.Wrap(define.ErrCtrStopped, err.Error())
case *iopodman.InvalidState:
- return errors.Wrap(libpod.ErrCtrStateInvalid, err.Error())
+ return errors.Wrap(define.ErrCtrStateInvalid, err.Error())
}
return err
}
diff --git a/pkg/adapter/info_remote.go b/pkg/adapter/info_remote.go
index 3b2d02a5a..3170e5b3d 100644
--- a/pkg/adapter/info_remote.go
+++ b/pkg/adapter/info_remote.go
@@ -4,16 +4,16 @@ package adapter
import (
"encoding/json"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/cmd/podman/varlink"
- "github.com/containers/libpod/libpod"
)
// Info returns information for the host system and its components
-func (r RemoteRuntime) Info() ([]libpod.InfoData, error) {
+func (r RemoteRuntime) Info() ([]define.InfoData, error) {
// TODO the varlink implementation for info should be updated to match the output for regular info
var (
- reply []libpod.InfoData
+ reply []define.InfoData
hostInfo map[string]interface{}
store map[string]interface{}
)
@@ -43,9 +43,9 @@ func (r RemoteRuntime) Info() ([]libpod.InfoData, error) {
insecureRegistries["registries"] = info.Insecure_registries
// Add everything to the reply
- reply = append(reply, libpod.InfoData{Type: "host", Data: hostInfo})
- reply = append(reply, libpod.InfoData{Type: "registries", Data: registries})
- reply = append(reply, libpod.InfoData{Type: "insecure registries", Data: insecureRegistries})
- reply = append(reply, libpod.InfoData{Type: "store", Data: store})
+ reply = append(reply, define.InfoData{Type: "host", Data: hostInfo})
+ reply = append(reply, define.InfoData{Type: "registries", Data: registries})
+ reply = append(reply, define.InfoData{Type: "insecure registries", Data: insecureRegistries})
+ reply = append(reply, define.InfoData{Type: "store", Data: store})
return reply, nil
}
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index bb7d9cce6..b9d7fcd9b 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -4,14 +4,33 @@ package adapter
import (
"context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
"strings"
+ "github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/adapter/shortcuts"
+ ns "github.com/containers/libpod/pkg/namespaces"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/storage"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ v1 "k8s.io/api/core/v1"
+)
+
+const (
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ createDirectoryPermission = 0755
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ createFilePermission = 0644
)
// PodContainerStats is struct containing an adapter Pod and a libpod
@@ -51,8 +70,9 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal
for _, p := range pods {
p := p
- pool.Add(shared.Job{p.ID(),
- func() error {
+ pool.Add(shared.Job{
+ ID: p.ID(),
+ Fn: func() error {
err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force)
if err != nil {
logrus.Debugf("Failed to remove pod %s: %s", p.ID(), err.Error())
@@ -135,7 +155,7 @@ func (r *LocalRuntime) StopPods(ctx context.Context, cli *cliconfig.PodStopValue
for _, p := range pods {
stopped := true
- conErrs, stopErr := p.StopWithTimeout(ctx, true, int(timeout))
+ conErrs, stopErr := p.StopWithTimeout(ctx, true, timeout)
if stopErr != nil {
errs = append(errs, stopErr)
stopped = false
@@ -420,3 +440,298 @@ func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error)
}
return adapterPods, nil
}
+
+// PlayKubeYAML creates pods and containers from a kube YAML file
+func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayValues, yamlFile string) (*Pod, error) {
+ var (
+ containers []*libpod.Container
+ pod *libpod.Pod
+ podOptions []libpod.PodCreateOption
+ podYAML v1.Pod
+ registryCreds *types.DockerAuthConfig
+ writer io.Writer
+ )
+
+ content, err := ioutil.ReadFile(yamlFile)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := yaml.Unmarshal(content, &podYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read %s as YAML", yamlFile)
+ }
+
+ // check for name collision between pod and container
+ podName := podYAML.ObjectMeta.Name
+ for _, n := range podYAML.Spec.Containers {
+ if n.Name == podName {
+ fmt.Printf("a container exists with the same name (%s) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName)
+ podName = fmt.Sprintf("%s_pod", podName)
+ }
+ }
+
+ podOptions = append(podOptions, libpod.WithInfraContainer())
+ podOptions = append(podOptions, libpod.WithPodName(podName))
+ // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml
+
+ nsOptions, err := shared.GetNamespaceOptions(strings.Split(shared.DefaultKernelNamespaces, ","))
+ if err != nil {
+ return nil, err
+ }
+ podOptions = append(podOptions, nsOptions...)
+ podPorts := getPodPorts(podYAML.Spec.Containers)
+ podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts))
+
+ // Create the Pod
+ pod, err = r.NewPod(ctx, podOptions...)
+ if err != nil {
+ return nil, err
+ }
+
+ podInfraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns := false
+ if podInfraID != "" {
+ podCtr, err := r.GetContainer(podInfraID)
+ if err != nil {
+ return nil, err
+ }
+ mappings, err := podCtr.IDMappings()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns = len(mappings.UIDMap) > 0
+ }
+
+ namespaces := map[string]string{
+ // Disabled during code review per mheon
+ //"pid": fmt.Sprintf("container:%s", podInfraID),
+ "net": fmt.Sprintf("container:%s", podInfraID),
+ "ipc": fmt.Sprintf("container:%s", podInfraID),
+ "uts": fmt.Sprintf("container:%s", podInfraID),
+ }
+ if hasUserns {
+ namespaces["user"] = fmt.Sprintf("container:%s", podInfraID)
+ }
+ if !c.Quiet {
+ writer = os.Stderr
+ }
+
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: c.CertDir,
+ }
+ if c.Flag("tls-verify").Changed {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify)
+ }
+
+ // map from name to mount point
+ volumes := make(map[string]string)
+ for _, volume := range podYAML.Spec.Volumes {
+ hostPath := volume.VolumeSource.HostPath
+ if hostPath == nil {
+ return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
+ }
+ if hostPath.Type != nil {
+ switch *hostPath.Type {
+ case v1.HostPathDirectoryOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ if err := os.Mkdir(hostPath.Path, createDirectoryPermission); err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ }
+ // unconditionally label a newly created volume as private
+ if err := libpod.LabelVolumePath(hostPath.Path, false); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathFileOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, createFilePermission)
+ if err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ if err := f.Close(); err != nil {
+ logrus.Warnf("Error in closing newly created HostPath file: %v", err)
+ }
+ }
+ // unconditionally label a newly created volume as private
+ if err := libpod.LabelVolumePath(hostPath.Path, false); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathDirectory:
+ case v1.HostPathFile:
+ case v1.HostPathUnset:
+ // do nothing here because we will verify the path exists in validateVolumeHostDir
+ break
+ default:
+ return nil, errors.Errorf("Directories are the only supported HostPath type")
+ }
+ }
+
+ if err := createconfig.ValidateVolumeHostDir(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error in parsing HostPath in YAML")
+ }
+ volumes[volume.Name] = hostPath.Path
+ }
+
+ for _, container := range podYAML.Spec.Containers {
+ newImage, err := r.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, false, nil)
+ if err != nil {
+ return nil, err
+ }
+ createConfig, err := kubeContainerToCreateConfig(ctx, container, r.Runtime, newImage, namespaces, volumes, pod.ID())
+ if err != nil {
+ return nil, err
+ }
+ ctr, err := shared.CreateContainerFromCreateConfig(r.Runtime, createConfig, ctx, pod)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, ctr)
+ }
+
+ // start the containers
+ for _, ctr := range containers {
+ if err := ctr.Start(ctx, true); err != nil {
+ // Making this a hard failure here to avoid a mess
+ // the other containers are in created status
+ return nil, err
+ }
+ }
+
+ // We've now successfully converted this YAML into a pod
+ // print our pod and containers, signifying we succeeded
+ fmt.Printf("Pod:\n%s\n", pod.ID())
+ if len(containers) == 1 {
+ fmt.Printf("Container:\n")
+ }
+ if len(containers) > 1 {
+ fmt.Printf("Containers:\n")
+ }
+ for _, ctr := range containers {
+ fmt.Println(ctr.ID())
+ }
+
+ if err := playcleanup(ctx, r, pod, nil); err != nil {
+ logrus.Errorf("unable to remove pod %s after failing to play kube", pod.ID())
+ }
+ return nil, nil
+}
+
+func playcleanup(ctx context.Context, runtime *LocalRuntime, pod *libpod.Pod, err error) error {
+ if err != nil && pod != nil {
+ return runtime.RemovePod(ctx, pod, true, true)
+ }
+ return nil
+}
+
+// getPodPorts converts a slice of kube container descriptions to an
+// array of ocicni portmapping descriptions usable in libpod
+func getPodPorts(containers []v1.Container) []ocicni.PortMapping {
+ var infraPorts []ocicni.PortMapping
+ for _, container := range containers {
+ for _, p := range container.Ports {
+ portBinding := ocicni.PortMapping{
+ HostPort: p.HostPort,
+ ContainerPort: p.ContainerPort,
+ Protocol: strings.ToLower(string(p.Protocol)),
+ }
+ if p.HostIP != "" {
+ logrus.Debug("HostIP on port bindings is not supported")
+ }
+ infraPorts = append(infraPorts, portBinding)
+ }
+ }
+ return infraPorts
+}
+
+// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
+func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID string) (*createconfig.CreateConfig, error) {
+ var (
+ containerConfig createconfig.CreateConfig
+ )
+
+ // The default for MemorySwappiness is -1, not 0
+ containerConfig.Resources.MemorySwappiness = -1
+
+ containerConfig.Image = containerYAML.Image
+ containerConfig.ImageID = newImage.ID()
+ containerConfig.Name = containerYAML.Name
+ containerConfig.Tty = containerYAML.TTY
+ containerConfig.WorkDir = containerYAML.WorkingDir
+
+ containerConfig.Pod = podID
+
+ imageData, _ := newImage.Inspect(ctx)
+
+ containerConfig.User = "0"
+ if imageData != nil {
+ containerConfig.User = imageData.Config.User
+ }
+
+ if containerConfig.SecurityOpts != nil {
+ if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
+ containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
+ }
+ if containerYAML.SecurityContext.Privileged != nil {
+ containerConfig.Privileged = *containerYAML.SecurityContext.Privileged
+ }
+
+ if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
+ containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
+ }
+ }
+
+ containerConfig.Command = []string{}
+ if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...)
+ }
+ if len(containerYAML.Command) != 0 {
+ containerConfig.Command = append(containerConfig.Command, containerYAML.Command...)
+ } else if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...)
+ }
+ if imageData != nil && len(containerConfig.Command) == 0 {
+ return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name)
+ }
+
+ containerConfig.StopSignal = 15
+
+ // If the user does not pass in ID mappings, just set to basics
+ if containerConfig.IDMappings == nil {
+ containerConfig.IDMappings = &storage.IDMappingOptions{}
+ }
+
+ containerConfig.NetMode = ns.NetworkMode(namespaces["net"])
+ containerConfig.IpcMode = ns.IpcMode(namespaces["ipc"])
+ containerConfig.UtsMode = ns.UTSMode(namespaces["uts"])
+ // disabled in code review per mheon
+ //containerConfig.PidMode = ns.PidMode(namespaces["pid"])
+ containerConfig.UsernsMode = ns.UsernsMode(namespaces["user"])
+ if len(containerConfig.WorkDir) == 0 {
+ containerConfig.WorkDir = "/"
+ }
+
+ // Set default environment variables and incorporate data from image, if necessary
+ envs := shared.EnvVariablesFromData(imageData)
+
+ // Environment Variables
+ for _, e := range containerYAML.Env {
+ envs[e.Name] = e.Value
+ }
+ containerConfig.Env = envs
+
+ for _, volume := range containerYAML.VolumeMounts {
+ hostPath, exists := volumes[volume.Name]
+ if !exists {
+ return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
+ }
+ if err := createconfig.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ return nil, errors.Wrapf(err, "error in parsing MountPath")
+ }
+ containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath))
+ }
+ return &containerConfig, nil
+}
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index e2c97c36a..0c62ac923 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/varlinkapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -257,25 +258,25 @@ func (p *Pod) AllContainers() ([]*Container, error) {
}
// Status ...
-func (p *Pod) Status() (map[string]libpod.ContainerStatus, error) {
- ctrs := make(map[string]libpod.ContainerStatus)
+func (p *Pod) Status() (map[string]define.ContainerStatus, error) {
+ ctrs := make(map[string]define.ContainerStatus)
for _, i := range p.containers {
- var status libpod.ContainerStatus
+ var status define.ContainerStatus
switch i.State {
case "exited":
- status = libpod.ContainerStateExited
+ status = define.ContainerStateExited
case "stopped":
- status = libpod.ContainerStateStopped
+ status = define.ContainerStateStopped
case "running":
- status = libpod.ContainerStateRunning
+ status = define.ContainerStateRunning
case "paused":
- status = libpod.ContainerStatePaused
+ status = define.ContainerStatePaused
case "created":
- status = libpod.ContainerStateCreated
- case "configured":
- status = libpod.ContainerStateConfigured
+ status = define.ContainerStateCreated
+ case "define.red":
+ status = define.ContainerStateConfigured
default:
- status = libpod.ContainerStateUnknown
+ status = define.ContainerStateUnknown
}
ctrs[i.ID] = status
}
@@ -509,7 +510,7 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerSta
newStats := varlinkapi.ContainerStatsToLibpodContainerStats(stats)
// If the container wasn't running, don't include it
// but also suppress the error
- if err != nil && errors.Cause(err) != libpod.ErrCtrStateInvalid {
+ if err != nil && errors.Cause(err) != define.ErrCtrStateInvalid {
return nil, err
}
if err == nil {
@@ -563,3 +564,8 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal
}
return ok, failures, nil
}
+
+// PlayKubeYAML creates pods and containers from a kube YAML file
+func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayValues, yamlFile string) (*Pod, error) {
+ return nil, define.ErrNotImplemented
+}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 37ee1b737..ee6913cc0 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -5,6 +5,7 @@ package adapter
import (
"bufio"
"context"
+ "github.com/containers/libpod/libpod/define"
"io"
"io/ioutil"
"os"
@@ -57,12 +58,26 @@ type Volume struct {
// VolumeFilter is for filtering volumes on the client
type VolumeFilter func(*Volume) bool
+// GetRuntimeNoStore returns a localruntime struct wit an embedded runtime but
+// without a configured storage.
+func GetRuntimeNoStore(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
+ runtime, err := libpodruntime.GetRuntimeNoStore(ctx, c)
+ if err != nil {
+ return nil, err
+ }
+ return getRuntime(runtime)
+}
+
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
runtime, err := libpodruntime.GetRuntime(ctx, c)
if err != nil {
return nil, err
}
+ return getRuntime(runtime)
+}
+
+func getRuntime(runtime *libpod.Runtime) (*LocalRuntime, error) {
return &LocalRuntime{
Runtime: runtime,
}, nil
@@ -70,16 +85,27 @@ func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime,
// GetImages returns a slice of images in containerimages
func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
+ return r.getImages(false)
+}
+
+// GetRWImages returns a slice of read/write images in containerimages
+func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) {
+ return r.getImages(true)
+}
+
+func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) {
var containerImages []*ContainerImage
images, err := r.Runtime.ImageRuntime().GetImages()
if err != nil {
return nil, err
}
for _, i := range images {
+ if rwOnly && i.IsReadOnly() {
+ continue
+ }
containerImages = append(containerImages, &ContainerImage{i})
}
return containerImages, nil
-
}
// NewImageFromLocal returns a containerimage representation of a image from local storage
@@ -306,15 +332,17 @@ func (r *LocalRuntime) LoadImage(ctx context.Context, name string, cli *cliconfi
// IsImageNotFound checks if the error indicates that no image was found.
func IsImageNotFound(err error) bool {
- if errors.Cause(err) == image.ErrNoSuchImage {
- return true
- }
- return false
+ return errors.Cause(err) == image.ErrNoSuchImage
}
// HealthCheck is a wrapper to same named function in libpod
-func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
- return r.Runtime.HealthCheck(c.InputArgs[0])
+func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (string, error) {
+ output := "unhealthy"
+ status, err := r.Runtime.HealthCheck(c.InputArgs[0])
+ if status == libpod.HealthCheckSuccess {
+ output = "healthy"
+ }
+ return output, err
}
// Events is a wrapper to libpod to obtain libpod/podman events
@@ -339,9 +367,6 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
if eventsError != nil {
return eventsError
}
- if err != nil {
- return errors.Wrapf(err, "unable to tail the events log")
- }
w := bufio.NewWriter(os.Stdout)
for event := range eventChannel {
if len(c.Format) > 0 {
@@ -395,8 +420,8 @@ func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*libpod.Pod, error)
}
// GetVersion is an alias to satisfy interface{}
-func (r *LocalRuntime) GetVersion() (libpod.Version, error) {
- return libpod.GetVersion()
+func (r *LocalRuntime) GetVersion() (define.Version, error) {
+ return define.GetVersion()
}
// RemoteEndpoint resolve interface requirement
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index e0c0898bd..9fae39df0 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -20,8 +20,10 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/remoteclientconfig"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/utils"
@@ -40,6 +42,7 @@ type RemoteRuntime struct {
Conn *varlink.Connection
Remote bool
cmd cliconfig.MainFlags
+ config io.Reader
}
// LocalRuntime describes a typical libpod runtime
@@ -47,12 +50,43 @@ type LocalRuntime struct {
*RemoteRuntime
}
+// GetRuntimeNoStore returns a LocalRuntime struct with the actual runtime embedded in it
+// The nostore is ignored
+func GetRuntimeNoStore(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
+ return GetRuntime(ctx, c)
+}
+
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
+ var (
+ customConfig bool
+ err error
+ f *os.File
+ )
runtime := RemoteRuntime{
Remote: true,
cmd: c.GlobalFlags,
}
+ configPath := remoteclientconfig.GetConfigFilePath()
+ if len(c.GlobalFlags.RemoteConfigFilePath) > 0 {
+ configPath = c.GlobalFlags.RemoteConfigFilePath
+ customConfig = true
+ }
+
+ f, err = os.Open(configPath)
+ if err != nil {
+ // If user does not explicitly provide a configuration file path and we cannot
+ // find a default, no error should occur.
+ if os.IsNotExist(err) && !customConfig {
+ logrus.Debugf("unable to load configuration file at %s", configPath)
+ runtime.config = nil
+ } else {
+ return nil, errors.Wrapf(err, "unable to load configuration file at %s", configPath)
+ }
+ } else {
+ // create the io reader for the remote client
+ runtime.config = bufio.NewReader(f)
+ }
conn, err := runtime.Connect()
if err != nil {
return nil, err
@@ -63,6 +97,14 @@ func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime,
}, nil
}
+// DeferredShutdown is a bogus wrapper for compaat with the libpod
+// runtime and should only be run when a defer is being used
+func (r RemoteRuntime) DeferredShutdown(force bool) {
+ if err := r.Shutdown(force); err != nil {
+ logrus.Error("unable to shutdown runtime")
+ }
+}
+
// Shutdown is a bogus wrapper for compat with the libpod runtime
func (r RemoteRuntime) Shutdown(force bool) error {
return nil
@@ -87,6 +129,7 @@ type remoteImage struct {
isParent bool
Runtime *LocalRuntime
TopLayer string
+ ReadOnly bool
}
// Container ...
@@ -127,12 +170,24 @@ type remoteVolume struct {
// GetImages returns a slice of containerimages over a varlink connection
func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
+ return r.getImages(false)
+}
+
+// GetRWImages returns a slice of read/write containerimages over a varlink connection
+func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) {
+ return r.getImages(true)
+}
+
+func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) {
var newImages []*ContainerImage
images, err := iopodman.ListImages().Call(r.Conn)
if err != nil {
return nil, err
}
for _, i := range images {
+ if rwOnly && i.ReadOnly {
+ continue
+ }
name := i.Id
if len(i.RepoTags) > 1 {
name = i.RepoTags[0]
@@ -165,6 +220,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu
isParent: i.IsParent,
Runtime: runtime,
TopLayer: i.TopLayer,
+ ReadOnly: i.ReadOnly,
}
return &ContainerImage{ri}, nil
}
@@ -260,6 +316,11 @@ func (ci *ContainerImage) Created() time.Time {
return ci.remoteImage.Created
}
+// IsReadOnly returns whether the image is ReadOnly
+func (ci *ContainerImage) IsReadOnly() bool {
+ return ci.remoteImage.ReadOnly
+}
+
// Size returns the size of the image
func (ci *ContainerImage) Size(ctx context.Context) (*uint64, error) {
usize := uint64(ci.remoteImage.Size)
@@ -500,12 +561,12 @@ func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) {
// GetAllVolumes retrieves all the volumes
func (r *LocalRuntime) GetAllVolumes() ([]*libpod.Volume, error) {
- return nil, libpod.ErrNotImplemented
+ return nil, define.ErrNotImplemented
}
// RemoveVolume removes a volumes
func (r *LocalRuntime) RemoveVolume(ctx context.Context, v *libpod.Volume, force, prune bool) error {
- return libpod.ErrNotImplemented
+ return define.ErrNotImplemented
}
// GetContainers retrieves all containers from the state
@@ -513,14 +574,14 @@ func (r *LocalRuntime) RemoveVolume(ctx context.Context, v *libpod.Volume, force
// the output. Multiple filters are handled by ANDing their output, so only
// containers matching all filters are returned
func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libpod.Container, error) {
- return nil, libpod.ErrNotImplemented
+ return nil, define.ErrNotImplemented
}
// RemoveContainer removes the given container
// If force is specified, the container will be stopped first
// Otherwise, RemoveContainer will return an error if the container is running
func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error {
- return libpod.ErrNotImplemented
+ return define.ErrNotImplemented
}
// CreateVolume creates a volume over a varlink connection for the remote client
@@ -743,8 +804,8 @@ func IsImageNotFound(err error) bool {
}
// HealthCheck executes a container's healthcheck over a varlink connection
-func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
- return -1, libpod.ErrNotImplemented
+func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (string, error) {
+ return "", define.ErrNotImplemented
}
// Events monitors libpod/podman events over a varlink connection
@@ -879,22 +940,22 @@ func (r *LocalRuntime) GetContainersByContext(all bool, latest bool, namesOrIDs
}
// GetVersion returns version information from service
-func (r *LocalRuntime) GetVersion() (libpod.Version, error) {
+func (r *LocalRuntime) GetVersion() (define.Version, error) {
version, goVersion, gitCommit, built, osArch, apiVersion, err := iopodman.GetVersion().Call(r.Conn)
if err != nil {
- return libpod.Version{}, errors.Wrapf(err, "Unable to obtain server version information")
+ return define.Version{}, errors.Wrapf(err, "Unable to obtain server version information")
}
var buildTime int64
if built != "" {
t, err := time.Parse(time.RFC3339, built)
if err != nil {
- return libpod.Version{}, nil
+ return define.Version{}, nil
}
buildTime = t.Unix()
}
- return libpod.Version{
+ return define.Version{
RemoteAPIVersion: apiVersion,
Version: version,
GoVersion: goVersion,
diff --git a/pkg/adapter/sigproxy_linux.go b/pkg/adapter/sigproxy_linux.go
index af968cb89..ebfeab725 100644
--- a/pkg/adapter/sigproxy_linux.go
+++ b/pkg/adapter/sigproxy_linux.go
@@ -27,10 +27,10 @@ func ProxySignals(ctr *libpod.Container) {
if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil {
logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err)
signal.StopCatch(sigBuffer)
- syscall.Kill(syscall.Getpid(), s.(syscall.Signal))
+ if err := syscall.Kill(syscall.Getpid(), s.(syscall.Signal)); err != nil {
+ logrus.Errorf("failed to kill pid %d", syscall.Getpid())
+ }
}
}
}()
-
- return
}
diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go
index 373c78322..51b747d23 100644
--- a/pkg/adapter/terminal.go
+++ b/pkg/adapter/terminal.go
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/remotecommand"
)
@@ -76,3 +77,25 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return bytes, err
}
+
+func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) {
+ logrus.Debugf("Handling terminal attach")
+
+ subCtx, cancel := context.WithCancel(ctx)
+
+ resizeTty(subCtx, resize)
+
+ oldTermState, err := term.SaveState(os.Stdin.Fd())
+ if err != nil {
+ // allow caller to not have to do any cleaning up if we error here
+ cancel()
+ return nil, nil, errors.Wrapf(err, "unable to save terminal state")
+ }
+
+ logrus.SetFormatter(&RawTtyFormatter{})
+ if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil {
+ return cancel, nil, err
+ }
+
+ return cancel, oldTermState, nil
+}
diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go
index 3c4c3bd38..26cfd7b5e 100644
--- a/pkg/adapter/terminal_linux.go
+++ b/pkg/adapter/terminal_linux.go
@@ -6,38 +6,56 @@ import (
"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 {
+// ExecAttachCtr execs and attaches to a container
+func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs int, detachKeys string) (int, 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)
+ if haveTerminal && tty {
+ cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
+ if err != nil {
+ return -1, err
+ }
defer cancel()
+ defer func() {
+ if err := restoreTerminal(oldTermState); err != nil {
+ logrus.Errorf("unable to restore terminal: %q", err)
+ }
+ }()
+ }
+ return ctr.Exec(tty, privileged, env, cmd, user, workDir, streams, preserveFDs, resize, detachKeys)
+}
- resizeTty(subCtx, resize)
+// StartAttachCtr starts and (if required) attaches to a container
+// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream
+// error. we may need to just lint disable this one.
+func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer
+ resize := make(chan remotecommand.TerminalSize)
- oldTermState, err := term.SaveState(os.Stdin.Fd())
+ 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 {
+ cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
if err != nil {
- return errors.Wrapf(err, "unable to save terminal state")
+ return err
}
-
- logrus.SetFormatter(&RawTtyFormatter{})
- term.SetRawTerminal(os.Stdin.Fd())
-
- defer restoreTerminal(oldTermState)
+ defer func() {
+ if err := restoreTerminal(oldTermState); err != nil {
+ logrus.Errorf("unable to restore terminal: %q", err)
+ }
+ }()
+ defer cancel()
}
streams := new(libpod.AttachStreams)
diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go
index 2c5022c1f..33710ff56 100644
--- a/pkg/apparmor/apparmor_linux.go
+++ b/pkg/apparmor/apparmor_linux.go
@@ -4,6 +4,7 @@ package apparmor
import (
"bufio"
+ "bytes"
"fmt"
"io"
"os"
@@ -91,19 +92,39 @@ func InstallDefault(name string) error {
return err
}
if err := cmd.Start(); err != nil {
- pipe.Close()
+ if pipeErr := pipe.Close(); pipeErr != nil {
+ logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
+ }
return err
}
if err := p.generateDefault(pipe); err != nil {
- pipe.Close()
- cmd.Wait()
+ if pipeErr := pipe.Close(); pipeErr != nil {
+ logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
+ }
+ if cmdErr := cmd.Wait(); cmdErr != nil {
+ logrus.Errorf("unable to wait for apparmor command: %q", cmdErr)
+ }
return err
}
- pipe.Close()
+ if pipeErr := pipe.Close(); pipeErr != nil {
+ logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
+ }
return cmd.Wait()
}
+// DefaultContent returns the default profile content as byte slice. The
+// profile is named as the provided `name`. The function errors if the profile
+// generation fails.
+func DefaultContent(name string) ([]byte, error) {
+ p := profileData{Name: name}
+ var bytes bytes.Buffer
+ if err := p.generateDefault(&bytes); err != nil {
+ return nil, err
+ }
+ return bytes.Bytes(), nil
+}
+
// IsLoaded checks if a profile with the given name has been loaded into the
// kernel.
func IsLoaded(name string) (bool, error) {
@@ -225,8 +246,13 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
}
}
- if name != "" && !runcaa.IsEnabled() {
- return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name)
+ // Check if AppArmor is disabled and error out if a profile is to be set.
+ if !runcaa.IsEnabled() {
+ if name == "" {
+ return "", nil
+ } else {
+ return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name)
+ }
}
// If the specified name is not empty or is not a default libpod one,
diff --git a/pkg/apparmor/apparmor_linux_test.go b/pkg/apparmor/apparmor_linux_test.go
index ac3260723..e94293d87 100644
--- a/pkg/apparmor/apparmor_linux_test.go
+++ b/pkg/apparmor/apparmor_linux_test.go
@@ -78,10 +78,12 @@ Copyright 2009-2012 Canonical Ltd.
}
}
-func TestInstallDefault(t *testing.T) {
- profile := "libpod-default-testing"
- aapath := "/sys/kernel/security/apparmor/"
+const (
+ aapath = "/sys/kernel/security/apparmor/"
+ profile = "libpod-default-testing"
+)
+func TestInstallDefault(t *testing.T) {
if _, err := os.Stat(aapath); err != nil {
t.Skip("AppArmor isn't available in this environment")
}
@@ -127,3 +129,12 @@ func TestInstallDefault(t *testing.T) {
}
checkLoaded(false)
}
+
+func TestDefaultContent(t *testing.T) {
+ if _, err := os.Stat(aapath); err != nil {
+ t.Skip("AppArmor isn't available in this environment")
+ }
+ if err := DefaultContent(profile); err != nil {
+ t.Fatalf("Couldn't retrieve default AppArmor profile content '%s': %v", profile, err)
+ }
+}
diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go
index b2b4de5f5..13469f1b6 100644
--- a/pkg/apparmor/apparmor_unsupported.go
+++ b/pkg/apparmor/apparmor_unsupported.go
@@ -24,3 +24,8 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
}
return "", ErrApparmorUnsupported
}
+
+// DefaultContent dummy.
+func DefaultContent(name string) ([]byte, error) {
+ return nil, nil
+}
diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go
new file mode 100644
index 000000000..bacd4eb93
--- /dev/null
+++ b/pkg/cgroups/blkio.go
@@ -0,0 +1,149 @@
+package cgroups
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+type blkioHandler struct {
+}
+
+func getBlkioHandler() *blkioHandler {
+ return &blkioHandler{}
+}
+
+// Apply set the specified constraints
+func (c *blkioHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
+ if res.BlockIO == nil {
+ return nil
+ }
+ return fmt.Errorf("blkio apply function not implemented yet")
+}
+
+// Create the cgroup
+func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Blkio)
+}
+
+// Destroy the cgroup
+func (c *blkioHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Blkio))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error {
+ var ioServiceBytesRecursive []BlkIOEntry
+
+ if ctr.cgroup2 {
+ // more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html
+ values, err := readCgroup2MapFile(ctr, "io.stat")
+ if err != nil {
+ return err
+ }
+ for k, v := range values {
+ d := strings.Split(k, ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ for _, item := range v {
+ d := strings.Split(item, "=")
+ if len(d) != 2 {
+ continue
+ }
+ op := d[0]
+
+ // Accommodate the cgroup v1 naming
+ switch op {
+ case "rbytes":
+ op = "read"
+ case "wbytes":
+ op = "write"
+ }
+
+ value, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ entry := BlkIOEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ }
+ } else {
+ BlkioRoot := ctr.getCgroupv1Path(Blkio)
+
+ p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive")
+ f, err := os.Open(p)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return errors.Wrapf(err, "open %s", p)
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 3 {
+ continue
+ }
+ d := strings.Split(parts[0], ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ op := parts[1]
+
+ value, err := strconv.ParseUint(parts[2], 10, 0)
+ if err != nil {
+ return err
+ }
+ entry := BlkIOEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ if err := scanner.Err(); err != nil {
+ return errors.Wrapf(err, "parse %s", p)
+ }
+ }
+ m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive}
+ return nil
+}
diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go
new file mode 100644
index 000000000..f2c6b548e
--- /dev/null
+++ b/pkg/cgroups/cgroups.go
@@ -0,0 +1,493 @@
+package cgroups
+
+import (
+ "bufio"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ // ErrCgroupDeleted means the cgroup was deleted
+ ErrCgroupDeleted = errors.New("cgroups: cgroup deleted")
+)
+
+// CgroupControl controls a cgroup hierarchy
+type CgroupControl struct {
+ cgroup2 bool
+ path string
+ systemd bool
+ // List of additional cgroup subsystems joined that
+ // do not have a custom handler.
+ additionalControllers []controller
+}
+
+// CPUUsage keeps stats for the CPU usage (unit: nanoseconds)
+type CPUUsage struct {
+ Kernel uint64
+ Total uint64
+ PerCPU []uint64
+}
+
+// MemoryUsage keeps stats for the memory usage
+type MemoryUsage struct {
+ Usage uint64
+ Limit uint64
+}
+
+// CPUMetrics keeps stats for the CPU usage
+type CPUMetrics struct {
+ Usage CPUUsage
+}
+
+// BlkIOEntry describes an entry in the blkio stats
+type BlkIOEntry struct {
+ Op string
+ Major uint64
+ Minor uint64
+ Value uint64
+}
+
+// BlkioMetrics keeps usage stats for the blkio cgroup controller
+type BlkioMetrics struct {
+ IoServiceBytesRecursive []BlkIOEntry
+}
+
+// MemoryMetrics keeps usage stats for the memory cgroup controller
+type MemoryMetrics struct {
+ Usage MemoryUsage
+}
+
+// PidsMetrics keeps usage stats for the pids cgroup controller
+type PidsMetrics struct {
+ Current uint64
+}
+
+// Metrics keeps usage stats for the cgroup controllers
+type Metrics struct {
+ CPU CPUMetrics
+ Blkio BlkioMetrics
+ Memory MemoryMetrics
+ Pids PidsMetrics
+}
+
+type controller struct {
+ name string
+ symlink bool
+}
+
+type controllerHandler interface {
+ Create(*CgroupControl) (bool, error)
+ Apply(*CgroupControl, *spec.LinuxResources) error
+ Destroy(*CgroupControl) error
+ Stat(*CgroupControl, *Metrics) error
+}
+
+const (
+ cgroupRoot = "/sys/fs/cgroup"
+ _cgroup2SuperMagic = 0x63677270
+ // CPU is the cpu controller
+ CPU = "cpu"
+ // CPUAcct is the cpuacct controller
+ CPUAcct = "cpuacct"
+ // CPUset is the cpuset controller
+ CPUset = "cpuset"
+ // Memory is the memory controller
+ Memory = "memory"
+ // Pids is the pids controller
+ Pids = "pids"
+ // Blkio is the blkio controller
+ Blkio = "blkio"
+)
+
+var handlers map[string]controllerHandler
+
+func init() {
+ handlers = make(map[string]controllerHandler)
+ handlers[CPU] = getCPUHandler()
+ handlers[CPUset] = getCpusetHandler()
+ handlers[Memory] = getMemoryHandler()
+ handlers[Pids] = getPidsHandler()
+ handlers[Blkio] = getBlkioHandler()
+}
+
+// getAvailableControllers get the available controllers
+func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
+ if cgroup2 {
+ return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2")
+ }
+
+ infos, err := ioutil.ReadDir(cgroupRoot)
+ if err != nil {
+ return nil, errors.Wrapf(err, "read directory %s", cgroupRoot)
+ }
+ var controllers []controller
+ for _, i := range infos {
+ name := i.Name()
+ if _, found := exclude[name]; found {
+ continue
+ }
+ c := controller{
+ name: name,
+ symlink: !i.IsDir(),
+ }
+ controllers = append(controllers, c)
+ }
+ return controllers, nil
+}
+
+// getCgroupv1Path is a helper function to get the cgroup v1 path
+func (c *CgroupControl) getCgroupv1Path(name string) string {
+ return filepath.Join(cgroupRoot, name, c.path)
+}
+
+// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
+func createCgroupv2Path(path string) (Err error) {
+ content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
+ if err != nil {
+ return errors.Wrapf(err, "read /sys/fs/cgroup/cgroup.controllers")
+ }
+ if !strings.HasPrefix(path, "/sys/fs/cgroup/") {
+ return fmt.Errorf("invalid cgroup path %s", path)
+ }
+
+ res := ""
+ for i, c := range strings.Split(strings.TrimSpace(string(content)), " ") {
+ if i == 0 {
+ res = fmt.Sprintf("+%s", c)
+ } else {
+ res = res + fmt.Sprintf(" +%s", c)
+ }
+ }
+ resByte := []byte(res)
+
+ current := "/sys/fs"
+ elements := strings.Split(path, "/")
+ for i, e := range elements[3:] {
+ current = filepath.Join(current, e)
+ if i > 0 {
+ if err := os.Mkdir(current, 0755); err != nil {
+ if !os.IsExist(err) {
+ return errors.Wrapf(err, "mkdir %s", path)
+ }
+ } else {
+ // If the directory was created, be sure it is not left around on errors.
+ defer func() {
+ if Err != nil {
+ os.Remove(current)
+ }
+ }()
+ }
+ }
+ // We enable the controllers for all the path components except the last one. It is not allowed to add
+ // PIDs if there are already enabled controllers.
+ if i < len(elements[3:])-1 {
+ if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), resByte, 0755); err != nil {
+ return errors.Wrapf(err, "write %s", filepath.Join(current, "cgroup.subtree_control"))
+ }
+ }
+ }
+ return nil
+}
+
+// initialize initializes the specified hierarchy
+func (c *CgroupControl) initialize() (err error) {
+ createdSoFar := map[string]controllerHandler{}
+ defer func() {
+ if err != nil {
+ for name, ctr := range createdSoFar {
+ if err := ctr.Destroy(c); err != nil {
+ logrus.Warningf("error cleaning up controller %s for %s", name, c.path)
+ }
+ }
+ }
+ }()
+ if c.cgroup2 {
+ if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.path)); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path %s", c.path)
+ }
+ }
+ for name, handler := range handlers {
+ created, err := handler.Create(c)
+ if err != nil {
+ return err
+ }
+ if created {
+ createdSoFar[name] = handler
+ }
+ }
+
+ if !c.cgroup2 {
+ // We won't need to do this for cgroup v2
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ path := c.getCgroupv1Path(ctr.name)
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path %s for %s", path, ctr.name)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
+ cPath := c.getCgroupv1Path(controller)
+ _, err := os.Stat(cPath)
+ if err == nil {
+ return false, nil
+ }
+
+ if !os.IsNotExist(err) {
+ return false, err
+ }
+
+ if err := os.MkdirAll(cPath, 0755); err != nil {
+ return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
+ }
+ return true, nil
+}
+
+func readFileAsUint64(path string) (uint64, error) {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return 0, errors.Wrapf(err, "open %s", path)
+ }
+ v := cleanString(string(data))
+ if v == "max" {
+ return math.MaxUint64, nil
+ }
+ ret, err := strconv.ParseUint(v, 10, 0)
+ if err != nil {
+ return ret, errors.Wrapf(err, "parse %s from %s", v, path)
+ }
+ return ret, nil
+}
+
+// New creates a new cgroup control
+func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ path: path,
+ }
+
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+
+ if err := control.initialize(); err != nil {
+ return nil, err
+ }
+
+ return control, nil
+}
+
+// NewSystemd creates a new cgroup control
+func NewSystemd(path string) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ path: path,
+ systemd: true,
+ }
+ return control, nil
+}
+
+// Load loads an existing cgroup control
+func Load(path string) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ path: path,
+ systemd: false,
+ }
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+ if !cgroup2 {
+ for name := range handlers {
+ p := control.getCgroupv1Path(name)
+ if _, err := os.Stat(p); err != nil {
+ if os.IsNotExist(err) {
+ // compatible with the error code
+ // used by containerd/cgroups
+ return nil, ErrCgroupDeleted
+ }
+ }
+ }
+ }
+ return control, nil
+}
+
+// CreateSystemdUnit creates the systemd cgroup
+func (c *CgroupControl) CreateSystemdUnit(path string) error {
+ if !c.systemd {
+ return fmt.Errorf("the cgroup controller is not using systemd")
+ }
+ return systemdCreate(path)
+}
+
+// Delete cleans a cgroup
+func (c *CgroupControl) Delete() error {
+ return c.DeleteByPath(c.path)
+}
+
+// rmDirRecursively delete recursively a cgroup directory.
+// It differs from os.RemoveAll as it doesn't attempt to unlink files.
+// On cgroupfs we are allowed only to rmdir empty directories.
+func rmDirRecursively(path string) error {
+ if err := os.Remove(path); err == nil || os.IsNotExist(err) {
+ return nil
+ }
+ entries, err := ioutil.ReadDir(path)
+ if err != nil {
+ return errors.Wrapf(err, "read %s", path)
+ }
+ for _, i := range entries {
+ if i.IsDir() {
+ if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil {
+ return err
+ }
+ }
+ }
+ if err := os.Remove(path); err != nil {
+ if !os.IsNotExist(err) {
+ return errors.Wrapf(err, "remove %s", path)
+ }
+ }
+ return nil
+}
+
+// DeleteByPath deletes the specified cgroup path
+func (c *CgroupControl) DeleteByPath(path string) error {
+ if c.systemd {
+ return systemdDestroy(path)
+ }
+ if c.cgroup2 {
+ return rmDirRecursively(filepath.Join(cgroupRoot, c.path))
+ }
+ var lastError error
+ for _, h := range handlers {
+ if err := h.Destroy(c); err != nil {
+ lastError = err
+ }
+ }
+
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ p := c.getCgroupv1Path(ctr.name)
+ if err := rmDirRecursively(p); err != nil {
+ lastError = errors.Wrapf(err, "remove %s", p)
+ }
+ }
+ return lastError
+}
+
+// Update updates the cgroups
+func (c *CgroupControl) Update(resources *spec.LinuxResources) error {
+ for _, h := range handlers {
+ if err := h.Apply(c, resources); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// AddPid moves the specified pid to the cgroup
+func (c *CgroupControl) AddPid(pid int) error {
+ pidString := []byte(fmt.Sprintf("%d\n", pid))
+
+ if c.cgroup2 {
+ p := filepath.Join(cgroupRoot, c.path, "cgroup.procs")
+ if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
+ return errors.Wrapf(err, "write %s", p)
+ }
+ return nil
+ }
+
+ var names []string
+ for n := range handlers {
+ names = append(names, n)
+ }
+
+ for _, c := range c.additionalControllers {
+ if !c.symlink {
+ names = append(names, c.name)
+ }
+ }
+
+ for _, n := range names {
+ p := filepath.Join(c.getCgroupv1Path(n), "tasks")
+ if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
+ return errors.Wrapf(err, "write %s", p)
+ }
+ }
+ return nil
+}
+
+// Stat returns usage statistics for the cgroup
+func (c *CgroupControl) Stat() (*Metrics, error) {
+ m := Metrics{}
+ for _, h := range handlers {
+ if err := h.Stat(c, &m); err != nil {
+ return nil, err
+ }
+ }
+ return &m, nil
+}
+
+func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
+ ret := map[string][]string{}
+ p := filepath.Join(cgroupRoot, ctr.path, name)
+ f, err := os.Open(p)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return ret, nil
+ }
+ return nil, errors.Wrapf(err, "open file %s", p)
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ continue
+ }
+ ret[parts[0]] = parts[1:]
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, errors.Wrapf(err, "parsing file %s", p)
+ }
+ return ret, nil
+}
diff --git a/pkg/cgroups/cgroups_supported.go b/pkg/cgroups/cgroups_supported.go
new file mode 100644
index 000000000..fcd44dfc8
--- /dev/null
+++ b/pkg/cgroups/cgroups_supported.go
@@ -0,0 +1,27 @@
+// +build linux
+
+package cgroups
+
+import (
+ "sync"
+ "syscall"
+)
+
+var (
+ isUnifiedOnce sync.Once
+ isUnified bool
+ isUnifiedErr error
+)
+
+// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
+func IsCgroup2UnifiedMode() (bool, error) {
+ isUnifiedOnce.Do(func() {
+ var st syscall.Statfs_t
+ if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
+ isUnified, isUnifiedErr = false, err
+ } else {
+ isUnified, isUnifiedErr = st.Type == _cgroup2SuperMagic, nil
+ }
+ })
+ return isUnified, isUnifiedErr
+}
diff --git a/pkg/cgroups/cgroups_unsupported.go b/pkg/cgroups/cgroups_unsupported.go
new file mode 100644
index 000000000..9dc196e42
--- /dev/null
+++ b/pkg/cgroups/cgroups_unsupported.go
@@ -0,0 +1,8 @@
+// +build !linux
+
+package cgroups
+
+// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
+func IsCgroup2UnifiedMode() (bool, error) {
+ return false, nil
+}
diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go
new file mode 100644
index 000000000..03677f1ef
--- /dev/null
+++ b/pkg/cgroups/cpu.go
@@ -0,0 +1,123 @@
+package cgroups
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+type cpuHandler struct {
+}
+
+func getCPUHandler() *cpuHandler {
+ return &cpuHandler{}
+}
+
+func cleanString(s string) string {
+ return strings.Trim(s, "\n")
+}
+
+func readAcct(ctr *CgroupControl, name string) (uint64, error) {
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ return readFileAsUint64(p)
+}
+
+func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
+ var r []uint64
+
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ data, err := ioutil.ReadFile(p)
+ if err != nil {
+ return nil, errors.Wrapf(err, "reading %s", p)
+ }
+ for _, s := range strings.Split(string(data), " ") {
+ s = cleanString(s)
+ if s == "" {
+ break
+ }
+ v, err := strconv.ParseUint(s, 10, 0)
+ if err != nil {
+ return nil, errors.Wrapf(err, "parsing %s", s)
+ }
+ r = append(r, v)
+ }
+ return r, nil
+}
+
+// Apply set the specified constraints
+func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
+ if res.CPU == nil {
+ return nil
+ }
+ return fmt.Errorf("cpu apply not implemented yet")
+}
+
+// Create the cgroup
+func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(CPU)
+}
+
+// Destroy the cgroup
+func (c *cpuHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPU))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
+ var err error
+ usage := CPUUsage{}
+ if ctr.cgroup2 {
+ values, err := readCgroup2MapFile(ctr, "cpu.stat")
+ if err != nil {
+ return err
+ }
+ if val, found := values["usage_usec"]; found {
+ usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 0)
+ if err != nil {
+ return err
+ }
+ usage.Kernel *= 1000
+ }
+ if val, found := values["system_usec"]; found {
+ usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 0)
+ if err != nil {
+ return err
+ }
+ usage.Total *= 1000
+ }
+ // FIXME: How to read usage.PerCPU?
+ } else {
+ usage.Total, err = readAcct(ctr, "cpuacct.usage")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ usage.Total = 0
+ }
+ usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ usage.Kernel = 0
+ }
+ usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ usage.PerCPU = nil
+ }
+ }
+ m.CPU = CPUMetrics{Usage: usage}
+ return nil
+}
diff --git a/pkg/cgroups/cpuset.go b/pkg/cgroups/cpuset.go
new file mode 100644
index 000000000..46d0484f2
--- /dev/null
+++ b/pkg/cgroups/cpuset.go
@@ -0,0 +1,85 @@
+package cgroups
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strings"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+type cpusetHandler struct {
+}
+
+func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
+ if dir == cgroupRoot {
+ return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
+ }
+ path := filepath.Join(dir, file)
+ parentPath := path
+ if cgroupv2 {
+ parentPath = fmt.Sprintf("%s.effective", parentPath)
+ }
+ data, err := ioutil.ReadFile(parentPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "open %s", path)
+ }
+ if len(strings.Trim(string(data), "\n")) != 0 {
+ return data, nil
+ }
+ data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
+ if err != nil {
+ return nil, err
+ }
+ if err := ioutil.WriteFile(path, data, 0644); err != nil {
+ return nil, errors.Wrapf(err, "write %s", path)
+ }
+ return data, nil
+}
+
+func cpusetCopyFromParent(path string, cgroupv2 bool) error {
+ for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
+ if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getCpusetHandler() *cpusetHandler {
+ return &cpusetHandler{}
+}
+
+// Apply set the specified constraints
+func (c *cpusetHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
+ if res.CPU == nil {
+ return nil
+ }
+ return fmt.Errorf("cpuset apply not implemented yet")
+}
+
+// Create the cgroup
+func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ path := filepath.Join(cgroupRoot, ctr.path)
+ return true, cpusetCopyFromParent(path, true)
+ }
+
+ created, err := ctr.createCgroupDirectory(CPUset)
+ if !created || err != nil {
+ return created, err
+ }
+ return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset), false)
+}
+
+// Destroy the cgroup
+func (c *cpusetHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPUset))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *cpusetHandler) Stat(ctr *CgroupControl, m *Metrics) error {
+ return nil
+}
diff --git a/pkg/cgroups/memory.go b/pkg/cgroups/memory.go
new file mode 100644
index 000000000..b3991f7e3
--- /dev/null
+++ b/pkg/cgroups/memory.go
@@ -0,0 +1,66 @@
+package cgroups
+
+import (
+ "fmt"
+ "path/filepath"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+type memHandler struct {
+}
+
+func getMemoryHandler() *memHandler {
+ return &memHandler{}
+}
+
+// Apply set the specified constraints
+func (c *memHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
+ if res.Memory == nil {
+ return nil
+ }
+ return fmt.Errorf("memory apply not implemented yet")
+}
+
+// Create the cgroup
+func (c *memHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Memory)
+}
+
+// Destroy the cgroup
+func (c *memHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Memory))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *memHandler) Stat(ctr *CgroupControl, m *Metrics) error {
+ var err error
+ usage := MemoryUsage{}
+
+ var memoryRoot string
+ filenames := map[string]string{}
+
+ if ctr.cgroup2 {
+ memoryRoot = filepath.Join(cgroupRoot, ctr.path)
+ filenames["usage"] = "memory.current"
+ filenames["limit"] = "memory.max"
+ } else {
+ memoryRoot = ctr.getCgroupv1Path(Memory)
+ filenames["usage"] = "memory.usage_in_bytes"
+ filenames["limit"] = "memory.limit_in_bytes"
+ }
+ usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["usage"]))
+ if err != nil {
+ return err
+ }
+ usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["limit"]))
+ if err != nil {
+ return err
+ }
+
+ m.Memory = MemoryMetrics{Usage: usage}
+ return nil
+}
diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go
new file mode 100644
index 000000000..65b9b5b34
--- /dev/null
+++ b/pkg/cgroups/pids.go
@@ -0,0 +1,62 @@
+package cgroups
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+type pidHandler struct {
+}
+
+func getPidsHandler() *pidHandler {
+ return &pidHandler{}
+}
+
+// Apply set the specified constraints
+func (c *pidHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
+ if res.Pids == nil {
+ return nil
+ }
+ var PIDRoot string
+
+ if ctr.cgroup2 {
+ PIDRoot = filepath.Join(cgroupRoot, ctr.path)
+ } else {
+ PIDRoot = ctr.getCgroupv1Path(Pids)
+ }
+
+ p := filepath.Join(PIDRoot, "pids.max")
+ return ioutil.WriteFile(p, []byte(fmt.Sprintf("%d\n", res.Pids.Limit)), 0644)
+}
+
+// Create the cgroup
+func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) {
+ return ctr.createCgroupDirectory(Pids)
+}
+
+// Destroy the cgroup
+func (c *pidHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Pids))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error {
+ var PIDRoot string
+
+ if ctr.cgroup2 {
+ PIDRoot = filepath.Join(cgroupRoot, ctr.path)
+ } else {
+ PIDRoot = ctr.getCgroupv1Path(Pids)
+ }
+
+ current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current"))
+ if err != nil {
+ return err
+ }
+
+ m.Pids = PidsMetrics{Current: current}
+ return nil
+}
diff --git a/pkg/cgroups/systemd.go b/pkg/cgroups/systemd.go
new file mode 100644
index 000000000..e72e456bc
--- /dev/null
+++ b/pkg/cgroups/systemd.go
@@ -0,0 +1,92 @@
+package cgroups
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ systemdDbus "github.com/coreos/go-systemd/dbus"
+ "github.com/godbus/dbus"
+)
+
+func systemdCreate(path string) error {
+ c, err := systemdDbus.New()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ slice, name := filepath.Split(path)
+ slice = strings.TrimSuffix(slice, "/")
+
+ var lastError error
+ for i := 0; i < 2; i++ {
+ properties := []systemdDbus.Property{
+ systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
+ systemdDbus.PropWants(slice),
+ }
+ pMap := map[string]bool{
+ "DefaultDependencies": false,
+ "MemoryAccounting": true,
+ "CPUAccounting": true,
+ "BlockIOAccounting": true,
+ }
+ if i == 0 {
+ pMap["Delegate"] = true
+ }
+ for k, v := range pMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ ch := make(chan string)
+ _, err = c.StartTransientUnit(name, "replace", properties, ch)
+ if err != nil {
+ lastError = err
+ continue
+ }
+ <-ch
+ return nil
+ }
+ return lastError
+}
+
+/*
+ systemdDestroy is copied from containerd/cgroups/systemd.go file, that
+ has the following license:
+
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+func systemdDestroy(path string) error {
+ c, err := systemdDbus.New()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ name := filepath.Base(path)
+
+ ch := make(chan string)
+ _, err = c.StopUnit(name, "replace", ch)
+ if err != nil {
+ return err
+ }
+ <-ch
+ return nil
+}
diff --git a/pkg/channelwriter/channelwriter.go b/pkg/channelwriter/channelwriter.go
new file mode 100644
index 000000000..d51400eb3
--- /dev/null
+++ b/pkg/channelwriter/channelwriter.go
@@ -0,0 +1,34 @@
+package channelwriter
+
+import "github.com/pkg/errors"
+
+// Writer is an io.writer-like object that "writes" to a channel
+// instead of a buffer or file, etc. It is handy for varlink endpoints when
+// needing to handle endpoints that do logging "real-time"
+type Writer struct {
+ ByteChannel chan []byte
+}
+
+// NewChannelWriter creates a new channel writer and adds a
+// byte slice channel into it.
+func NewChannelWriter() *Writer {
+ byteChannel := make(chan []byte)
+ return &Writer{
+ ByteChannel: byteChannel,
+ }
+}
+
+// Write method for Writer
+func (c *Writer) Write(w []byte) (int, error) {
+ if c.ByteChannel == nil {
+ return 0, errors.New("channel writer channel cannot be nil")
+ }
+ c.ByteChannel <- w
+ return len(w), nil
+}
+
+// Close method for Writer
+func (c *Writer) Close() error {
+ close(c.ByteChannel)
+ return nil
+}
diff --git a/pkg/ctime/ctime_linux.go b/pkg/ctime/ctime_linux.go
index e83269d49..113693e87 100644
--- a/pkg/ctime/ctime_linux.go
+++ b/pkg/ctime/ctime_linux.go
@@ -10,5 +10,6 @@ import (
func created(fi os.FileInfo) time.Time {
st := fi.Sys().(*syscall.Stat_t)
+ //nolint
return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec))
}
diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go
new file mode 100644
index 000000000..970d47636
--- /dev/null
+++ b/pkg/errorhandling/errorhandling.go
@@ -0,0 +1,23 @@
+package errorhandling
+
+import (
+ "os"
+
+ "github.com/sirupsen/logrus"
+)
+
+// SyncQuiet syncs a file and logs any error. Should only be used within
+// a defer.
+func SyncQuiet(f *os.File) {
+ if err := f.Sync(); err != nil {
+ logrus.Errorf("unable to sync file %s: %q", f.Name(), err)
+ }
+}
+
+// CloseQuiet closes a file and logs any error. Should only be used within
+// a defer.
+func CloseQuiet(f *os.File) {
+ if err := f.Close(); err != nil {
+ logrus.Errorf("unable to close file %s: %q", f.Name(), err)
+ }
+}
diff --git a/pkg/firewall/firewalld.go b/pkg/firewall/firewalld.go
index 32c2337a0..15e845cb7 100644
--- a/pkg/firewall/firewalld.go
+++ b/pkg/firewall/firewalld.go
@@ -18,6 +18,7 @@ package firewall
import (
"fmt"
+ "github.com/sirupsen/logrus"
"strings"
"github.com/godbus/dbus"
@@ -113,7 +114,9 @@ func (fb *fwdBackend) Del(conf *FirewallNetConf) error {
// Remove firewalld rules which assigned the given source IP to the given zone
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
var res string
- firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, getFirewalldZone(conf), ipStr).Store(&res)
+ if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, getFirewalldZone(conf), ipStr).Store(&res); err != nil {
+ logrus.Errorf("unable to store firewallobj")
+ }
}
return nil
}
diff --git a/pkg/firewall/iptables.go b/pkg/firewall/iptables.go
index 59d81b287..169ddc1d7 100644
--- a/pkg/firewall/iptables.go
+++ b/pkg/firewall/iptables.go
@@ -21,6 +21,7 @@ package firewall
import (
"fmt"
+ "github.com/sirupsen/logrus"
"net"
"github.com/coreos/go-iptables/iptables"
@@ -53,7 +54,9 @@ func generateFilterRule(privChainName string) []string {
func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) {
for _, rule := range rules {
- ipt.Delete("filter", privChainName, rule...)
+ if err := ipt.Delete("filter", privChainName, rule...); err != nil {
+ logrus.Errorf("failed to delete iptables rule %s", privChainName)
+ }
}
}
@@ -148,7 +151,6 @@ type iptablesBackend struct {
protos map[iptables.Protocol]*iptables.IPTables
privChainName string
adminChainName string
- ifName string
}
// iptablesBackend implements the FirewallBackend interface
@@ -185,7 +187,9 @@ func (ib *iptablesBackend) Add(conf *FirewallNetConf) error {
func (ib *iptablesBackend) Del(conf *FirewallNetConf) error {
for proto, ipt := range ib.protos {
- ib.delRules(conf, ipt, proto)
+ if err := ib.delRules(conf, ipt, proto); err != nil {
+ logrus.Errorf("failed to delete iptables backend rule %s", conf.IptablesAdminChainName)
+ }
}
return nil
}
diff --git a/pkg/hooks/0.1.0/hook.go b/pkg/hooks/0.1.0/hook.go
index ba68b0f10..88a387647 100644
--- a/pkg/hooks/0.1.0/hook.go
+++ b/pkg/hooks/0.1.0/hook.go
@@ -6,7 +6,7 @@ import (
"errors"
"strings"
- hooks "github.com/containers/libpod/pkg/hooks"
+ "github.com/containers/libpod/pkg/hooks"
current "github.com/containers/libpod/pkg/hooks/1.0.0"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
diff --git a/pkg/hooks/1.0.0/when_test.go b/pkg/hooks/1.0.0/when_test.go
index 7187b297b..a749063ff 100644
--- a/pkg/hooks/1.0.0/when_test.go
+++ b/pkg/hooks/1.0.0/when_test.go
@@ -30,7 +30,7 @@ func TestAlways(t *testing.T) {
for _, always := range []bool{true, false} {
for _, or := range []bool{true, false} {
for _, process := range []*rspec.Process{processStruct, nil} {
- t.Run(fmt.Sprintf("always %t, or %t, has process %t", always, or, (process != nil)), func(t *testing.T) {
+ t.Run(fmt.Sprintf("always %t, or %t, has process %t", always, or, process != nil), func(t *testing.T) {
config.Process = process
when := When{Always: &always, Or: or}
match, err := when.Match(config, map[string]string{}, false)
diff --git a/pkg/hooks/docs/oci-hooks.5.md b/pkg/hooks/docs/oci-hooks.5.md
index c876dd2f8..fc0442283 100644
--- a/pkg/hooks/docs/oci-hooks.5.md
+++ b/pkg/hooks/docs/oci-hooks.5.md
@@ -90,7 +90,7 @@ $ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
"path": "/usr/libexec/oci/hooks.d/oci-systemd-hook"
}
"when": {
- "args": [".*/init$" , ".*/systemd$"],
+ "commands": [".*/init$" , ".*/systemd$"],
},
"stages": ["prestart", "poststop"]
}
diff --git a/pkg/hooks/exec/exec.go b/pkg/hooks/exec/exec.go
index 0dd091561..4038e3d94 100644
--- a/pkg/hooks/exec/exec.go
+++ b/pkg/hooks/exec/exec.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"fmt"
+ "github.com/sirupsen/logrus"
"io"
osexec "os/exec"
"time"
@@ -54,7 +55,9 @@ func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer,
case err = <-exit:
return err, err
case <-ctx.Done():
- cmd.Process.Kill()
+ if err := cmd.Process.Kill(); err != nil {
+ logrus.Errorf("failed to kill pid %v", cmd.Process)
+ }
timer := time.NewTimer(postKillTimeout)
defer timer.Stop()
select {
diff --git a/pkg/hooks/exec/runtimeconfigfilter.go b/pkg/hooks/exec/runtimeconfigfilter.go
index c6971f680..10b8fedc2 100644
--- a/pkg/hooks/exec/runtimeconfigfilter.go
+++ b/pkg/hooks/exec/runtimeconfigfilter.go
@@ -27,7 +27,11 @@ var spewConfig = spew.ConfigState{
// reads back a possibly-altered form from their standard output).
func RuntimeConfigFilter(ctx context.Context, hooks []spec.Hook, config *spec.Spec, postKillTimeout time.Duration) (hookErr, err error) {
data, err := json.Marshal(config)
+ if err != nil {
+ return nil, err
+ }
for i, hook := range hooks {
+ hook := hook
var stdout bytes.Buffer
hookErr, err = Run(ctx, &hook, data, &stdout, nil, postKillTimeout)
if err != nil {
@@ -43,11 +47,11 @@ func RuntimeConfigFilter(ctx context.Context, hooks []spec.Hook, config *spec.Sp
}
if !reflect.DeepEqual(config, &newConfig) {
- old := spewConfig.Sdump(config)
- new := spewConfig.Sdump(&newConfig)
+ oldConfig := spewConfig.Sdump(config)
+ newConfig := spewConfig.Sdump(&newConfig)
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
- A: difflib.SplitLines(old),
- B: difflib.SplitLines(new),
+ A: difflib.SplitLines(oldConfig),
+ B: difflib.SplitLines(newConfig),
FromFile: "Old",
FromDate: "",
ToFile: "New",
diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go
index 5ed028b95..b962ffa5c 100644
--- a/pkg/hooks/hooks.go
+++ b/pkg/hooks/hooks.go
@@ -4,7 +4,6 @@ package hooks
import (
"context"
"fmt"
- "path/filepath"
"sort"
"strings"
"sync"
@@ -138,26 +137,3 @@ func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBi
return extensionStageHooks, nil
}
-
-// remove remove a hook by name.
-func (m *Manager) remove(hook string) (ok bool) {
- m.lock.Lock()
- defer m.lock.Unlock()
- _, ok = m.hooks[hook]
- if ok {
- delete(m.hooks, hook)
- }
- return ok
-}
-
-// add adds a hook by path
-func (m *Manager) add(path string) (err error) {
- m.lock.Lock()
- defer m.lock.Unlock()
- hook, err := Read(path, m.extensionStages)
- if err != nil {
- return err
- }
- m.hooks[filepath.Base(path)] = hook
- return nil
-}
diff --git a/pkg/hooks/monitor.go b/pkg/hooks/monitor.go
index febe3483f..c50b321f2 100644
--- a/pkg/hooks/monitor.go
+++ b/pkg/hooks/monitor.go
@@ -2,9 +2,8 @@ package hooks
import (
"context"
- "os"
- "path/filepath"
+ current "github.com/containers/libpod/pkg/hooks/1.0.0"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
@@ -49,47 +48,11 @@ func (m *Manager) Monitor(ctx context.Context, sync chan<- error) {
for {
select {
case event := <-watcher.Events:
- filename := filepath.Base(event.Name)
- if len(m.directories) <= 1 {
- if event.Op&fsnotify.Remove == fsnotify.Remove {
- ok := m.remove(filename)
- if ok {
- logrus.Debugf("removed hook %s", event.Name)
- }
- } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
- err = m.add(event.Name)
- if err == nil {
- logrus.Debugf("added hook %s", event.Name)
- } else if err != ErrNoJSONSuffix {
- logrus.Errorf("failed to add hook %s: %v", event.Name, err)
- }
- }
- } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Remove == fsnotify.Remove {
- err = nil
- found := false
- for i := len(m.directories) - 1; i >= 0; i-- {
- path := filepath.Join(m.directories[i], filename)
- err = m.add(path)
- if err == nil {
- found = true
- logrus.Debugf("(re)added hook %s (triggered activity on %s)", path, event.Name)
- break
- } else if err == ErrNoJSONSuffix {
- found = true
- break // this is not going to change for fallback directories
- } else if os.IsNotExist(err) {
- continue // move on to the next fallback directory
- } else {
- found = true
- logrus.Errorf("failed to (re)add hook %s (triggered by activity on %s): %v", path, event.Name, err)
- break
- }
- }
- if (found || event.Op&fsnotify.Remove == fsnotify.Remove) && err != nil {
- ok := m.remove(filename)
- if ok {
- logrus.Debugf("removed hook %s (triggered by activity on %s)", filename, event.Name)
- }
+ m.hooks = make(map[string]*current.Hook)
+ for _, dir := range m.directories {
+ err = ReadDir(dir, m.extensionStages, m.hooks)
+ if err != nil {
+ logrus.Errorf("failed loading hooks for %s: %v", event.Name, err)
}
}
case <-ctx.Done():
diff --git a/pkg/hooks/monitor_test.go b/pkg/hooks/monitor_test.go
index 31d7f9e39..dc67eaf83 100644
--- a/pkg/hooks/monitor_test.go
+++ b/pkg/hooks/monitor_test.go
@@ -226,7 +226,28 @@ func TestMonitorTwoDirGood(t *testing.T) {
assert.Equal(t, primaryInjected, config.Hooks) // masked by primary
})
- t.Run("bad-primary-addition", func(t *testing.T) {
+ primaryPath2 := filepath.Join(primaryDir, "0a.json") //0a because it will be before a.json alphabetically
+
+ t.Run("bad-primary-new-addition", func(t *testing.T) {
+ err = ioutil.WriteFile(primaryPath2, []byte("{\"version\": \"-1\"}"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ fmt.Println("expected: ", config.Hooks)
+ expected := primaryInjected // 0a.json is bad, a.json is still good
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ fmt.Println("actual: ", config.Hooks)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, expected, config.Hooks)
+ })
+
+ t.Run("bad-primary-same-addition", func(t *testing.T) {
err = ioutil.WriteFile(primaryPath, []byte("{\"version\": \"-1\"}"), 0644)
if err != nil {
t.Fatal(err)
@@ -235,7 +256,7 @@ func TestMonitorTwoDirGood(t *testing.T) {
time.Sleep(100 * time.Millisecond) // wait for monitor to notice
config := &rspec.Spec{}
- expected := config.Hooks
+ expected := fallbackInjected
_, err = manager.Hooks(config, map[string]string{}, false)
if err != nil {
t.Fatal(err)
diff --git a/pkg/hooks/read.go b/pkg/hooks/read.go
index d3995a0be..560ff1899 100644
--- a/pkg/hooks/read.go
+++ b/pkg/hooks/read.go
@@ -67,7 +67,7 @@ func ReadDir(path string, extensionStages []string, hooks map[string]*current.Ho
if err != nil {
return err
}
-
+ res := err
for _, file := range files {
filePath := filepath.Join(path, file.Name())
hook, err := Read(filePath, extensionStages)
@@ -80,12 +80,17 @@ func ReadDir(path string, extensionStages []string, hooks map[string]*current.Ho
continue
}
}
- return err
+ if res == nil {
+ res = err
+ } else {
+ res = errors.Wrapf(res, "%v", err)
+ }
+ continue
}
hooks[file.Name()] = hook
logrus.Debugf("added hook %s", filePath)
}
- return nil
+ return res
}
func init() {
diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go
index 2082bb3a6..ec3d98613 100644
--- a/pkg/inspect/inspect.go
+++ b/pkg/inspect/inspect.go
@@ -3,110 +3,11 @@ package inspect
import (
"time"
- "github.com/containers/image/manifest"
- "github.com/cri-o/ocicni/pkg/ocicni"
- "github.com/docker/go-connections/nat"
+ "github.com/containers/libpod/libpod/driver"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/opencontainers/runtime-spec/specs-go"
)
-// ContainerData holds the podman inspect data for a container
-type ContainerData struct {
- *ContainerInspectData
- HostConfig *HostConfig `json:"HostConfig"`
- Config *CtrConfig `json:"Config"`
-}
-
-// HostConfig represents the host configuration for the container
-type HostConfig struct {
- ContainerIDFile string `json:"ContainerIDFile"`
- LogConfig *LogConfig `json:"LogConfig"` //TODO
- NetworkMode string `json:"NetworkMode"`
- PortBindings nat.PortMap `json:"PortBindings"` //TODO
- AutoRemove bool `json:"AutoRemove"`
- CapAdd []string `json:"CapAdd"`
- CapDrop []string `json:"CapDrop"`
- DNS []string `json:"DNS"`
- DNSOptions []string `json:"DNSOptions"`
- DNSSearch []string `json:"DNSSearch"`
- ExtraHosts []string `json:"ExtraHosts"`
- GroupAdd []uint32 `json:"GroupAdd"`
- IpcMode string `json:"IpcMode"`
- Cgroup string `json:"Cgroup"`
- OomScoreAdj *int `json:"OomScoreAdj"`
- PidMode string `json:"PidMode"`
- Privileged bool `json:"Privileged"`
- PublishAllPorts bool `json:"PublishAllPorts"` //TODO
- ReadOnlyRootfs bool `json:"ReadonlyRootfs"`
- ReadOnlyTmpfs bool `json:"ReadonlyTmpfs"`
- SecurityOpt []string `json:"SecurityOpt"`
- UTSMode string `json:"UTSMode"`
- UsernsMode string `json:"UsernsMode"`
- ShmSize int64 `json:"ShmSize"`
- Runtime string `json:"Runtime"`
- ConsoleSize *specs.Box `json:"ConsoleSize"`
- CPUShares *uint64 `json:"CpuShares"`
- Memory int64 `json:"Memory"`
- NanoCPUs int `json:"NanoCpus"`
- CgroupParent string `json:"CgroupParent"`
- BlkioWeight *uint16 `json:"BlkioWeight"`
- BlkioWeightDevice []specs.LinuxWeightDevice `json:"BlkioWeightDevice"`
- BlkioDeviceReadBps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadBps"`
- BlkioDeviceWriteBps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteBps"`
- BlkioDeviceReadIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadIOps"`
- BlkioDeviceWriteIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteIOps"`
- CPUPeriod *uint64 `json:"CpuPeriod"`
- CPUQuota *int64 `json:"CpuQuota"`
- CPURealtimePeriod *uint64 `json:"CpuRealtimePeriod"`
- CPURealtimeRuntime *int64 `json:"CpuRealtimeRuntime"`
- CPUSetCPUs string `json:"CpuSetCpus"`
- CPUSetMems string `json:"CpuSetMems"`
- Devices []specs.LinuxDevice `json:"Devices"`
- DiskQuota int `json:"DiskQuota"` //check type, TODO
- KernelMemory *int64 `json:"KernelMemory"`
- MemoryReservation *int64 `json:"MemoryReservation"`
- MemorySwap *int64 `json:"MemorySwap"`
- MemorySwappiness *uint64 `json:"MemorySwappiness"`
- OomKillDisable *bool `json:"OomKillDisable"`
- PidsLimit *int64 `json:"PidsLimit"`
- Ulimits []string `json:"Ulimits"`
- CPUCount int `json:"CpuCount"`
- CPUPercent int `json:"CpuPercent"`
- IOMaximumIOps int `json:"IOMaximumIOps"` //check type, TODO
- IOMaximumBandwidth int `json:"IOMaximumBandwidth"` //check type, TODO
- Tmpfs []string `json:"Tmpfs"`
-}
-
-// CtrConfig holds information about the container configuration
-type CtrConfig struct {
- Hostname string `json:"Hostname"`
- DomainName string `json:"Domainname"` //TODO
- User specs.User `json:"User"`
- AttachStdin bool `json:"AttachStdin"` //TODO
- AttachStdout bool `json:"AttachStdout"` //TODO
- AttachStderr bool `json:"AttachStderr"` //TODO
- Tty bool `json:"Tty"`
- OpenStdin bool `json:"OpenStdin"`
- StdinOnce bool `json:"StdinOnce"` //TODO
- Env []string `json:"Env"`
- Cmd []string `json:"Cmd"`
- Image string `json:"Image"`
- Volumes map[string]struct{} `json:"Volumes"`
- WorkingDir string `json:"WorkingDir"`
- Entrypoint string `json:"Entrypoint"`
- Labels map[string]string `json:"Labels"`
- Annotations map[string]string `json:"Annotations"`
- StopSignal uint `json:"StopSignal"`
- Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
-}
-
-// LogConfig holds the log information for a container
-type LogConfig struct {
- Type string `json:"Type"`
- Config map[string]string `json:"Config"` //idk type, TODO
-}
-
// ImageData holds the inspect information of an image
type ImageData struct {
ID string `json:"Id"`
@@ -123,7 +24,7 @@ type ImageData struct {
Os string `json:"Os"`
Size int64 `json:"Size"`
VirtualSize int64 `json:"VirtualSize"`
- GraphDriver *Data `json:"GraphDriver"`
+ GraphDriver *driver.Data `json:"GraphDriver"`
RootFS *RootFS `json:"RootFS"`
Labels map[string]string `json:"Labels"`
Annotations map[string]string `json:"Annotations"`
@@ -138,86 +39,6 @@ type RootFS struct {
Layers []digest.Digest `json:"Layers"`
}
-// Data handles the data for a storage driver
-type Data struct {
- Name string `json:"Name"`
- Data map[string]string `json:"Data"`
-}
-
-// ContainerInspectData handles the data used when inspecting a container
-type ContainerInspectData struct {
- ID string `json:"ID"`
- Created time.Time `json:"Created"`
- Path string `json:"Path"`
- Args []string `json:"Args"`
- State *ContainerInspectState `json:"State"`
- ImageID string `json:"Image"`
- ImageName string `json:"ImageName"`
- Rootfs string `json:"Rootfs"`
- ResolvConfPath string `json:"ResolvConfPath"`
- HostnamePath string `json:"HostnamePath"`
- HostsPath string `json:"HostsPath"`
- StaticDir string `json:"StaticDir"`
- LogPath string `json:"LogPath"`
- ConmonPidFile string `json:"ConmonPidFile"`
- Name string `json:"Name"`
- RestartCount int32 `json:"RestartCount"`
- Driver string `json:"Driver"`
- MountLabel string `json:"MountLabel"`
- ProcessLabel string `json:"ProcessLabel"`
- AppArmorProfile string `json:"AppArmorProfile"`
- EffectiveCaps []string `json:"EffectiveCaps"`
- BoundingCaps []string `json:"BoundingCaps"`
- ExecIDs []string `json:"ExecIDs"`
- GraphDriver *Data `json:"GraphDriver"`
- SizeRw int64 `json:"SizeRw,omitempty"`
- SizeRootFs int64 `json:"SizeRootFs,omitempty"`
- Mounts []specs.Mount `json:"Mounts"`
- Dependencies []string `json:"Dependencies"`
- NetworkSettings *NetworkSettings `json:"NetworkSettings"` //TODO
- ExitCommand []string `json:"ExitCommand"`
- Namespace string `json:"Namespace"`
- IsInfra bool `json:"IsInfra"`
-}
-
-// ContainerInspectState represents the state of a container.
-type ContainerInspectState struct {
- OciVersion string `json:"OciVersion"`
- Status string `json:"Status"`
- Running bool `json:"Running"`
- Paused bool `json:"Paused"`
- Restarting bool `json:"Restarting"` // TODO
- OOMKilled bool `json:"OOMKilled"`
- Dead bool `json:"Dead"`
- Pid int `json:"Pid"`
- ExitCode int32 `json:"ExitCode"`
- Error string `json:"Error"` // TODO
- StartedAt time.Time `json:"StartedAt"`
- FinishedAt time.Time `json:"FinishedAt"`
- Healthcheck HealthCheckResults `json:"Healthcheck,omitempty"`
-}
-
-// NetworkSettings holds information about the newtwork settings of the container
-type NetworkSettings struct {
- Bridge string `json:"Bridge"`
- SandboxID string `json:"SandboxID"`
- HairpinMode bool `json:"HairpinMode"`
- LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"`
- LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"`
- Ports []ocicni.PortMapping `json:"Ports"`
- SandboxKey string `json:"SandboxKey"`
- SecondaryIPAddresses []string `json:"SecondaryIPAddresses"`
- SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses"`
- EndpointID string `json:"EndpointID"`
- Gateway string `json:"Gateway"`
- GlobalIPv6Address string `json:"GlobalIPv6Address"`
- GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"`
- IPAddress string `json:"IPAddress"`
- IPPrefixLen int `json:"IPPrefixLen"`
- IPv6Gateway string `json:"IPv6Gateway"`
- MacAddress string `json:"MacAddress"`
-}
-
// ImageResult is used for podman images for collection and output
type ImageResult struct {
Tag string
@@ -232,25 +53,3 @@ type ImageResult struct {
Labels map[string]string
Dangling bool
}
-
-// HealthCheckResults describes the results/logs from a healthcheck
-type HealthCheckResults struct {
- // Status healthy or unhealthy
- Status string `json:"Status"`
- // FailingStreak is the number of consecutive failed healthchecks
- FailingStreak int `json:"FailingStreak"`
- // Log describes healthcheck attempts and results
- Log []HealthCheckLog `json:"Log"`
-}
-
-// HealthCheckLog describes the results of a single healthcheck
-type HealthCheckLog struct {
- // Start time as string
- Start string `json:"Start"`
- // End time as a string
- End string `json:"End"`
- // Exitcode is 0 or 1
- ExitCode int `json:"ExitCode"`
- // Output is the stdout/stderr from the healthcheck command
- Output string `json:"Output"`
-}
diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go
index 7fb5c7ea8..89e4e5686 100644
--- a/pkg/logs/logs.go
+++ b/pkg/logs/logs.go
@@ -29,6 +29,8 @@ import (
"time"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -134,7 +136,7 @@ func parseCRILog(log []byte, msg *logMessage) error {
}
// Keep this forward compatible.
tags := bytes.Split(log[:idx], tagDelimiter)
- partial := (LogTag(tags[0]) == LogTagPartial)
+ partial := LogTag(tags[0]) == LogTagPartial
// Trim the tailing new line if this is a partial line.
if partial && len(log) > 0 && log[len(log)-1] == '\n' {
log = log[:len(log)-1]
@@ -152,7 +154,7 @@ func ReadLogs(logPath string, ctr *libpod.Container, opts *LogOptions) error {
if err != nil {
return errors.Wrapf(err, "failed to open log file %q", logPath)
}
- defer file.Close()
+ defer errorhandling.CloseQuiet(file)
msg := &logMessage{}
opts.bytes = -1
@@ -160,9 +162,9 @@ func ReadLogs(logPath string, ctr *libpod.Container, opts *LogOptions) error {
reader := bufio.NewReader(file)
if opts.Follow {
- followLog(reader, writer, opts, ctr, msg, logPath)
+ err = followLog(reader, writer, opts, ctr, msg, logPath)
} else {
- dumpLog(reader, writer, opts, msg, logPath)
+ err = dumpLog(reader, writer, opts, msg, logPath)
}
return err
}
@@ -209,7 +211,7 @@ func followLog(reader *bufio.Reader, writer *logWriter, opts *LogOptions, ctr *l
if err != nil {
return err
}
- if state != libpod.ContainerStateRunning && state != libpod.ContainerStatePaused {
+ if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
break
}
continue
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index ec9276344..35298796f 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -4,6 +4,63 @@ import (
"strings"
)
+// CgroupMode represents cgroup mode in the container.
+type CgroupMode string
+
+// IsHost indicates whether the container uses the host's cgroup.
+func (n CgroupMode) IsHost() bool {
+ return n == "host"
+}
+
+// IsNS indicates a cgroup namespace passed in by path (ns:<path>)
+func (n CgroupMode) IsNS() bool {
+ return strings.HasPrefix(string(n), "ns:")
+}
+
+// NS gets the path associated with a ns:<path> cgroup ns
+func (n CgroupMode) NS() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
+// IsContainer indicates whether the container uses a new cgroup namespace.
+func (n CgroupMode) IsContainer() bool {
+ parts := strings.SplitN(string(n), ":", 2)
+ return len(parts) > 1 && parts[0] == "container"
+}
+
+// Container returns the name of the container whose cgroup namespace is going to be used.
+func (n CgroupMode) Container() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
+// IsPrivate indicates whether the container uses the a private cgroup.
+func (n CgroupMode) IsPrivate() bool {
+ return n == "private"
+}
+
+// Valid indicates whether the Cgroup namespace is valid.
+func (n CgroupMode) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host", "private", "ns":
+ case "container":
+ if len(parts) != 2 || parts[1] == "" {
+ return false
+ }
+ default:
+ return false
+ }
+ return true
+}
+
// UsernsMode represents userns mode in the container.
type UsernsMode string
@@ -19,27 +76,50 @@ func (n UsernsMode) IsKeepID() bool {
// IsPrivate indicates whether the container uses the a private userns.
func (n UsernsMode) IsPrivate() bool {
- return !(n.IsHost())
+ return !(n.IsHost() || n.IsContainer())
}
// Valid indicates whether the userns is valid.
func (n UsernsMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
- case "", "host", "keep-id":
+ case "", "host", "keep-id", "ns":
+ case "container":
+ if len(parts) != 2 || parts[1] == "" {
+ return false
+ }
default:
return false
}
return true
}
+// IsNS indicates a userns namespace passed in by path (ns:<path>)
+func (n UsernsMode) IsNS() bool {
+ return strings.HasPrefix(string(n), "ns:")
+}
+
+// NS gets the path associated with a ns:<path> userns ns
+func (n UsernsMode) NS() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
// IsContainer indicates whether container uses a container userns.
func (n UsernsMode) IsContainer() bool {
- return false
+ parts := strings.SplitN(string(n), ":", 2)
+ return len(parts) > 1 && parts[0] == "container"
}
// Container is the id of the container which network this container is connected to.
func (n UsernsMode) Container() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
return ""
}
diff --git a/pkg/netns/netns_linux.go b/pkg/netns/netns_linux.go
index a72a2d098..1d6fb873c 100644
--- a/pkg/netns/netns_linux.go
+++ b/pkg/netns/netns_linux.go
@@ -28,6 +28,7 @@ import (
"sync"
"github.com/containernetworking/plugins/pkg/ns"
+ "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -83,12 +84,16 @@ func NewNS() (ns.NetNS, error) {
if err != nil {
return nil, err
}
- mountPointFd.Close()
+ if err := mountPointFd.Close(); err != nil {
+ return nil, err
+ }
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use
- defer os.RemoveAll(nsPath)
+ defer func() {
+ _ = os.RemoveAll(nsPath)
+ }()
var wg sync.WaitGroup
wg.Add(1)
@@ -107,7 +112,11 @@ func NewNS() (ns.NetNS, error) {
if err != nil {
return
}
- defer origNS.Close()
+ defer func() {
+ if err := origNS.Close(); err != nil {
+ logrus.Errorf("unable to close namespace: %q", err)
+ }
+ }()
// create a new netns on the current thread
err = unix.Unshare(unix.CLONE_NEWNET)
@@ -116,7 +125,11 @@ func NewNS() (ns.NetNS, error) {
}
// Put this thread back to the orig ns, since it might get reused (pre go1.10)
- defer origNS.Set()
+ defer func() {
+ if err := origNS.Set(); err != nil {
+ logrus.Errorf("unable to set namespace: %q", err)
+ }
+ }()
// bind mount the netns from the current thread (from /proc) onto the
// mount point. This causes the namespace to persist, even when there
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 5c4ecd020..de63dcbf1 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -44,17 +44,7 @@ func getRegistries() ([]sysregistriesv2.Registry, error) {
// GetRegistries obtains the list of search registries defined in the global registries file.
func GetRegistries() ([]string, error) {
- var searchRegistries []string
- registries, err := getRegistries()
- if err != nil {
- return nil, err
- }
- for _, reg := range registries {
- if reg.Search {
- searchRegistries = append(searchRegistries, reg.Location)
- }
- }
- return searchRegistries, nil
+ return sysregistriesv2.UnqualifiedSearchRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
}
// GetBlockedRegistries obtains the list of blocked registries defined in the global registries file.
@@ -66,7 +56,7 @@ func GetBlockedRegistries() ([]string, error) {
}
for _, reg := range registries {
if reg.Blocked {
- blockedRegistries = append(blockedRegistries, reg.Location)
+ blockedRegistries = append(blockedRegistries, reg.Prefix)
}
}
return blockedRegistries, nil
@@ -81,7 +71,7 @@ func GetInsecureRegistries() ([]string, error) {
}
for _, reg := range registries {
if reg.Insecure {
- insecureRegistries = append(insecureRegistries, reg.Location)
+ insecureRegistries = append(insecureRegistries, reg.Prefix)
}
}
return insecureRegistries, nil
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 2356882e7..19b76f387 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -34,6 +34,15 @@ int renameat2 (int olddirfd, const char *oldpath, int newdirfd, const char *newp
}
#endif
+#ifndef TEMP_FAILURE_RETRY
+#define TEMP_FAILURE_RETRY(expression) \
+ (__extension__ \
+ ({ long int __result; \
+ do __result = (long int) (expression); \
+ while (__result == -1L && errno == EINTR); \
+ __result; }))
+#endif
+
static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces";
static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone";
@@ -73,7 +82,7 @@ do_pause ()
struct sigaction act;
int const sig[] =
{
- SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, SIGPOLL,
+ SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGPOLL,
SIGPROF, SIGVTALRM, SIGXCPU, SIGXFSZ, 0
};
@@ -113,9 +122,7 @@ get_cmd_line_args (pid_t pid)
return NULL;
for (;;)
{
- do
- ret = read (fd, buffer + used, allocated - used);
- while (ret < 0 && errno == EINTR);
+ ret = TEMP_FAILURE_RETRY (read (fd, buffer + used, allocated - used));
if (ret < 0)
{
free (buffer);
@@ -180,7 +187,7 @@ can_use_shortcut ()
argv = get_cmd_line_args (0);
if (argv == NULL)
- return NULL;
+ return false;
for (argc = 0; argv[argc]; argc++)
{
@@ -237,7 +244,7 @@ static void __attribute__((constructor)) init()
/* Shortcut. If we are able to join the pause pid file, do it now so we don't
need to re-exec. */
xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR");
- if (xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut ())
+ if (geteuid () != 0 && xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut ())
{
int r;
int fd;
@@ -269,13 +276,15 @@ static void __attribute__((constructor)) init()
return;
}
- r = read (fd, buf, sizeof (buf));
+ r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof (buf)));
close (fd);
if (r < 0)
{
free (cwd);
return;
}
+ buf[r] = '\0';
+
pid = strtol (buf, NULL, 10);
if (pid == LONG_MAX)
{
@@ -286,7 +295,7 @@ static void __attribute__((constructor)) init()
uid = geteuid ();
gid = getegid ();
- sprintf (path, "/proc/%d/ns/user", pid);
+ sprintf (path, "/proc/%ld/ns/user", pid);
fd = open (path, O_RDONLY);
if (fd < 0 || setns (fd, 0) < 0)
{
@@ -296,7 +305,7 @@ static void __attribute__((constructor)) init()
close (fd);
/* Errors here cannot be ignored as we already joined a ns. */
- sprintf (path, "/proc/%d/ns/mnt", pid);
+ sprintf (path, "/proc/%ld/ns/mnt", pid);
fd = open (path, O_RDONLY);
if (fd < 0)
{
@@ -307,7 +316,7 @@ static void __attribute__((constructor)) init()
r = setns (fd, 0);
if (r < 0)
{
- fprintf (stderr, "cannot join mount namespace for %d: %s", pid, strerror (errno));
+ fprintf (stderr, "cannot join mount namespace for %ld: %s", pid, strerror (errno));
exit (EXIT_FAILURE);
}
close (fd);
@@ -352,10 +361,7 @@ reexec_in_user_namespace_wait (int pid, int options)
pid_t p;
int status;
- do
- p = waitpid (pid, &status, 0);
- while (p < 0 && errno == EINTR);
-
+ p = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
if (p < 0)
return -1;
@@ -384,12 +390,10 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
close (p[1]);
/* Block until we write the pid file. */
- do
- r = read (p[0], &b, 1);
- while (r < 0 && errno == EINTR);
+ r = TEMP_FAILURE_RETRY (read (p[0], &b, 1));
close (p[0]);
- reexec_in_user_namespace_wait(r, 0);
+ reexec_in_user_namespace_wait (r, 0);
return r == 1 && b == '0' ? 0 : -1;
}
@@ -412,9 +416,16 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
sprintf (pid_str, "%d", pid);
- asprintf (&tmp_file_path, "%s.XXXXXX", pause_pid_file_path);
+ if (asprintf (&tmp_file_path, "%s.XXXXXX", pause_pid_file_path) < 0)
+ {
+ fprintf (stderr, "unable to print to string\n");
+ kill (pid, SIGKILL);
+ _exit (EXIT_FAILURE);
+ }
+
if (tmp_file_path == NULL)
{
+ fprintf (stderr, "temporary file path is NULL\n");
kill (pid, SIGKILL);
_exit (EXIT_FAILURE);
}
@@ -422,15 +433,15 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
fd = mkstemp (tmp_file_path);
if (fd < 0)
{
+ fprintf (stderr, "error creating temporary file: %s\n", strerror (errno));
kill (pid, SIGKILL);
_exit (EXIT_FAILURE);
}
- do
- r = write (fd, pid_str, strlen (pid_str));
- while (r < 0 && errno == EINTR);
+ r = TEMP_FAILURE_RETRY (write (fd, pid_str, strlen (pid_str)));
if (r < 0)
{
+ fprintf (stderr, "cannot write to file descriptor: %s\n", strerror (errno));
kill (pid, SIGKILL);
_exit (EXIT_FAILURE);
}
@@ -445,9 +456,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
_exit (EXIT_FAILURE);
}
- do
- r = write (p[1], "0", 1);
- while (r < 0 && errno == EINTR);
+ r = TEMP_FAILURE_RETRY (write (p[1], "0", 1));
close (p[1]);
_exit (EXIT_SUCCESS);
@@ -471,7 +480,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
close (fd);
setenv ("_PODMAN_PAUSE", "1", 1);
- execlp (argv[0], NULL);
+ execlp (argv[0], argv[0], NULL);
/* If the execve fails, then do the pause here. */
do_pause ();
@@ -489,6 +498,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
char **argv;
int pid;
char *cwd = getcwd (NULL, 0);
+ sigset_t sigset, oldsigset;
if (cwd == NULL)
{
@@ -522,6 +532,27 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
return pid;
}
+ if (sigfillset (&sigset) < 0)
+ {
+ fprintf (stderr, "cannot fill sigset: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ if (sigdelset (&sigset, SIGCHLD) < 0)
+ {
+ fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ if (sigdelset (&sigset, SIGTERM) < 0)
+ {
+ fprintf (stderr, "cannot sigdelset(SIGTERM): %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0)
+ {
+ fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+
setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1);
setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1);
@@ -570,6 +601,11 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
/* We ignore errors here as we didn't create the namespace anyway. */
create_pause_process (pause_pid_file_path, argv);
}
+ if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0)
+ {
+ fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
execvp (argv[0], argv);
@@ -609,9 +645,7 @@ copy_file_to_fd (const char *file_to_read, int outfd)
{
ssize_t r, w, t = 0;
- do
- r = read (fd, buf, sizeof buf);
- while (r < 0 && errno == EINTR);
+ r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof buf));
if (r < 0)
{
close (fd);
@@ -623,9 +657,7 @@ copy_file_to_fd (const char *file_to_read, int outfd)
while (t < r)
{
- do
- w = write (outfd, &buf[t], r - t);
- while (w < 0 && errno == EINTR);
+ w = TEMP_FAILURE_RETRY (write (outfd, &buf[t], r - t));
if (w < 0)
{
close (fd);
@@ -675,7 +707,6 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
pid = syscall_clone (CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, NULL);
if (pid < 0)
{
- FILE *fp;
fprintf (stderr, "cannot clone: %s\n", strerror (errno));
check_proc_sys_userns_file (_max_user_namespaces);
check_proc_sys_userns_file (_unprivileged_user_namespaces);
@@ -710,6 +741,11 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno));
_exit (EXIT_FAILURE);
}
+ if (sigdelset (&sigset, SIGTERM) < 0)
+ {
+ fprintf (stderr, "cannot sigdelset(SIGTERM): %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0)
{
fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
@@ -734,9 +770,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1);
- do
- ret = read (ready, &b, 1) < 0;
- while (ret < 0 && errno == EINTR);
+ ret = TEMP_FAILURE_RETRY (read (ready, &b, 1));
if (ret < 0)
{
fprintf (stderr, "cannot read from sync pipe: %s\n", strerror (errno));
@@ -748,21 +782,21 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
if (syscall_setresgid (0, 0, 0) < 0)
{
fprintf (stderr, "cannot setresgid: %s\n", strerror (errno));
- write (ready, "1", 1);
+ TEMP_FAILURE_RETRY (write (ready, "1", 1));
_exit (EXIT_FAILURE);
}
if (syscall_setresuid (0, 0, 0) < 0)
{
fprintf (stderr, "cannot setresuid: %s\n", strerror (errno));
- write (ready, "1", 1);
+ TEMP_FAILURE_RETRY (write (ready, "1", 1));
_exit (EXIT_FAILURE);
}
if (chdir (cwd) < 0)
{
fprintf (stderr, "cannot chdir: %s\n", strerror (errno));
- write (ready, "1", 1);
+ TEMP_FAILURE_RETRY (write (ready, "1", 1));
_exit (EXIT_FAILURE);
}
free (cwd);
@@ -771,14 +805,12 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
{
if (create_pause_process (pause_pid_file_path, argv) < 0)
{
- write (ready, "2", 1);
+ TEMP_FAILURE_RETRY (write (ready, "2", 1));
_exit (EXIT_FAILURE);
}
}
- do
- ret = write (ready, "0", 1) < 0;
- while (ret < 0 && errno == EINTR);
+ ret = TEMP_FAILURE_RETRY (write (ready, "0", 1));
close (ready);
if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0)
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index d302b1777..6e48988c5 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -1,4 +1,4 @@
-// +build linux
+// +build linux,cgo
package rootless
@@ -9,21 +9,26 @@ import (
"os/exec"
gosignal "os/signal"
"os/user"
+ "path/filepath"
"runtime"
"strconv"
+ "strings"
"sync"
"syscall"
"unsafe"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/storage/pkg/idtools"
"github.com/docker/docker/pkg/signal"
+ "github.com/godbus/dbus"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
/*
-#cgo remoteclient CFLAGS: -DDISABLE_JOIN_SHORTCUT
+#cgo remoteclient CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT
#include <stdlib.h>
+#include <sys/types.h>
extern uid_t rootless_uid();
extern uid_t rootless_gid();
extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd);
@@ -37,8 +42,7 @@ const (
)
func runInUser() error {
- os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done")
- return nil
+ return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done")
}
var (
@@ -53,9 +57,15 @@ func IsRootless() bool {
rootlessGIDInit := int(C.rootless_gid())
if rootlessUIDInit != 0 {
// This happens if we joined the user+mount namespace as part of
- os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done")
- os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit))
- os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit))
+ if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil {
+ logrus.Errorf("failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done")
+ }
+ if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)); err != nil {
+ logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit)
+ }
+ if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)); err != nil {
+ logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit)
+ }
}
isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != ""
})
@@ -101,10 +111,8 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap)
args := []string{path, fmt.Sprintf("%d", pid)}
args = appendTriplet(args, 0, hostID, 1)
- if mappings != nil {
- for _, i := range mappings {
- args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size)
- }
+ for _, i := range mappings {
+ args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size)
}
cmd := exec.Cmd{
Path: path,
@@ -169,6 +177,9 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) {
for {
nextFd, err := getParentUserNs(fd)
if err != nil {
+ if err == syscall.ENOTTY {
+ return os.NewFile(fd, "userns child"), nil
+ }
return nil, errors.Wrapf(err, "cannot get parent user namespace")
}
@@ -178,40 +189,118 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) {
}
if ns == currentNS {
- syscall.Close(int(nextFd))
+ if err := syscall.Close(int(nextFd)); err != nil {
+ return nil, err
+ }
// Drop O_CLOEXEC for the fd.
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0)
if errno != 0 {
- syscall.Close(int(fd))
+ if err := syscall.Close(int(fd)); err != nil {
+ logrus.Errorf("failed to close file descriptor %d", fd)
+ }
return nil, errno
}
return os.NewFile(fd, "userns child"), nil
}
- syscall.Close(int(fd))
+ if err := syscall.Close(int(fd)); err != nil {
+ return nil, err
+ }
fd = nextFd
}
}
-func enableLinger(pausePid string) {
- if pausePid == "" {
- return
+// EnableLinger configures the system to not kill the user processes once the session
+// terminates
+func EnableLinger() (string, error) {
+ uid := fmt.Sprintf("%d", GetRootlessUID())
+
+ conn, err := dbus.SystemBus()
+ if err == nil {
+ defer func() {
+ if err := conn.Close(); err != nil {
+ logrus.Errorf("unable to close dbus connection: %q", err)
+ }
+ }()
+ }
+
+ lingerEnabled := false
+
+ // If we have a D-BUS connection, attempt to read the LINGER property from it.
+ if conn != nil {
+ path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid))
+ ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.Linger")
+ if err == nil && ret.Value().(bool) {
+ lingerEnabled = true
+ }
+ }
+
+ xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
+ lingerFile := ""
+ if xdgRuntimeDir != "" && !lingerEnabled {
+ lingerFile = filepath.Join(xdgRuntimeDir, "libpod/linger")
+ _, err := os.Stat(lingerFile)
+ if err == nil {
+ lingerEnabled = true
+ }
+ }
+
+ if !lingerEnabled {
+ // First attempt with D-BUS, if it fails, then attempt with "loginctl enable-linger"
+ if conn != nil {
+ o := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1")
+ ret := o.Call("org.freedesktop.login1.Manager.SetUserLinger", 0, uint32(GetRootlessUID()), true, true)
+ if ret.Err == nil {
+ lingerEnabled = true
+ }
+ }
+ if !lingerEnabled {
+ err := exec.Command("loginctl", "enable-linger", uid).Run()
+ if err == nil {
+ lingerEnabled = true
+ } else {
+ logrus.Debugf("cannot run `loginctl enable-linger` for the current user: %v", err)
+ }
+ }
+ if lingerEnabled && lingerFile != "" {
+ f, err := os.Create(lingerFile)
+ if err == nil {
+ if err := f.Close(); err != nil {
+ logrus.Errorf("failed to close %s", f.Name())
+ }
+ } else {
+ logrus.Debugf("could not create linger file: %v", err)
+ }
+ }
+ }
+
+ if !lingerEnabled {
+ return "", nil
+ }
+
+ // If we have a D-BUS connection, attempt to read the RUNTIME PATH from it.
+ if conn != nil {
+ path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid))
+ ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.RuntimePath")
+ if err == nil {
+ return strings.Trim(ret.String(), "\"\n"), nil
+ }
}
- // If we are trying to write a pause pid file, make sure we can leave processes
- // running longer than the user session.
- err := exec.Command("loginctl", "enable-linger", fmt.Sprintf("%d", GetRootlessUID())).Run()
+
+ // If XDG_RUNTIME_DIR is not set and the D-BUS call didn't work, try to get the runtime path with "loginctl"
+ output, err := exec.Command("loginctl", "-pRuntimePath", "show-user", uid).Output()
if err != nil {
- logrus.Warnf("cannot run `loginctl enable-linger` for the current user: %v", err)
+ logrus.Debugf("could not get RuntimePath using loginctl: %v", err)
+ return "", nil
}
+ return strings.Trim(strings.Replace(string(output), "RuntimePath=", "", -1), "\"\n"), nil
}
// joinUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
// the conmon process.
func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
- enableLinger(pausePid)
-
if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
return false, -1, nil
}
@@ -223,13 +312,21 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
if err != nil {
return false, -1, err
}
- defer userNS.Close()
+ defer func() {
+ if err := userNS.Close(); err != nil {
+ logrus.Errorf("unable to close namespace: %q", err)
+ }
+ }()
mountNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", pid))
if err != nil {
return false, -1, err
}
- defer userNS.Close()
+ defer func() {
+ if err := mountNS.Close(); err != nil {
+ logrus.Errorf("unable to close namespace: %q", err)
+ }
+ }()
fd, err := getUserNSFirstChild(userNS.Fd())
if err != nil {
@@ -275,9 +372,13 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
}
r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child")
- defer r.Close()
- defer w.Close()
- defer w.Write([]byte("0"))
+ defer errorhandling.CloseQuiet(r)
+ defer errorhandling.CloseQuiet(w)
+ defer func() {
+ if _, err := w.Write([]byte("0")); err != nil {
+ logrus.Errorf("failed to write byte 0: %q", err)
+ }
+ }()
pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD)
pid := int(pidC)
@@ -288,9 +389,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
var uids, gids []idtools.IDMap
username := os.Getenv("USER")
if username == "" {
- user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid()))
+ userID, err := user.LookupId(fmt.Sprintf("%d", os.Getuid()))
if err == nil {
- username = user.Username
+ username = userID.Username
}
}
mappings, err := idtools.NewIDMappings(username, username)
@@ -339,7 +440,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
return false, -1, errors.Wrapf(err, "write to sync pipe")
}
- b := make([]byte, 1, 1)
+ b := make([]byte, 1)
_, err = w.Read(b)
if err != nil {
return false, -1, errors.Wrapf(err, "read from sync pipe")
@@ -385,7 +486,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
continue
}
- syscall.Kill(int(pidC), s.(syscall.Signal))
+ if err := syscall.Kill(int(pidC), s.(syscall.Signal)); err != nil {
+ logrus.Errorf("failed to kill %d", int(pidC))
+ }
}
}()
@@ -402,7 +505,6 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
// If podman was re-executed the caller needs to propagate the error code returned by the child
// process.
func BecomeRootInUserNS(pausePid string) (bool, int, error) {
- enableLinger(pausePid)
return becomeRootInUserNS(pausePid, "", nil)
}
@@ -447,17 +549,19 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st
r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file")
- defer w.Close()
- defer r.Close()
+ defer errorhandling.CloseQuiet(w)
+ defer errorhandling.CloseQuiet(r)
if _, _, err := becomeRootInUserNS("", path, w); err != nil {
lastErr = err
continue
}
- w.Close()
+ if err := w.Close(); err != nil {
+ return false, 0, err
+ }
defer func() {
- r.Close()
+ errorhandling.CloseQuiet(r)
C.reexec_in_user_namespace_wait(-1, 0)
}()
diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go
index c063adee5..a8485c083 100644
--- a/pkg/rootless/rootless_unsupported.go
+++ b/pkg/rootless/rootless_unsupported.go
@@ -1,14 +1,21 @@
-// +build !linux
+// +build !linux !cgo
package rootless
import (
+ "os"
+
"github.com/pkg/errors"
)
-// IsRootless returns false on all non-linux platforms
+// IsRootless returns whether the user is rootless
func IsRootless() bool {
- return false
+ uid := os.Geteuid()
+ // os.Geteuid() on Windows returns -1
+ if uid == -1 {
+ return false
+ }
+ return uid != 0
}
// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed
@@ -29,6 +36,12 @@ func GetRootlessGID() int {
return -1
}
+// EnableLinger configures the system to not kill the user processes once the session
+// terminates
+func EnableLinger() (string, error) {
+ return "", nil
+}
+
// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths.
// This is useful when there are already running containers and we
// don't have a pause process yet. We can use the paths to the conmon
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index eb2acf984..60d31d78e 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -4,12 +4,11 @@ package createconfig
import (
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strings"
- "github.com/docker/docker/profiles/seccomp"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -120,37 +119,50 @@ func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
return err
}
g.ClearLinuxDevices()
- for _, d := range hostDevices {
- g.AddDevice(Device(d))
- }
-
- // Add resources device - need to clear the existing one first.
- g.Spec().Linux.Resources.Devices = nil
- g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
- return nil
-}
-
-func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
- var seccompConfig *spec.LinuxSeccomp
- var err error
- if config.SeccompProfilePath != "" {
- seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
- if err != nil {
- return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
+ if rootless.IsRootless() {
+ mounts := make(map[string]interface{})
+ for _, m := range g.Mounts() {
+ mounts[m.Destination] = true
}
- seccompConfig, err = seccomp.LoadProfile(string(seccompProfile), configSpec)
- if err != nil {
- return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ newMounts := []spec.Mount{}
+ for _, d := range hostDevices {
+ devMnt := spec.Mount{
+ Destination: d.Path,
+ Type: TypeBind,
+ Source: d.Path,
+ Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"},
+ }
+ if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") {
+ continue
+ }
+ if _, found := mounts[d.Path]; found {
+ continue
+ }
+ st, err := os.Stat(d.Path)
+ if err != nil {
+ if err == unix.EPERM {
+ continue
+ }
+ return errors.Wrapf(err, "stat %s", d.Path)
+ }
+ // Skip devices that the user has not access to.
+ if st.Mode()&0007 == 0 {
+ continue
+ }
+ newMounts = append(newMounts, devMnt)
}
+ g.Config.Mounts = append(newMounts, g.Config.Mounts...)
} else {
- seccompConfig, err = seccomp.GetDefaultProfile(configSpec)
- if err != nil {
- return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ for _, d := range hostDevices {
+ g.AddDevice(Device(d))
}
}
- return seccompConfig, nil
+ // Add resources device - need to clear the existing one first.
+ g.Config.Linux.Resources.Devices = nil
+ g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
+ return nil
}
func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) {
diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go
new file mode 100644
index 000000000..e6e92a7cc
--- /dev/null
+++ b/pkg/spec/config_linux_cgo.go
@@ -0,0 +1,34 @@
+// +build linux,cgo
+
+package createconfig
+
+import (
+ "io/ioutil"
+
+ "github.com/docker/docker/profiles/seccomp"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+ var seccompConfig *spec.LinuxSeccomp
+ var err error
+
+ if config.SeccompProfilePath != "" {
+ seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ seccompConfig, err = seccomp.LoadProfile(string(seccompProfile), configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ } else {
+ seccompConfig, err = seccomp.GetDefaultProfile(configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ }
+
+ return seccompConfig, nil
+}
diff --git a/pkg/spec/config_linux_nocgo.go b/pkg/spec/config_linux_nocgo.go
new file mode 100644
index 000000000..10329ff3b
--- /dev/null
+++ b/pkg/spec/config_linux_nocgo.go
@@ -0,0 +1,11 @@
+// +build linux,!cgo
+
+package createconfig
+
+import (
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+ return nil, nil
+}
diff --git a/pkg/spec/containerconfig.go b/pkg/spec/containerconfig.go
index b2f8a268f..ae6420117 100644
--- a/pkg/spec/containerconfig.go
+++ b/pkg/spec/containerconfig.go
@@ -2,6 +2,7 @@ package createconfig
import (
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -11,9 +12,9 @@ import (
// container with libpod from a completed CreateConfig struct.
func (config *CreateConfig) MakeContainerConfig(runtime *libpod.Runtime, pod *libpod.Pod) (*spec.Spec, []libpod.CtrCreateOption, error) {
if config.Pod != "" && pod == nil {
- return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was specified but no pod passed")
+ return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was specified but no pod passed")
} else if config.Pod == "" && pod != nil {
- return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was given but no pod is specified")
+ return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was given but no pod is specified")
}
// Parse volumes flag into OCI spec mounts and libpod Named Volumes.
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index e4501aaac..214a3c5ed 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
@@ -62,6 +63,7 @@ type CreateConfig struct {
CapDrop []string // cap-drop
CidFile string
ConmonPidFile string
+ Cgroupns string
CgroupParent string // cgroup-parent
Command []string
Detach bool // detach
@@ -100,6 +102,7 @@ type CreateConfig struct {
NetworkAlias []string //network-alias
PidMode namespaces.PidMode //pid
Pod string //pod
+ CgroupMode namespaces.CgroupMode //cgroup
PortBindings nat.PortMap
Privileged bool //privileged
Publish []string //publish
@@ -162,6 +165,10 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err
if config.StorageConfig.GraphDriverName != "" {
command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...)
}
+ for _, opt := range config.StorageConfig.GraphDriverOptions {
+ command = append(command, []string{"--storage-opt", opt}...)
+ }
+
if c.Syslog {
command = append(command, "--syslog", "true")
}
@@ -259,10 +266,48 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
options = append(options, libpod.WithNetNSFrom(connectedCtr))
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
- postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
+ hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0
+ postConfigureNetNS := c.NetMode.IsSlirp4netns() || (hasUserns && !c.UsernsMode.IsHost())
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
}
+ if c.CgroupMode.IsNS() {
+ ns := c.CgroupMode.NS()
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined network namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ } else if c.CgroupMode.IsContainer() {
+ connectedCtr, err := runtime.LookupContainer(c.CgroupMode.Container())
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", c.CgroupMode.Container())
+ }
+ options = append(options, libpod.WithCgroupNSFrom(connectedCtr))
+ }
+
+ if c.UsernsMode.IsNS() {
+ ns := c.UsernsMode.NS()
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined user namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, libpod.WithIDMappings(*c.IDMappings))
+ } else if c.UsernsMode.IsContainer() {
+ connectedCtr, err := runtime.LookupContainer(c.UsernsMode.Container())
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", c.UsernsMode.Container())
+ }
+ options = append(options, libpod.WithUserNSFrom(connectedCtr))
+ } else {
+ options = append(options, libpod.WithIDMappings(*c.IDMappings))
+ }
+
if c.PidMode.IsContainer() {
connectedCtr, err := runtime.LookupContainer(c.PidMode.Container())
if err != nil {
@@ -320,14 +365,16 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, libpod.WithLogPath(logPath))
}
- options = append(options, libpod.WithLogDriver(c.LogDriver))
+ if c.LogDriver != "" {
+ options = append(options, libpod.WithLogDriver(c.LogDriver))
+ }
if c.IPAddress != "" {
ip := net.ParseIP(c.IPAddress)
if ip == nil {
- return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress)
+ return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress)
} else if ip.To4() == nil {
- return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress)
+ return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress)
}
options = append(options, libpod.WithStaticIP(ip))
}
@@ -353,7 +400,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
options = append(options, libpod.WithShmSize(c.Resources.ShmSize))
options = append(options, libpod.WithGroups(c.GroupAdd))
- options = append(options, libpod.WithIDMappings(*c.IDMappings))
if c.Rootfs != "" {
options = append(options, libpod.WithRootFS(c.Rootfs))
}
@@ -365,7 +411,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
if c.RestartPolicy != "" {
if c.RestartPolicy == "unless-stopped" {
- return nil, errors.Wrapf(libpod.ErrInvalidArg, "the unless-stopped restart policy is not supported")
+ return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported")
}
split := strings.Split(c.RestartPolicy, ":")
@@ -375,7 +421,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
return nil, errors.Wrapf(err, "%s is not a valid number of retries for restart policy", split[1])
}
if numTries < 0 {
- return nil, errors.Wrapf(libpod.ErrInvalidArg, "restart policy requires a positive number of retries")
+ return nil, errors.Wrapf(define.ErrInvalidArg, "restart policy requires a positive number of retries")
}
options = append(options, libpod.WithRestartRetries(uint(numTries)))
}
diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go
index d688b8d1b..c2572a033 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -126,13 +126,9 @@ func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint
if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
}
- if rate < 0 {
- return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
- }
-
return &throttleDevice{
path: split[0],
- rate: uint64(rate),
+ rate: rate,
}, nil
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 0d953ff6f..15c8c77fa 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -6,8 +6,8 @@ import (
"strings"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/util"
pmount "github.com/containers/storage/pkg/mount"
"github.com/docker/docker/oci/caps"
"github.com/docker/go-units"
@@ -46,7 +46,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
canMountSys := true
isRootless := rootless.IsRootless()
- inUserNS := isRootless || (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost()
+ hasUserns := config.UsernsMode.IsContainer() || config.UsernsMode.IsNS() || len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0
+ inUserNS := isRootless || (hasUserns && !config.UsernsMode.IsHost())
if inUserNS && config.NetMode.IsHost() {
canMountSys = false
@@ -80,23 +81,41 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
g.AddLinuxMaskedPaths("/sys/kernel")
}
}
+ gid5Available := true
if isRootless {
nGids, err := getAvailableGids()
if err != nil {
return nil, err
}
- if nGids < 5 {
- // If we have no GID mappings, the gid=5 default option would fail, so drop it.
- g.RemoveMount("/dev/pts")
- devPts := spec.Mount{
- Destination: "/dev/pts",
- Type: "devpts",
- Source: "devpts",
- Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
+ gid5Available = nGids >= 5
+ }
+ // When using a different user namespace, check that the GID 5 is mapped inside
+ // the container.
+ if gid5Available && len(config.IDMappings.GIDMap) > 0 {
+ mappingFound := false
+ for _, r := range config.IDMappings.GIDMap {
+ if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size {
+ mappingFound = true
+ break
}
- g.AddMount(devPts)
}
+ if !mappingFound {
+ gid5Available = false
+ }
+
}
+ if !gid5Available {
+ // If we have no GID mappings, the gid=5 default option would fail, so drop it.
+ g.RemoveMount("/dev/pts")
+ devPts := spec.Mount{
+ Destination: "/dev/pts",
+ Type: "devpts",
+ Source: "devpts",
+ Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
+ }
+ g.AddMount(devPts)
+ }
+
if inUserNS && config.IpcMode.IsHost() {
g.RemoveMount("/dev/mqueue")
devMqueue := spec.Mount{
@@ -246,10 +265,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
// already adding them all.
- if !rootless.IsRootless() {
- if err := config.AddPrivilegedDevices(&g); err != nil {
- return nil, err
- }
+ if err := config.AddPrivilegedDevices(&g); err != nil {
+ return nil, err
}
} else {
for _, devicePath := range config.Devices {
@@ -307,6 +324,10 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
if err := addIpcNS(config, &g); err != nil {
return nil, err
}
+
+ if err := addCgroupNS(config, &g); err != nil {
+ return nil, err
+ }
configSpec := g.Config
// HANDLE CAPABILITIES
@@ -350,7 +371,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
if rootless.IsRootless() {
- cgroup2, err := util.IsCgroup2UnifiedMode()
+ cgroup2, err := cgroups.IsCgroup2UnifiedMode()
if err != nil {
return nil, err
}
@@ -400,6 +421,62 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
}
+ // Add annotations
+ if configSpec.Annotations == nil {
+ configSpec.Annotations = make(map[string]string)
+ }
+
+ if config.CidFile != "" {
+ configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
+ }
+
+ if config.Rm {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ }
+
+ if len(config.VolumesFrom) > 0 {
+ configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
+ }
+
+ if config.Privileged {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ }
+
+ if config.PublishAll {
+ configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ }
+
+ if config.Init {
+ configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
+ }
+
+ for _, opt := range config.SecurityOpts {
+ // Split on both : and =
+ splitOpt := strings.Split(opt, "=")
+ if len(splitOpt) == 1 {
+ splitOpt = strings.Split(opt, ":")
+ }
+ if len(splitOpt) < 2 {
+ continue
+ }
+ switch splitOpt[0] {
+ case "label":
+ configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
+ case "seccomp":
+ configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
+ case "apparmor":
+ configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
+ }
+ }
+
return configSpec, nil
}
@@ -475,15 +552,18 @@ func addPidNS(config *CreateConfig, g *generate.Generator) error {
func addUserNS(config *CreateConfig, g *generate.Generator) error {
if IsNS(string(config.UsernsMode)) {
- g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode)))
-
+ if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode))); err != nil {
+ return err
+ }
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
}
if (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() {
- g.AddOrReplaceLinuxNamespace(spec.UserNamespace, "")
+ if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, ""); err != nil {
+ return err
+ }
}
return nil
}
@@ -544,6 +624,23 @@ func addIpcNS(config *CreateConfig, g *generate.Generator) error {
return nil
}
+func addCgroupNS(config *CreateConfig, g *generate.Generator) error {
+ cgroupMode := config.CgroupMode
+ if cgroupMode.IsNS() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), NS(string(cgroupMode)))
+ }
+ if cgroupMode.IsHost() {
+ return g.RemoveLinuxNamespace(spec.CgroupNamespace)
+ }
+ if cgroupMode.IsPrivate() {
+ return g.AddOrReplaceLinuxNamespace(spec.CgroupNamespace, "")
+ }
+ if cgroupMode.IsContainer() {
+ logrus.Debug("Using container cgroup mode")
+ }
+ return nil
+}
+
func addRlimits(config *CreateConfig, g *generate.Generator) error {
var (
kernelMax uint64 = 1048576
@@ -553,6 +650,14 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error {
)
for _, u := range config.Resources.Ulimit {
+ if u == "host" {
+ if len(config.Resources.Ulimit) != 1 {
+ return errors.New("ulimit can use host only once")
+ }
+ g.Config.Process.Rlimits = nil
+ break
+ }
+
ul, err := units.ParseUlimit(u)
if err != nil {
return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
index dcc149b55..88f1f6dc1 100644
--- a/pkg/spec/storage.go
+++ b/pkg/spec/storage.go
@@ -211,6 +211,13 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
}
mount.Options = opts
}
+ if mount.Type == TypeBind {
+ absSrc, err := filepath.Abs(mount.Source)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ }
+ mount.Source = absSrc
+ }
finalMounts = append(finalMounts, mount)
}
finalVolumes := make([]*libpod.ContainerNamedVolume, 0, len(baseVolumes))
@@ -251,9 +258,11 @@ func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]
return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1])
}
options = strings.Split(splitVol[1], ",")
- if err := ValidateVolumeOpts(options); err != nil {
+ opts, err := ValidateVolumeOpts(options)
+ if err != nil {
return nil, nil, err
}
+ options = opts
}
ctr, err := runtime.LookupContainer(splitVol[0])
if err != nil {
@@ -384,7 +393,7 @@ func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libp
}
finalNamedVolumes[volume.Dest] = volume
default:
- return nil, nil, errors.Errorf("invalid fylesystem type %q", kv[1])
+ return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
}
}
@@ -403,6 +412,8 @@ func getBindMount(args []string) (spec.Mount, error) {
for _, val := range args {
kv := strings.Split(val, "=")
switch kv[0] {
+ case "bind-nonrecursive":
+ newMount.Options = append(newMount.Options, "bind")
case "ro", "nosuid", "nodev", "noexec":
// TODO: detect duplication of these options.
// (Is this necessary?)
@@ -445,9 +456,11 @@ func getBindMount(args []string) (spec.Mount, error) {
newMount.Source = newMount.Destination
}
- if err := ValidateVolumeOpts(newMount.Options); err != nil {
+ opts, err := ValidateVolumeOpts(newMount.Options)
+ if err != nil {
return newMount, err
}
+ newMount.Options = opts
return newMount, nil
}
@@ -573,30 +586,45 @@ func ValidateVolumeCtrDir(ctrDir string) error {
}
// ValidateVolumeOpts validates a volume's options
-func ValidateVolumeOpts(options []string) error {
- var foundRootPropagation, foundRWRO, foundLabelChange int
+func ValidateVolumeOpts(options []string) ([]string, error) {
+ var foundRootPropagation, foundRWRO, foundLabelChange, bindType int
+ finalOpts := make([]string, 0, len(options))
for _, opt := range options {
switch opt {
case "rw", "ro":
foundRWRO++
if foundRWRO > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", "))
+ return nil, errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", "))
}
case "z", "Z":
foundLabelChange++
if foundLabelChange > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", "))
+ return nil, errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", "))
}
case "private", "rprivate", "shared", "rshared", "slave", "rslave":
foundRootPropagation++
if foundRootPropagation > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", "))
- }
+ return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", "))
+ }
+ case "bind", "rbind":
+ bindType++
+ if bindType > 1 {
+ return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", "))
+ }
+ case "cached", "delegated":
+ // The discarded ops are OS X specific volume options
+ // introduced in a recent Docker version.
+ // They have no meaning on Linux, so here we silently
+ // drop them. This matches Docker's behavior (the options
+ // are intended to be always safe to use, even not on OS
+ // X).
+ continue
default:
- return errors.Errorf("invalid option type %q", opt)
+ return nil, errors.Errorf("invalid mount option %q", opt)
}
+ finalOpts = append(finalOpts, opt)
}
- return nil
+ return finalOpts, nil
}
// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
@@ -626,9 +654,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string
}
if len(splitVol) > 2 {
options = strings.Split(splitVol[2], ",")
- if err := ValidateVolumeOpts(options); err != nil {
+ opts, err := ValidateVolumeOpts(options)
+ if err != nil {
return nil, nil, err
}
+ options = opts
}
if err := ValidateVolumeHostDir(src); err != nil {
@@ -797,7 +827,7 @@ func initFSMounts(inputMounts []spec.Mount) []spec.Mount {
if m.Type == TypeBind {
m.Options = util.ProcessOptions(m.Options)
}
- if m.Type == TypeTmpfs {
+ if m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev" {
m.Options = append(m.Options, "tmpcopyup")
}
mounts = append(mounts, m)
diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go
index f4047b63c..9e675c655 100644
--- a/pkg/sysinfo/sysinfo_linux.go
+++ b/pkg/sysinfo/sysinfo_linux.go
@@ -15,7 +15,7 @@ import (
func findCgroupMountpoints() (map[string]string, error) {
cgMounts, err := cgroups.GetCgroupMounts(false)
if err != nil {
- return nil, fmt.Errorf("Failed to parse cgroup information: %v", err)
+ return nil, fmt.Errorf("failed to parse cgroup information: %v", err)
}
mps := make(map[string]string)
for _, m := range cgMounts {
diff --git a/pkg/sysinfo/sysinfo_solaris.go b/pkg/sysinfo/sysinfo_solaris.go
index c858d57e0..7463cdd8f 100644
--- a/pkg/sysinfo/sysinfo_solaris.go
+++ b/pkg/sysinfo/sysinfo_solaris.go
@@ -11,6 +11,7 @@ import (
/*
#cgo LDFLAGS: -llgrp
+#cgo CFLAGS: -Wall -Werror
#include <unistd.h>
#include <stdlib.h>
#include <sys/lgrp_user.h>
diff --git a/pkg/sysinfo/sysinfo_test.go b/pkg/sysinfo/sysinfo_test.go
index b61fbcf54..895828f26 100644
--- a/pkg/sysinfo/sysinfo_test.go
+++ b/pkg/sysinfo/sysinfo_test.go
@@ -20,7 +20,7 @@ func TestIsCpusetListAvailable(t *testing.T) {
for _, c := range cases {
r, err := isCpusetListAvailable(c.provided, c.available)
if (c.err && err == nil) && r != c.res {
- t.Fatalf("Expected pair: %v, %v for %s, %s. Got %v, %v instead", c.res, c.err, c.provided, c.available, (c.err && err == nil), r)
+ t.Fatalf("Expected pair: %v, %v for %s, %s. Got %v, %v instead", c.res, c.err, c.provided, c.available, c.err && err == nil, r)
}
}
}
diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go
index 3d1c31b5d..06c5ebde5 100644
--- a/pkg/systemdgen/systemdgen.go
+++ b/pkg/systemdgen/systemdgen.go
@@ -2,17 +2,18 @@ package systemdgen
import (
"fmt"
- "path/filepath"
+ "os"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
var template = `[Unit]
Description=%s Podman Container
[Service]
Restart=%s
-ExecStart=/usr/bin/podman start %s
-ExecStop=/usr/bin/podman stop -t %d %s
+ExecStart=%s start %s
+ExecStop=%s stop -t %d %s
KillMode=none
Type=forking
PIDFile=%s
@@ -33,11 +34,26 @@ func ValidateRestartPolicy(restart string) error {
// 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) {
+func CreateSystemdUnitAsString(name, cid, restart, pidFile string, stopTimeout int) (string, error) {
+ podmanExe := getPodmanExecutable()
+ return createSystemdUnitAsString(podmanExe, name, cid, restart, pidFile, stopTimeout)
+}
+
+func createSystemdUnitAsString(exe, name, cid, restart, pidFile 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)
+
+ unit := fmt.Sprintf(template, name, restart, exe, name, exe, stopTimeout, name, pidFile)
return unit, nil
}
+
+func getPodmanExecutable() string {
+ podmanExe, err := os.Executable()
+ if err != nil {
+ podmanExe = "/usr/bin/podman"
+ logrus.Warnf("Could not obtain podman executable location, using default %s", podmanExe)
+ }
+
+ return podmanExe
+}
diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemdgen/systemdgen_test.go
index f2f49e750..e413b24ce 100644
--- a/pkg/systemdgen/systemdgen_test.go
+++ b/pkg/systemdgen/systemdgen_test.go
@@ -41,7 +41,7 @@ ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4
ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
KillMode=none
Type=forking
-PIDFile=/var/lib/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.pid
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
@@ -53,15 +53,16 @@ ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
KillMode=none
Type=forking
-PIDFile=/var/lib/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.pid
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
type args struct {
+ exe string
name string
cid string
restart string
- pidPath string
+ pidFile string
stopTimeout int
}
tests := []struct {
@@ -73,10 +74,11 @@ WantedBy=multi-user.target`
{"good with id",
args{
+ "/usr/bin/podman",
"639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
"639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
"always",
- "/var/lib/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/",
+ "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
10,
},
goodID,
@@ -84,10 +86,11 @@ WantedBy=multi-user.target`
},
{"good with name",
args{
+ "/usr/bin/podman",
"foobar",
"639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
"always",
- "/var/lib/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/",
+ "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
10,
},
goodName,
@@ -95,10 +98,11 @@ WantedBy=multi-user.target`
},
{"bad restart policy",
args{
+ "/usr/bin/podman",
"639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
"639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
"never",
- "/var/lib/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/",
+ "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
10,
},
"",
@@ -107,7 +111,7 @@ WantedBy=multi-user.target`
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := CreateSystemdUnitAsString(tt.args.name, tt.args.cid, tt.args.restart, tt.args.pidPath, tt.args.stopTimeout)
+ got, err := createSystemdUnitAsString(tt.args.exe, tt.args.name, tt.args.cid, tt.args.restart, tt.args.pidFile, tt.args.stopTimeout)
if (err != nil) != tt.wantErr {
t.Errorf("CreateSystemdUnitAsString() error = %v, wantErr %v", err, tt.wantErr)
return
diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go
index cae76dee8..d028ddf8f 100644
--- a/pkg/tracing/tracing.go
+++ b/pkg/tracing/tracing.go
@@ -4,9 +4,9 @@ import (
"fmt"
"io"
- opentracing "github.com/opentracing/opentracing-go"
- jaeger "github.com/uber/jaeger-client-go"
- config "github.com/uber/jaeger-client-go/config"
+ "github.com/opentracing/opentracing-go"
+ "github.com/uber/jaeger-client-go"
+ "github.com/uber/jaeger-client-go/config"
)
// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go
index 9a75474ae..3bfe4bda1 100644
--- a/pkg/trust/trust.go
+++ b/pkg/trust/trust.go
@@ -14,7 +14,7 @@ import (
"github.com/containers/image/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- yaml "gopkg.in/yaml.v2"
+ "gopkg.in/yaml.v2"
)
// PolicyContent struct for policy.json file
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index 59459807c..40c99384d 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -17,20 +17,25 @@ var (
// sensible and follow convention.
func ProcessOptions(options []string) []string {
var (
- foundrw, foundro bool
- rootProp string
+ foundbind, foundrw, foundro bool
+ rootProp string
)
- options = append(options, "rbind")
+
for _, opt := range options {
switch opt {
- case "rw":
- foundrw = true
+ case "bind", "rbind":
+ foundbind = true
case "ro":
foundro = true
+ case "rw":
+ foundrw = true
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
rootProp = opt
}
}
+ if !foundbind {
+ options = append(options, "rbind")
+ }
if !foundrw && !foundro {
options = append(options, "rw")
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index a074f276c..fba34a337 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -12,12 +12,14 @@ import (
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
)
@@ -99,7 +101,10 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) {
var st struct{}
exposedPorts[pair[1]] = st
case "ENV":
- env = append(env, pair[1])
+ if len(pair) < 3 {
+ return v1.ImageConfig{}, errors.Errorf("no value given for environment variable %q", pair[1])
+ }
+ env = append(env, strings.Join(pair[1:], "="))
case "ENTRYPOINT":
entrypoint = append(entrypoint, pair[1])
case "CMD":
@@ -269,16 +274,20 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig {
// WriteStorageConfigFile writes the configuration to a file
func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error {
- os.MkdirAll(filepath.Dir(storageConf), 0755)
- file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+ if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil {
+ return err
+ }
+ storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return errors.Wrapf(err, "cannot open %s", storageConf)
}
tomlConfiguration := getTomlStorage(storageOpts)
- defer file.Close()
- enc := toml.NewEncoder(file)
+ defer errorhandling.CloseQuiet(storageFile)
+ enc := toml.NewEncoder(storageFile)
if err := enc.Encode(tomlConfiguration); err != nil {
- os.Remove(storageConf)
+ if err := os.Remove(storageConf); err != nil {
+ logrus.Errorf("unable to remove file %s", storageConf)
+ }
return err
}
return nil
@@ -334,3 +343,14 @@ func GetGlobalOpts(c *cliconfig.RunlabelValues) string {
})
return strings.Join(optsCommand, " ")
}
+
+// OpenExclusiveFile opens a file for writing and ensure it doesn't already exist
+func OpenExclusiveFile(path string) (*os.File, error) {
+ baseDir := filepath.Dir(path)
+ if baseDir != "" {
+ if _, err := os.Stat(baseDir); err != nil {
+ return nil, err
+ }
+ }
+ return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+}
diff --git a/pkg/util/utils_darwin.go b/pkg/util/utils_darwin.go
new file mode 100644
index 000000000..33a46a5d4
--- /dev/null
+++ b/pkg/util/utils_darwin.go
@@ -0,0 +1,11 @@
+//+build darwin
+
+package util
+
+import (
+ "github.com/pkg/errors"
+)
+
+func GetContainerPidInformationDescriptors() ([]string, error) {
+ return []string{}, errors.New("this function is not supported on darwin")
+}
diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go
new file mode 100644
index 000000000..288137ca5
--- /dev/null
+++ b/pkg/util/utils_linux.go
@@ -0,0 +1,54 @@
+package util
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "syscall"
+
+ "github.com/containers/psgo"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// GetContainerPidInformationDescriptors returns a string slice of all supported
+// format descriptors of GetContainerPidInformation.
+func GetContainerPidInformationDescriptors() ([]string, error) {
+ return psgo.ListDescriptors(), nil
+}
+
+// FindDeviceNodes parses /dev/ into a set of major:minor -> path, where
+// [major:minor] is the device's major and minor numbers formatted as, for
+// example, 2:0 and path is the path to the device node.
+// Symlinks to nodes are ignored.
+func FindDeviceNodes() (map[string]string, error) {
+ nodes := make(map[string]string)
+ err := filepath.Walk("/dev", func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ logrus.Warnf("Error descending into path %s: %v", path, err)
+ return filepath.SkipDir
+ }
+
+ // If we aren't a device node, do nothing.
+ if info.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
+ return nil
+ }
+
+ // We are a device node. Get major/minor.
+ sysstat, ok := info.Sys().(*syscall.Stat_t)
+ if !ok {
+ return errors.Errorf("Could not convert stat output for use")
+ }
+ major := sysstat.Rdev / 256
+ minor := sysstat.Rdev % 256
+
+ nodes[fmt.Sprintf("%d:%d", major, minor)] = path
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return nodes, nil
+}
diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go
index 3d9140a23..af55689a6 100644
--- a/pkg/util/utils_supported.go
+++ b/pkg/util/utils_supported.go
@@ -7,37 +7,15 @@ package util
import (
"fmt"
- "github.com/containers/libpod/pkg/rootless"
- "github.com/pkg/errors"
"os"
"path/filepath"
- "sync"
"syscall"
-)
-const (
- _cgroup2SuperMagic = 0x63677270
-)
-
-var (
- isUnifiedOnce sync.Once
- isUnified bool
- isUnifiedErr error
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
-// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 unified mode.
-func IsCgroup2UnifiedMode() (bool, error) {
- isUnifiedOnce.Do(func() {
- var st syscall.Statfs_t
- if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
- isUnified, isUnifiedErr = false, err
- } else {
- isUnified, isUnifiedErr = st.Type == _cgroup2SuperMagic, nil
- }
- })
- return isUnified, isUnifiedErr
-}
-
// GetRootlessRuntimeDir returns the runtime directory when running as non root
func GetRootlessRuntimeDir() (string, error) {
var rootlessRuntimeDirError error
@@ -47,7 +25,9 @@ func GetRootlessRuntimeDir() (string, error) {
uid := fmt.Sprintf("%d", rootless.GetRootlessUID())
if runtimeDir == "" {
tmpDir := filepath.Join("/run", "user", uid)
- os.MkdirAll(tmpDir, 0700)
+ if err := os.MkdirAll(tmpDir, 0700); err != nil {
+ logrus.Errorf("unable to make temp dir %s", tmpDir)
+ }
st, err := os.Stat(tmpDir)
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
runtimeDir = tmpDir
@@ -55,7 +35,9 @@ func GetRootlessRuntimeDir() (string, error) {
}
if runtimeDir == "" {
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid))
- os.MkdirAll(tmpDir, 0700)
+ if err := os.MkdirAll(tmpDir, 0700); err != nil {
+ logrus.Errorf("unable to make temp dir %s", tmpDir)
+ }
st, err := os.Stat(tmpDir)
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
runtimeDir = tmpDir
diff --git a/pkg/util/utils_unsupported.go b/pkg/util/utils_unsupported.go
new file mode 100644
index 000000000..62805d7c8
--- /dev/null
+++ b/pkg/util/utils_unsupported.go
@@ -0,0 +1,12 @@
+// +build darwin windows
+
+package util
+
+import (
+ "github.com/pkg/errors"
+)
+
+// FindDeviceNodes is not implemented anywhere except Linux.
+func FindDeviceNodes() (map[string]string, error) {
+ return nil, errors.Errorf("not supported on non-Linux OSes")
+}
diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go
index 3faa6f10c..635558bf7 100644
--- a/pkg/util/utils_windows.go
+++ b/pkg/util/utils_windows.go
@@ -6,18 +6,24 @@ 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")
-}
-
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 unified mode.
func IsCgroup2UnifiedMode() (bool, error) {
return false, errors.New("this function is not implemented for windows")
}
+// GetContainerPidInformationDescriptors returns a string slice of all supported
+// format descriptors of GetContainerPidInformation.
+func GetContainerPidInformationDescriptors() ([]string, error) {
+ return nil, errors.New("this function is not implemented for windows")
+}
+
// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for
// the pause process
func GetRootlessPauseProcessPidPath() (string, error) {
return "", errors.New("this function is not implemented for windows")
}
+
+// 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/attach.go b/pkg/varlinkapi/attach.go
index 2234899a5..1f8d48eb9 100644
--- a/pkg/varlinkapi/attach.go
+++ b/pkg/varlinkapi/attach.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/remotecommand"
@@ -57,30 +58,34 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if !start && state != libpod.ContainerStateRunning {
+ if !start && state != define.ContainerStateRunning {
return call.ReplyErrorOccurred("container must be running to attach")
}
- call.Reply(nil)
+
+ // ACK the client upgrade request
+ call.ReplyAttach()
+
reader, writer, _, pw, streams := setupStreams(call)
go func() {
- if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil {
+ if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil {
errChan <- err
}
}()
- if state == libpod.ContainerStateRunning {
+ if state == define.ContainerStateRunning {
finalErr = attach(ctr, streams, detachKeys, resize, errChan)
} else {
finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan)
}
- if finalErr != libpod.ErrDetach && finalErr != nil {
+ if finalErr != define.ErrDetach && finalErr != nil {
logrus.Error(finalErr)
}
- quitWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.Quit)
- _, err = quitWriter.Write([]byte("HANG-UP"))
- // TODO error handling is not quite right here yet
+
+ if err = virtwriter.HangUp(writer, 0); err != nil {
+ logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error())
+ }
return call.Writer.Flush()
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 8611a1a7d..cd5f305c9 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -16,10 +16,14 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/adapter/shortcuts"
- cc "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "k8s.io/client-go/tools/remotecommand"
)
// ListContainers ...
@@ -64,32 +68,34 @@ func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error {
for _, ctr := range psContainerOutputs {
container := iopodman.PsContainer{
- Id: ctr.ID,
- Image: ctr.Image,
- Command: ctr.Command,
- Created: ctr.Created,
- Ports: ctr.Ports,
- Names: ctr.Names,
- IsInfra: ctr.IsInfra,
- Status: ctr.Status,
- State: ctr.State.String(),
- PidNum: int64(ctr.Pid),
- RootFsSize: ctr.Size.RootFsSize,
- RwSize: ctr.Size.RwSize,
- Pod: ctr.Pod,
- CreatedAt: ctr.CreatedAt.Format(time.RFC3339Nano),
- ExitedAt: ctr.ExitedAt.Format(time.RFC3339Nano),
- StartedAt: ctr.StartedAt.Format(time.RFC3339Nano),
- Labels: ctr.Labels,
- NsPid: ctr.PID,
- Cgroup: ctr.Cgroup,
- Ipc: ctr.Cgroup,
- Mnt: ctr.MNT,
- Net: ctr.NET,
- PidNs: ctr.PIDNS,
- User: ctr.User,
- Uts: ctr.UTS,
- Mounts: ctr.Mounts,
+ Id: ctr.ID,
+ Image: ctr.Image,
+ Command: ctr.Command,
+ Created: ctr.Created,
+ Ports: ctr.Ports,
+ Names: ctr.Names,
+ IsInfra: ctr.IsInfra,
+ Status: ctr.Status,
+ State: ctr.State.String(),
+ PidNum: int64(ctr.Pid),
+ Pod: ctr.Pod,
+ CreatedAt: ctr.CreatedAt.Format(time.RFC3339Nano),
+ ExitedAt: ctr.ExitedAt.Format(time.RFC3339Nano),
+ StartedAt: ctr.StartedAt.Format(time.RFC3339Nano),
+ Labels: ctr.Labels,
+ NsPid: ctr.PID,
+ Cgroup: ctr.Cgroup,
+ Ipc: ctr.Cgroup,
+ Mnt: ctr.MNT,
+ Net: ctr.NET,
+ PidNs: ctr.PIDNS,
+ User: ctr.User,
+ Uts: ctr.UTS,
+ Mounts: ctr.Mounts,
+ }
+ if ctr.Size != nil {
+ container.RootFsSize = ctr.Size.RootFsSize
+ container.RwSize = ctr.Size.RwSize
}
containers = append(containers, container)
}
@@ -119,7 +125,7 @@ func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, lates
ctrs, err := shortcuts.GetContainersByContext(all, latest, input, i.Runtime)
if err != nil {
- if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
return call.ReplyContainerNotFound("", err.Error())
}
return call.ReplyErrorOccurred(err.Error())
@@ -138,7 +144,7 @@ func (i *LibpodAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses []
containers []iopodman.Container
)
for _, status := range statuses {
- lpstatus, err := libpod.StringToContainerStatus(status)
+ lpstatus, err := define.StringToContainerStatus(status)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -168,16 +174,7 @@ func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) err
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
- inspectInfo, err := ctr.Inspect(true)
- if err != nil {
- return call.ReplyErrorOccurred(err.Error())
- }
- artifact, err := getArtifact(ctr)
- if err != nil {
- return call.ReplyErrorOccurred(err.Error())
- }
-
- data, err := shared.GetCtrInspectInfo(ctr.Config(), inspectInfo, artifact)
+ data, err := ctr.Inspect(true)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -198,7 +195,7 @@ func (i *LibpodAPI) ListContainerProcesses(call iopodman.VarlinkCall, name strin
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if containerState != libpod.ContainerStateRunning {
+ if containerState != define.ContainerStateRunning {
return call.ReplyErrorOccurred(fmt.Sprintf("container %s is not running", name))
}
var psArgs []string
@@ -229,7 +226,7 @@ func (i *LibpodAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) err
return call.ReplyErrorOccurred(err.Error())
}
if _, err := os.Stat(logPath); err != nil {
- if containerState == libpod.ContainerStateConfigured {
+ if containerState == define.ContainerStateConfigured {
return call.ReplyGetContainerLogs(logs)
}
}
@@ -259,7 +256,7 @@ func (i *LibpodAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) err
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if state != libpod.ContainerStateRunning && state != libpod.ContainerStatePaused {
+ if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
return call.ReplyErrorOccurred(fmt.Sprintf("%s is no longer running", ctr.ID()))
}
@@ -326,7 +323,7 @@ func (i *LibpodAPI) GetContainerStats(call iopodman.VarlinkCall, name string) er
}
containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
if err != nil {
- if errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyNoContainerRunning()
}
return call.ReplyErrorOccurred(err.Error())
@@ -359,7 +356,7 @@ func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused {
+ if state == define.ContainerStateRunning || state == define.ContainerStatePaused {
return call.ReplyErrorOccurred("container is already running or paused")
}
recursive := false
@@ -379,7 +376,7 @@ func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.Init(getContext()); err != nil {
- if errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyInvalidState(ctr.ID(), err.Error())
}
return call.ReplyErrorOccurred(err.Error())
@@ -394,10 +391,10 @@ func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeou
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.StopWithTimeout(uint(timeout)); err != nil {
- if errors.Cause(err) == libpod.ErrCtrStopped {
+ if errors.Cause(err) == define.ErrCtrStopped {
return call.ReplyErrCtrStopped(ctr.ID())
}
- if errors.Cause(err) == libpod.ErrCtrStateInvalid {
+ if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyInvalidState(ctr.ID(), err.Error())
}
return call.ReplyErrorOccurred(err.Error())
@@ -420,7 +417,7 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim
// ContainerExists looks in local storage for the existence of a container
func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error {
_, err := i.Runtime.LookupContainer(name)
- if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
return call.ReplyContainerExists(1)
}
if err != nil {
@@ -510,7 +507,7 @@ func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error {
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if state != libpod.ContainerStateRunning {
+ if state != define.ContainerStateRunning {
if err := i.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -534,7 +531,7 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err
// If the container hasn't been run, we need to run init
// so the conmon sockets get created.
- if status == libpod.ContainerStateConfigured || status == libpod.ContainerStateStopped {
+ if status == define.ContainerStateConfigured || status == define.ContainerStateStopped {
if err := ctr.Init(getContext()); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -585,18 +582,6 @@ func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, kee
return call.ReplyContainerRestore(ctr.ID())
}
-func getArtifact(ctr *libpod.Container) (*cc.CreateConfig, error) {
- var createArtifact cc.CreateConfig
- artifact, err := ctr.GetArtifact("create-config")
- if err != nil {
- return nil, err
- }
- if err := json.Unmarshal(artifact, &createArtifact); err != nil {
- return nil, err
- }
- return &createArtifact, nil
-}
-
// ContainerConfig returns just the container.config struct
func (i *LibpodAPI) ContainerConfig(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
@@ -719,7 +704,7 @@ func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string,
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- options := libpod.LogOptions{
+ options := logs.LogOptions{
Follow: follow,
Since: sinceTime,
Tail: uint64(tail),
@@ -730,7 +715,7 @@ func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string,
if len(names) > 1 {
options.Multi = true
}
- logChannel := make(chan *libpod.LogLine, int(tail)*len(names)+1)
+ logChannel := make(chan *logs.LogLine, int(tail)*len(names)+1)
containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
@@ -752,7 +737,7 @@ func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string,
return call.ReplyGetContainersLogs(iopodman.LogLine{})
}
-func newPodmanLogLine(line *libpod.LogLine) iopodman.LogLine {
+func newPodmanLogLine(line *logs.LogLine) iopodman.LogLine {
return iopodman.LogLine{
Device: line.Device,
ParseLogType: line.ParseLogType,
@@ -774,3 +759,93 @@ func (i *LibpodAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors
}
return call.ReplyTop(topInfo)
}
+
+// ExecContainer is the varlink endpoint to execute a command in a container
+func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecOpts) error {
+ if !call.WantsUpgrade() {
+ return call.ReplyErrorOccurred("client must use upgraded connection to exec")
+ }
+
+ ctr, err := i.Runtime.LookupContainer(opts.Name)
+ if err != nil {
+ return call.ReplyContainerNotFound(opts.Name, err.Error())
+ }
+
+ state, err := ctr.State()
+ if err != nil {
+ return call.ReplyErrorOccurred(
+ fmt.Sprintf("exec failed to obtain container %s state: %s", ctr.ID(), err.Error()))
+ }
+
+ if state != define.ContainerStateRunning {
+ return call.ReplyErrorOccurred(
+ fmt.Sprintf("exec requires a running container, %s is %s", ctr.ID(), state.String()))
+ }
+
+ // ACK the client upgrade request
+ call.ReplyExecContainer()
+
+ envs := []string{}
+ if opts.Env != nil {
+ envs = *opts.Env
+ }
+
+ var user string
+ if opts.User != nil {
+ user = *opts.User
+ }
+
+ var workDir string
+ if opts.Workdir != nil {
+ workDir = *opts.Workdir
+ }
+
+ var detachKeys string
+ if opts.DetachKeys != nil {
+ detachKeys = *opts.DetachKeys
+ }
+
+ resizeChan := make(chan remotecommand.TerminalSize)
+
+ reader, writer, _, pipeWriter, streams := setupStreams(call)
+
+ type ExitCodeError struct {
+ ExitCode uint32
+ Error error
+ }
+ ecErrChan := make(chan ExitCodeError, 1)
+
+ go func() {
+ if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan, nil); err != nil {
+ ecErrChan <- ExitCodeError{
+ define.ExecErrorCodeGeneric,
+ err,
+ }
+ }
+ }()
+
+ go func() {
+ ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, detachKeys)
+ if err != nil {
+ logrus.Errorf(err.Error())
+ }
+ ecErrChan <- ExitCodeError{
+ uint32(ec),
+ err,
+ }
+ }()
+
+ ecErr := <-ecErrChan
+
+ exitCode := define.TranslateExecErrorToExitCode(int(ecErr.ExitCode), ecErr.Error)
+
+ if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil {
+ logrus.Errorf("ExecContainer failed to HANG-UP on %s: %s", ctr.ID(), err.Error())
+ }
+
+ if err := call.Writer.Flush(); err != nil {
+ logrus.Errorf("Exec Container err: %s", err.Error())
+ }
+
+ return ecErr.Error
+}
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index fa1a0a109..739a3e582 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -23,7 +23,9 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/channelwriter"
"github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
@@ -67,6 +69,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
Containers: int64(len(containers)),
Labels: labels,
IsParent: isParent,
+ ReadOnly: image.IsReadOnly(),
}
imageList = append(imageList, i)
}
@@ -107,6 +110,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error {
Containers: int64(len(containers)),
Labels: labels,
TopLayer: newImage.TopLayer(),
+ ReadOnly: newImage.IsReadOnly(),
}
return call.ReplyGetImage(il)
}
@@ -371,7 +375,6 @@ func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compr
done = true
default:
if !call.WantsMore() {
- time.Sleep(1 * time.Second)
break
}
br := iopodman.MoreResponse{
@@ -495,6 +498,19 @@ func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error {
// Commit ...
func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error {
+ var (
+ newImage *image.Image
+ log []string
+ mimeType string
+ )
+ output := channelwriter.NewChannelWriter()
+ channelClose := func() {
+ if err := output.Close(); err != nil {
+ logrus.Errorf("failed to close channel writer: %q", err)
+ }
+ }
+ defer channelClose()
+
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
@@ -504,7 +520,6 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch
return call.ReplyErrorOccurred(err.Error())
}
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
- var mimeType string
switch manifestType {
case "oci", "": //nolint
mimeType = buildah.OCIv1ImageManifest
@@ -515,7 +530,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch
}
coptions := buildah.CommitOptions{
SignaturePolicyPath: rtc.SignaturePolicyPath,
- ReportWriter: nil,
+ ReportWriter: output,
SystemContext: sc,
PreferredManifestType: mimeType,
}
@@ -527,11 +542,36 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch
Author: author,
}
- newImage, err := ctr.Commit(getContext(), imageName, options)
+ if call.WantsMore() {
+ call.Continues = true
+ }
+
+ c := make(chan error)
+ defer close(c)
+
+ go func() {
+ newImage, err = ctr.Commit(getContext(), imageName, options)
+ if err != nil {
+ c <- err
+ }
+ c <- nil
+ }()
+
+ // reply is the func being sent to the output forwarder. in this case it is replying
+ // with a more response struct
+ reply := func(br iopodman.MoreResponse) error {
+ return call.ReplyCommit(br)
+ }
+ log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- return call.ReplyCommit(newImage.ID())
+ call.Continues = false
+ br := iopodman.MoreResponse{
+ Logs: log,
+ Id: newImage.ID(),
+ }
+ return call.ReplyCommit(br)
}
// ImportImage imports an image from a tarball to the image store
@@ -583,6 +623,7 @@ func (i *LibpodAPI) ExportImage(call iopodman.VarlinkCall, name, destination str
func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error {
var (
imageID string
+ err error
)
dockerRegistryOptions := image.DockerRegistryOptions{}
so := image.SigningOptions{}
@@ -590,8 +631,16 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error {
if call.WantsMore() {
call.Continues = true
}
- output := bytes.NewBuffer([]byte{})
+ output := channelwriter.NewChannelWriter()
+ channelClose := func() {
+ if err := output.Close(); err != nil {
+ logrus.Errorf("failed to close channel writer: %q", err)
+ }
+ }
+ defer channelClose()
c := make(chan error)
+ defer close(c)
+
go func() {
if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") {
srcRef, err := alltransports.ParseImageName(name)
@@ -613,44 +662,17 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error {
}
}
c <- nil
- close(c)
}()
var log []string
- done := false
- for {
- line, err := output.ReadString('\n')
- if err == nil {
- log = append(log, line)
- continue
- } else if err == io.EOF {
- select {
- case err := <-c:
- if err != nil {
- logrus.Errorf("reading of output during pull failed for %s", name)
- return call.ReplyErrorOccurred(err.Error())
- }
- done = true
- default:
- if !call.WantsMore() {
- time.Sleep(1 * time.Second)
- break
- }
- br := iopodman.MoreResponse{
- Logs: log,
- }
- call.ReplyPullImage(br)
- log = []string{}
- }
- } else {
- return call.ReplyErrorOccurred(err.Error())
- }
- if done {
- break
- }
+ reply := func(br iopodman.MoreResponse) error {
+ return call.ReplyPullImage(br)
+ }
+ log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
}
call.Continues = false
-
br := iopodman.MoreResponse{
Logs: log,
Id: imageID,
@@ -709,7 +731,7 @@ func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error {
func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name)
if err != nil {
- if errors.Cause(err) == libpod.ErrNoSuchImage {
+ if errors.Cause(err) == define.ErrNoSuchImage {
return call.ReplyImageNotFound(options.Name, err.Error())
}
return call.ReplyErrorOccurred(err.Error())
@@ -764,7 +786,6 @@ func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageS
done = true
default:
if !call.WantsMore() {
- time.Sleep(1 * time.Second)
break
}
br := iopodman.MoreResponse{
@@ -844,7 +865,6 @@ func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string,
done = true
default:
if !call.WantsMore() {
- time.Sleep(1 * time.Second)
break
}
br := iopodman.MoreResponse{
diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go
index 59bfec75b..9b5b3a5b1 100644
--- a/pkg/varlinkapi/system.go
+++ b/pkg/varlinkapi/system.go
@@ -3,17 +3,17 @@
package varlinkapi
import (
+ "github.com/containers/libpod/libpod/define"
goruntime "runtime"
"strings"
"time"
"github.com/containers/libpod/cmd/podman/varlink"
- "github.com/containers/libpod/libpod"
)
// GetVersion ...
func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error {
- versionInfo, err := libpod.GetVersion()
+ versionInfo, err := define.GetVersion()
if err != nil {
return err
}
@@ -30,7 +30,7 @@ func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error {
// GetInfo returns details about the podman host and its stores
func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error {
- versionInfo, err := libpod.GetVersion()
+ versionInfo, err := define.GetVersion()
if err != nil {
return err
}
diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go
index 96f76bcdc..31d26c3aa 100644
--- a/pkg/varlinkapi/transfers.go
+++ b/pkg/varlinkapi/transfers.go
@@ -26,7 +26,8 @@ func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int
defer outputFile.Close()
if err = call.ReplySendFile(outputFile.Name()); err != nil {
- return call.ReplyErrorOccurred(err.Error())
+ // If an error occurs while sending the reply, return the error
+ return err
}
writer := bufio.NewWriter(outputFile)
@@ -60,9 +61,10 @@ func (i *LibpodAPI) ReceiveFile(call iopodman.VarlinkCall, filepath string, dele
}
// Send the file length down to client
- // Varlink connection upraded
+ // Varlink connection upgraded
if err = call.ReplyReceiveFile(fileInfo.Size()); err != nil {
- return call.ReplyErrorOccurred(err.Error())
+ // If an error occurs while sending the reply, return the error
+ return err
}
reader := bufio.NewReader(fs)
diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go
index 8716c963a..d3a41f7ab 100644
--- a/pkg/varlinkapi/util.go
+++ b/pkg/varlinkapi/util.go
@@ -12,6 +12,8 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/channelwriter"
"github.com/containers/storage/pkg/archive"
)
@@ -73,7 +75,7 @@ func makeListContainer(containerID string, batchInfo shared.BatchContainerStruct
Names: batchInfo.ConConfig.Name,
Labels: batchInfo.ConConfig.Labels,
Mounts: mounts,
- Containerrunning: batchInfo.ConState == libpod.ContainerStateRunning,
+ Containerrunning: batchInfo.ConState == define.ContainerStateRunning,
Namespaces: namespace,
}
if batchInfo.Size != nil {
@@ -189,9 +191,48 @@ func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions {
Latest: derefBool(inOpts.Latest),
NoTrunc: derefBool(inOpts.NoTrunc),
Pod: derefBool(inOpts.Pod),
- Size: true,
+ Size: derefBool(inOpts.Size),
Sort: derefString(inOpts.Sort),
Namespace: true,
Sync: derefBool(inOpts.Sync),
}
}
+
+// forwardOutput is a helper method for varlink endpoints that employ both more and without
+// more. it is capable of sending updates as the output writer gets them or append them
+// all to a log. the chan error is the error from the libpod call so we can honor
+// and error event in that case.
+func forwardOutput(log []string, c chan error, wantsMore bool, output *channelwriter.Writer, reply func(br iopodman.MoreResponse) error) ([]string, error) {
+ done := false
+ for {
+ select {
+ // We need to check if the libpod func being called has returned an
+ // error yet
+ case err := <-c:
+ if err != nil {
+ return nil, err
+ }
+ done = true
+ // if no error is found, we pull what we can from the log writer and
+ // append it to log string slice
+ case line := <-output.ByteChannel:
+ log = append(log, string(line))
+ // If the end point is being used in more mode, send what we have
+ if wantsMore {
+ br := iopodman.MoreResponse{
+ Logs: log,
+ }
+ if err := reply(br); err != nil {
+ return nil, err
+ }
+ // "reset" the log to empty because we are sending what we
+ // get as we get it
+ log = []string{}
+ }
+ }
+ if done {
+ break
+ }
+ }
+ return log, nil
+}
diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go
index 3adaf6e17..27ecd1f52 100644
--- a/pkg/varlinkapi/virtwriter/virtwriter.go
+++ b/pkg/varlinkapi/virtwriter/virtwriter.go
@@ -4,10 +4,9 @@ import (
"bufio"
"encoding/binary"
"encoding/json"
- "errors"
"io"
- "os"
+ "github.com/pkg/errors"
"k8s.io/client-go/tools/remotecommand"
)
@@ -90,66 +89,100 @@ func (v VirtWriteCloser) Write(input []byte) (int, error) {
}
// Reader decodes the content that comes over the wire and directs it to the proper destination.
-func Reader(r *bufio.Reader, output, errput *os.File, input *io.PipeWriter, resize chan remotecommand.TerminalSize) error {
- var saveb []byte
- var eom int
+func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remotecommand.TerminalSize, execEcChan chan int) error {
+ var messageSize int64
+ headerBytes := make([]byte, 8)
+
+ if r == nil {
+ return errors.Errorf("Reader must not be nil")
+ }
+
for {
- readb := make([]byte, 32*1024)
- n, err := r.Read(readb)
- // TODO, later may be worth checking in len of the read is 0
+ n, err := io.ReadFull(r, headerBytes)
if err != nil {
- return err
+ return errors.Wrapf(err, "Virtual Read failed, %d", n)
}
- b := append(saveb, readb[0:n]...)
- // no sense in reading less than the header len
- for len(b) > 7 {
- eom = int(binary.BigEndian.Uint32(b[4:8])) + 8
- // The message and header are togther
- if len(b) >= eom {
- out := append([]byte{}, b[8:eom]...)
-
- switch IntToSocketDest(int(b[0])) {
- case ToStdout:
- n, err := output.Write(out)
- if err != nil {
- return err
- }
- if n < len(out) {
- return errors.New("short write error occurred on stdout")
- }
- case ToStderr:
- n, err := errput.Write(out)
- if err != nil {
- return err
- }
- if n < len(out) {
- return errors.New("short write error occurred on stderr")
- }
- case ToStdin:
- n, err := input.Write(out)
+ if n < 8 {
+ return errors.New("short read and no full header read")
+ }
+
+ messageSize = int64(binary.BigEndian.Uint32(headerBytes[4:8]))
+
+ switch IntToSocketDest(int(headerBytes[0])) {
+ case ToStdout:
+ if output != nil {
+ _, err := io.CopyN(output, r, messageSize)
+ if err != nil {
+ return err
+ }
+ }
+ case ToStderr:
+ if errput != nil {
+ _, err := io.CopyN(errput, r, messageSize)
+ if err != nil {
+ return err
+ }
+ }
+ case ToStdin:
+ if input != nil {
+ _, err := io.CopyN(input, r, messageSize)
+ if err != nil {
+ return err
+ }
+ }
+ case TerminalResize:
+ if resize != nil {
+ out := make([]byte, messageSize)
+ if messageSize > 0 {
+ _, err = io.ReadFull(r, out)
+
if err != nil {
return err
}
- if n < len(out) {
- return errors.New("short write error occurred on stdin")
- }
- case TerminalResize:
- // Resize events come over in bytes, need to be reserialized
- resizeEvent := remotecommand.TerminalSize{}
- if err := json.Unmarshal(out, &resizeEvent); err != nil {
- return err
- }
- resize <- resizeEvent
- case Quit:
- return nil
}
- b = b[eom:]
- } else {
- // We do not have the header and full message, need to slurp again
- saveb = b
- break
+ // Resize events come over in bytes, need to be reserialized
+ resizeEvent := remotecommand.TerminalSize{}
+ if err := json.Unmarshal(out, &resizeEvent); err != nil {
+ return err
+ }
+ resize <- resizeEvent
+ }
+ case Quit:
+ out := make([]byte, messageSize)
+ if messageSize > 0 {
+ _, err = io.ReadFull(r, out)
+
+ if err != nil {
+ return err
+ }
+ }
+ if execEcChan != nil {
+ ecInt := binary.BigEndian.Uint32(out)
+ execEcChan <- int(ecInt)
}
+ return nil
+
+ default:
+ // Something really went wrong
+ return errors.New("unknown multiplex destination")
}
}
- return nil
+}
+
+// HangUp sends message to peer to close connection
+func HangUp(writer *bufio.Writer, ec uint32) (err error) {
+ n := 0
+ msg := make([]byte, 4)
+
+ binary.BigEndian.PutUint32(msg, ec)
+
+ writeQuit := NewVirtWriteCloser(writer, Quit)
+ if n, err = writeQuit.Write(msg); err != nil {
+ return
+ }
+
+ if n != len(msg) {
+ return errors.Errorf("Failed to send complete %s message", string(msg))
+ }
+ return
}