summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/apparmor/apparmor.go7
-rw-r--r--pkg/apparmor/apparmor_linux.go66
-rw-r--r--pkg/apparmor/apparmor_unsupported.go16
-rw-r--r--pkg/hooks/exec/exec.go7
-rw-r--r--pkg/hooks/exec/exec_test.go6
-rw-r--r--pkg/hooks/exec/runtimeconfigfilter.go68
-rw-r--r--pkg/hooks/exec/runtimeconfigfilter_test.go266
-rw-r--r--pkg/rootless/rootless_linux.c9
-rw-r--r--pkg/rootless/rootless_unsupported.go13
-rw-r--r--pkg/spec/config_unsupported.go4
-rw-r--r--pkg/spec/createconfig.go54
-rw-r--r--pkg/spec/spec.go7
-rw-r--r--pkg/sysinfo/README.md1
-rw-r--r--pkg/sysinfo/numcpu.go12
-rw-r--r--pkg/sysinfo/numcpu_linux.go44
-rw-r--r--pkg/sysinfo/numcpu_windows.go37
-rw-r--r--pkg/sysinfo/sysinfo.go144
-rw-r--r--pkg/sysinfo/sysinfo_linux.go254
-rw-r--r--pkg/sysinfo/sysinfo_linux_test.go104
-rw-r--r--pkg/sysinfo/sysinfo_solaris.go121
-rw-r--r--pkg/sysinfo/sysinfo_test.go26
-rw-r--r--pkg/sysinfo/sysinfo_unix.go9
-rw-r--r--pkg/sysinfo/sysinfo_windows.go9
-rw-r--r--pkg/trust/trust.go129
-rw-r--r--pkg/util/utils.go23
-rw-r--r--pkg/varlinkapi/containers.go86
-rw-r--r--pkg/varlinkapi/containers_create.go4
-rw-r--r--pkg/varlinkapi/images.go36
-rw-r--r--pkg/varlinkapi/system.go12
29 files changed, 1495 insertions, 79 deletions
diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go
index 8b9f99477..45c029c07 100644
--- a/pkg/apparmor/apparmor.go
+++ b/pkg/apparmor/apparmor.go
@@ -2,11 +2,16 @@ package apparmor
import (
"errors"
+ libpodVersion "github.com/containers/libpod/version"
)
var (
+ // DefaultLipodProfilePrefix is used for version-independent presence checks.
+ DefaultLipodProfilePrefix = "libpod-default" + "-"
// DefaultLibpodProfile is the name of default libpod AppArmor profile.
- DefaultLibpodProfile = "libpod-default"
+ DefaultLibpodProfile = DefaultLipodProfilePrefix + libpodVersion.Version
// ErrApparmorUnsupported indicates that AppArmor support is not supported.
ErrApparmorUnsupported = errors.New("AppArmor is not supported")
+ // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode.
+ ErrApparmorRootless = errors.New("AppArmor is not supported in rootless mode")
)
diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go
index b0e3ca0fd..0787b3fa5 100644
--- a/pkg/apparmor/apparmor_linux.go
+++ b/pkg/apparmor/apparmor_linux.go
@@ -13,7 +13,10 @@ import (
"strings"
"text/template"
+ "github.com/containers/libpod/pkg/rootless"
runcaa "github.com/opencontainers/runc/libcontainer/apparmor"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// profileDirectory is the file store for apparmor profiles and macros.
@@ -21,6 +24,9 @@ var profileDirectory = "/etc/apparmor.d"
// IsEnabled returns true if AppArmor is enabled on the host.
func IsEnabled() bool {
+ if rootless.IsRootless() {
+ return false
+ }
return runcaa.IsEnabled()
}
@@ -71,6 +77,10 @@ func macroExists(m string) bool {
// InstallDefault generates a default profile and loads it into the kernel
// using 'apparmor_parser'.
func InstallDefault(name string) error {
+ if rootless.IsRootless() {
+ return ErrApparmorRootless
+ }
+
p := profileData{
Name: name,
}
@@ -97,6 +107,10 @@ func InstallDefault(name string) error {
// IsLoaded checks if a profile with the given name has been loaded into the
// kernel.
func IsLoaded(name string) (bool, error) {
+ if name != "" && rootless.IsRootless() {
+ return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
+ }
+
file, err := os.Open("/sys/kernel/security/apparmor/profiles")
if err != nil {
if os.IsNotExist(err) {
@@ -188,3 +202,55 @@ func parseAAParserVersion(output string) (int, error) {
return numericVersion, nil
}
+
+// CheckProfileAndLoadDefault checks if the specified profile is loaded and
+// loads the DefaultLibpodProfile if the specified on is prefixed by
+// DefaultLipodProfilePrefix. This allows to always load and apply the latest
+// default AppArmor profile. Note that AppArmor requires root. If it's a
+// default profile, return DefaultLipodProfilePrefix, otherwise the specified
+// one.
+func CheckProfileAndLoadDefault(name string) (string, error) {
+ if name == "unconfined" {
+ return name, nil
+ }
+
+ if name != "" && rootless.IsRootless() {
+ return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
+ }
+
+ if name != "" && !runcaa.IsEnabled() {
+ 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,
+ // ignore it and return the name.
+ if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) {
+ isLoaded, err := IsLoaded(name)
+ if err != nil {
+ return "", err
+ }
+ if !isLoaded {
+ return "", fmt.Errorf("AppArmor profile %q specified but not loaded")
+ }
+ return name, nil
+ }
+
+ name = DefaultLibpodProfile
+ // To avoid expensive redundant loads on each invocation, check
+ // if it's loaded before installing it.
+ isLoaded, err := IsLoaded(name)
+ if err != nil {
+ return "", err
+ }
+ if !isLoaded {
+ err = InstallDefault(name)
+ if err != nil {
+ return "", err
+ }
+ logrus.Infof("successfully loaded AppAmor profile %q", name)
+ } else {
+ logrus.Infof("AppAmor profile %q is already loaded", name)
+ }
+
+ return name, nil
+}
diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go
index df1336b07..b2b4de5f5 100644
--- a/pkg/apparmor/apparmor_unsupported.go
+++ b/pkg/apparmor/apparmor_unsupported.go
@@ -2,19 +2,25 @@
package apparmor
-// IsEnabled returns true if AppArmor is enabled on the host.
+// IsEnabled dummy.
func IsEnabled() bool {
return false
}
-// InstallDefault generates a default profile in a temp directory determined by
-// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'.
+// InstallDefault dummy.
func InstallDefault(name string) error {
return ErrApparmorUnsupported
}
-// IsLoaded checks if a profile with the given name has been loaded into the
-// kernel.
+// IsLoaded dummy.
func IsLoaded(name string) (bool, error) {
return false, ErrApparmorUnsupported
}
+
+// CheckProfileAndLoadDefault dummy.
+func CheckProfileAndLoadDefault(name string) (string, error) {
+ if name == "" {
+ return "", nil
+ }
+ return "", ErrApparmorUnsupported
+}
diff --git a/pkg/hooks/exec/exec.go b/pkg/hooks/exec/exec.go
index 94469b1d2..0dd091561 100644
--- a/pkg/hooks/exec/exec.go
+++ b/pkg/hooks/exec/exec.go
@@ -10,6 +10,7 @@ import (
"time"
rspec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
)
// DefaultPostKillTimeout is the recommended default post-kill timeout.
@@ -42,7 +43,11 @@ func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer,
}
exit := make(chan error, 1)
go func() {
- exit <- cmd.Wait()
+ err := cmd.Wait()
+ if err != nil {
+ err = errors.Wrapf(err, "executing %v", cmd.Args)
+ }
+ exit <- err
}()
select {
diff --git a/pkg/hooks/exec/exec_test.go b/pkg/hooks/exec/exec_test.go
index 62e45ff3a..7aac315cb 100644
--- a/pkg/hooks/exec/exec_test.go
+++ b/pkg/hooks/exec/exec_test.go
@@ -163,14 +163,14 @@ func TestRunCancel(t *testing.T) {
name: "context timeout",
contextTimeout: time.Duration(1) * time.Second,
expectedStdout: "waiting\n",
- expectedHookError: "^signal: killed$",
+ expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$",
expectedRunError: context.DeadlineExceeded,
},
{
name: "hook timeout",
hookTimeout: &one,
expectedStdout: "waiting\n",
- expectedHookError: "^signal: killed$",
+ expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$",
expectedRunError: context.DeadlineExceeded,
},
} {
@@ -207,7 +207,7 @@ func TestRunKillTimeout(t *testing.T) {
}
hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, time.Duration(0))
assert.Equal(t, context.DeadlineExceeded, err)
- assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|signal: killed)$", hookErr)
+ assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|executing \\[sh -c sleep 1]: signal: killed)$", hookErr)
}
func init() {
diff --git a/pkg/hooks/exec/runtimeconfigfilter.go b/pkg/hooks/exec/runtimeconfigfilter.go
new file mode 100644
index 000000000..c6971f680
--- /dev/null
+++ b/pkg/hooks/exec/runtimeconfigfilter.go
@@ -0,0 +1,68 @@
+package exec
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "reflect"
+ "time"
+
+ "github.com/davecgh/go-spew/spew"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/pmezard/go-difflib/difflib"
+ "github.com/sirupsen/logrus"
+)
+
+var spewConfig = spew.ConfigState{
+ Indent: " ",
+ DisablePointerAddresses: true,
+ DisableCapacities: true,
+ SortKeys: true,
+}
+
+// RuntimeConfigFilter calls a series of hooks. But instead of
+// passing container state on their standard input,
+// RuntimeConfigFilter passes the proposed runtime configuration (and
+// 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)
+ for i, hook := range hooks {
+ var stdout bytes.Buffer
+ hookErr, err = Run(ctx, &hook, data, &stdout, nil, postKillTimeout)
+ if err != nil {
+ return hookErr, err
+ }
+
+ data = stdout.Bytes()
+ var newConfig spec.Spec
+ err = json.Unmarshal(data, &newConfig)
+ if err != nil {
+ logrus.Debugf("invalid JSON from config-filter hook %d:\n%s", i, string(data))
+ return nil, errors.Wrapf(err, "unmarshal output from config-filter hook %d", i)
+ }
+
+ if !reflect.DeepEqual(config, &newConfig) {
+ old := spewConfig.Sdump(config)
+ new := spewConfig.Sdump(&newConfig)
+ diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
+ A: difflib.SplitLines(old),
+ B: difflib.SplitLines(new),
+ FromFile: "Old",
+ FromDate: "",
+ ToFile: "New",
+ ToDate: "",
+ Context: 1,
+ })
+ if err == nil {
+ logrus.Debugf("precreate hook %d made configuration changes:\n%s", i, diff)
+ } else {
+ logrus.Warnf("precreate hook %d made configuration changes, but we could not compute a diff: %v", i, err)
+ }
+ }
+
+ *config = newConfig
+ }
+
+ return nil, nil
+}
diff --git a/pkg/hooks/exec/runtimeconfigfilter_test.go b/pkg/hooks/exec/runtimeconfigfilter_test.go
new file mode 100644
index 000000000..52d590d14
--- /dev/null
+++ b/pkg/hooks/exec/runtimeconfigfilter_test.go
@@ -0,0 +1,266 @@
+package exec
+
+import (
+ "context"
+ "encoding/json"
+ "os"
+ "testing"
+ "time"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+)
+
+func pointerInt(value int) *int {
+ return &value
+}
+
+func pointerUInt32(value uint32) *uint32 {
+ return &value
+}
+
+func pointerFileMode(value os.FileMode) *os.FileMode {
+ return &value
+}
+
+func TestRuntimeConfigFilter(t *testing.T) {
+ unexpectedEndOfJSONInput := json.Unmarshal([]byte("{\n"), nil)
+
+ for _, test := range []struct {
+ name string
+ contextTimeout time.Duration
+ hooks []spec.Hook
+ input *spec.Spec
+ expected *spec.Spec
+ expectedHookError string
+ expectedRunError error
+ }{
+ {
+ name: "no-op",
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", "cat"},
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ },
+ {
+ name: "device injection",
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`},
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ Linux: &spec.Linux{
+ Devices: []spec.LinuxDevice{
+ {
+ Path: "/dev/fuse",
+ Type: "c",
+ Major: 10,
+ Minor: 229,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ },
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ Linux: &spec.Linux{
+ Devices: []spec.LinuxDevice{
+ {
+ Path: "/dev/fuse",
+ Type: "c",
+ Major: 10,
+ Minor: 229,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ {
+ Path: "/dev/sda",
+ Type: "b",
+ Major: 8,
+ Minor: 0,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "chaining",
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`},
+ },
+ {
+ Path: path,
+ Args: []string{"sh", "-c", `sed 's|/dev/sda|/dev/sdb|'`},
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ Linux: &spec.Linux{
+ Devices: []spec.LinuxDevice{
+ {
+ Path: "/dev/fuse",
+ Type: "c",
+ Major: 10,
+ Minor: 229,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ },
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ Linux: &spec.Linux{
+ Devices: []spec.LinuxDevice{
+ {
+ Path: "/dev/fuse",
+ Type: "c",
+ Major: 10,
+ Minor: 229,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ {
+ Path: "/dev/sdb",
+ Type: "b",
+ Major: 8,
+ Minor: 0,
+ FileMode: pointerFileMode(0600),
+ UID: pointerUInt32(0),
+ GID: pointerUInt32(0),
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "context timeout",
+ contextTimeout: time.Duration(1) * time.Second,
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", "sleep 2"},
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$",
+ expectedRunError: context.DeadlineExceeded,
+ },
+ {
+ name: "hook timeout",
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", "sleep 2"},
+ Timeout: pointerInt(1),
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$",
+ expectedRunError: context.DeadlineExceeded,
+ },
+ {
+ name: "invalid JSON",
+ hooks: []spec.Hook{
+ {
+ Path: path,
+ Args: []string{"sh", "-c", "echo '{'"},
+ },
+ },
+ input: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expected: &spec.Spec{
+ Version: "1.0.0",
+ Root: &spec.Root{
+ Path: "rootfs",
+ },
+ },
+ expectedRunError: unexpectedEndOfJSONInput,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ ctx := context.Background()
+ if test.contextTimeout > 0 {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, test.contextTimeout)
+ defer cancel()
+ }
+ hookErr, err := RuntimeConfigFilter(ctx, test.hooks, test.input, DefaultPostKillTimeout)
+ assert.Equal(t, test.expectedRunError, errors.Cause(err))
+ if test.expectedHookError == "" {
+ if hookErr != nil {
+ t.Fatal(hookErr)
+ }
+ } else {
+ assert.Regexp(t, test.expectedHookError, hookErr.Error())
+ }
+ assert.Equal(t, test.expected, test.input)
+ })
+ }
+}
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 1d28ff68d..279a03d3f 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -70,9 +70,12 @@ get_cmd_line_args (pid_t pid)
if (allocated == used)
{
allocated += 512;
- buffer = realloc (buffer, allocated);
- if (buffer == NULL)
- return NULL;
+ char *tmp = realloc (buffer, allocated);
+ if (buffer == NULL) {
+ free(buffer);
+ return NULL;
+ }
+ buffer=tmp;
}
}
close (fd);
diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go
index d72402c9f..1823c023e 100644
--- a/pkg/rootless/rootless_unsupported.go
+++ b/pkg/rootless/rootless_unsupported.go
@@ -14,7 +14,7 @@ func IsRootless() bool {
// BecomeRootInUserNS is a stub function that always returns false and an
// error on unsupported OS's
func BecomeRootInUserNS() (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os")
+ return false, -1, errors.New("this function is not supported on this os1")
}
// GetRootlessUID returns the UID of the user in the parent userNS
@@ -34,11 +34,18 @@ func SkipStorageSetup() bool {
// JoinNS re-exec podman in a new userNS and join the user namespace of the specified
// PID.
func JoinNS(pid uint) (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os")
+ return false, -1, errors.New("this function is not supported on this os2")
}
// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the
// specified path.
func JoinNSPath(path string) (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os")
+ return false, -1, errors.New("this function is not supported on this os3")
+}
+
+// JoinDirectUserAndMountNS 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 JoinDirectUserAndMountNS(pid uint) (bool, int, error) {
+ return false, -1, errors.New("this function is not supported on this os4")
}
diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go
index c2a58696d..160414878 100644
--- a/pkg/spec/config_unsupported.go
+++ b/pkg/spec/config_unsupported.go
@@ -26,3 +26,7 @@ func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) {
func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) {
return nil, errors.New("function not implemented")
}
+
+func devicesFromPath(g *generate.Generator, devicePath string) error {
+ return errors.New("function not implemented")
+}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 25f8cd7a1..344f4afb9 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -2,6 +2,7 @@ package createconfig
import (
"encoding/json"
+ "fmt"
"net"
"os"
"strconv"
@@ -145,6 +146,36 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
return c.createBlockIO()
}
+// AddContainerInitBinary adds the init binary specified by path iff the
+// container will run in a private PID namespace that is not shared with the
+// host or another pre-existing container, where an init-like process is
+// already running.
+//
+// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
+// to execute the bind-mounted binary as PID 1.
+func (c *CreateConfig) AddContainerInitBinary(path string) error {
+ if path == "" {
+ return fmt.Errorf("please specify a path to the container-init binary")
+ }
+ if !c.PidMode.IsPrivate() {
+ return fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
+ }
+ if c.Systemd {
+ return fmt.Errorf("cannot use container-init binary with systemd")
+ }
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return errors.Wrap(err, "container-init binary not found on the host")
+ }
+ c.Command = append([]string{"/dev/init", "--"}, c.Command...)
+ c.Mounts = append(c.Mounts, spec.Mount{
+ Destination: "/dev/init",
+ Type: "bind",
+ Source: path,
+ Options: []string{"bind", "ro"},
+ })
+ return nil
+}
+
func processOptions(options []string) []string {
var (
foundrw, foundro bool
@@ -310,10 +341,9 @@ func (c *CreateConfig) createExitCommand() []string {
}
// GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions
-func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
+func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var portBindings []ocicni.PortMapping
- var pod *libpod.Pod
var err error
if c.Interactive {
@@ -327,12 +357,14 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
logrus.Debugf("appending name %s", c.Name)
options = append(options, libpod.WithName(c.Name))
}
- if c.Pod != "" {
- logrus.Debugf("adding container to pod %s", c.Pod)
- pod, err = runtime.LookupPod(c.Pod)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to add container to pod %s", c.Pod)
+ if c.Pod != "" || pod != nil {
+ if pod == nil {
+ pod, err = runtime.LookupPod(c.Pod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to add container to pod %s", c.Pod)
+ }
}
+ logrus.Debugf("adding container to pod %s", c.Pod)
options = append(options, runtime.WithPod(pod))
}
if len(c.PortBindings) > 0 {
@@ -390,11 +422,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
}
options = append(options, libpod.WithNetNSFrom(connectedCtr))
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
- isRootless := rootless.IsRootless()
postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
- if isRootless && len(portBindings) > 0 {
- return nil, errors.New("port bindings are not yet supported by rootless containers")
- }
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
}
@@ -487,7 +515,9 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
if c.CgroupParent != "" {
options = append(options, libpod.WithCgroupParent(c.CgroupParent))
}
- if c.Detach {
+ // For a rootless container always cleanup the storage/network as they
+ // run in a different namespace thus not reusable when we restart.
+ if c.Detach || rootless.IsRootless() {
options = append(options, libpod.WithExitCommand(c.createExitCommand()))
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 3d6603364..46105af4a 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -6,8 +6,8 @@ import (
"strings"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/storage/pkg/mount"
"github.com/docker/docker/daemon/caps"
- "github.com/docker/docker/pkg/mount"
"github.com/docker/go-units"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
@@ -252,6 +252,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
}
// SECURITY OPTS
g.SetProcessNoNewPrivileges(config.NoNewPrivs)
+
g.SetProcessApparmorProfile(config.ApparmorProfile)
blockAccessToKernelFilesystems(config, &g)
@@ -375,6 +376,10 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
}
func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) {
+ if config.PidMode.IsHost() && rootless.IsRootless() {
+ return
+ }
+
if !config.Privileged {
for _, mp := range []string{
"/proc/acpi",
diff --git a/pkg/sysinfo/README.md b/pkg/sysinfo/README.md
new file mode 100644
index 000000000..c1530cef0
--- /dev/null
+++ b/pkg/sysinfo/README.md
@@ -0,0 +1 @@
+SysInfo stores information about which features a kernel supports.
diff --git a/pkg/sysinfo/numcpu.go b/pkg/sysinfo/numcpu.go
new file mode 100644
index 000000000..aeb1a3a80
--- /dev/null
+++ b/pkg/sysinfo/numcpu.go
@@ -0,0 +1,12 @@
+// +build !linux,!windows
+
+package sysinfo
+
+import (
+ "runtime"
+)
+
+// NumCPU returns the number of CPUs
+func NumCPU() int {
+ return runtime.NumCPU()
+}
diff --git a/pkg/sysinfo/numcpu_linux.go b/pkg/sysinfo/numcpu_linux.go
new file mode 100644
index 000000000..f1d2d9db3
--- /dev/null
+++ b/pkg/sysinfo/numcpu_linux.go
@@ -0,0 +1,44 @@
+// +build linux
+
+package sysinfo
+
+import (
+ "runtime"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// numCPU queries the system for the count of threads available
+// for use to this process.
+//
+// Issues two syscalls.
+// Returns 0 on errors. Use |runtime.NumCPU| in that case.
+func numCPU() int {
+ // Gets the affinity mask for a process: The very one invoking this function.
+ pid, _, _ := unix.RawSyscall(unix.SYS_GETPID, 0, 0, 0)
+
+ var mask [1024 / 64]uintptr
+ _, _, err := unix.RawSyscall(unix.SYS_SCHED_GETAFFINITY, pid, uintptr(len(mask)*8), uintptr(unsafe.Pointer(&mask[0])))
+ if err != 0 {
+ return 0
+ }
+
+ // For every available thread a bit is set in the mask.
+ ncpu := 0
+ for _, e := range mask {
+ if e == 0 {
+ continue
+ }
+ ncpu += int(popcnt(uint64(e)))
+ }
+ return ncpu
+}
+
+// NumCPU returns the number of CPUs which are currently online
+func NumCPU() int {
+ if ncpu := numCPU(); ncpu > 0 {
+ return ncpu
+ }
+ return runtime.NumCPU()
+}
diff --git a/pkg/sysinfo/numcpu_windows.go b/pkg/sysinfo/numcpu_windows.go
new file mode 100644
index 000000000..1d89dd550
--- /dev/null
+++ b/pkg/sysinfo/numcpu_windows.go
@@ -0,0 +1,37 @@
+// +build windows
+
+package sysinfo
+
+import (
+ "runtime"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var (
+ kernel32 = windows.NewLazySystemDLL("kernel32.dll")
+ getCurrentProcess = kernel32.NewProc("GetCurrentProcess")
+ getProcessAffinityMask = kernel32.NewProc("GetProcessAffinityMask")
+)
+
+func numCPU() int {
+ // Gets the affinity mask for a process
+ var mask, sysmask uintptr
+ currentProcess, _, _ := getCurrentProcess.Call()
+ ret, _, _ := getProcessAffinityMask.Call(currentProcess, uintptr(unsafe.Pointer(&mask)), uintptr(unsafe.Pointer(&sysmask)))
+ if ret == 0 {
+ return 0
+ }
+ // For every available thread a bit is set in the mask.
+ ncpu := int(popcnt(uint64(mask)))
+ return ncpu
+}
+
+// NumCPU returns the number of CPUs which are currently online
+func NumCPU() int {
+ if ncpu := numCPU(); ncpu > 0 {
+ return ncpu
+ }
+ return runtime.NumCPU()
+}
diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go
new file mode 100644
index 000000000..f046de4b1
--- /dev/null
+++ b/pkg/sysinfo/sysinfo.go
@@ -0,0 +1,144 @@
+package sysinfo
+
+import "github.com/docker/docker/pkg/parsers"
+
+// SysInfo stores information about which features a kernel supports.
+// TODO Windows: Factor out platform specific capabilities.
+type SysInfo struct {
+ // Whether the kernel supports AppArmor or not
+ AppArmor bool
+ // Whether the kernel supports Seccomp or not
+ Seccomp bool
+
+ cgroupMemInfo
+ cgroupCPUInfo
+ cgroupBlkioInfo
+ cgroupCpusetInfo
+ cgroupPids
+
+ // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work
+ IPv4ForwardingDisabled bool
+
+ // Whether bridge-nf-call-iptables is supported or not
+ BridgeNFCallIPTablesDisabled bool
+
+ // Whether bridge-nf-call-ip6tables is supported or not
+ BridgeNFCallIP6TablesDisabled bool
+
+ // Whether the cgroup has the mountpoint of "devices" or not
+ CgroupDevicesEnabled bool
+}
+
+type cgroupMemInfo struct {
+ // Whether memory limit is supported or not
+ MemoryLimit bool
+
+ // Whether swap limit is supported or not
+ SwapLimit bool
+
+ // Whether soft limit is supported or not
+ MemoryReservation bool
+
+ // Whether OOM killer disable is supported or not
+ OomKillDisable bool
+
+ // Whether memory swappiness is supported or not
+ MemorySwappiness bool
+
+ // Whether kernel memory limit is supported or not
+ KernelMemory bool
+}
+
+type cgroupCPUInfo struct {
+ // Whether CPU shares is supported or not
+ CPUShares bool
+
+ // Whether CPU CFS(Completely Fair Scheduler) period is supported or not
+ CPUCfsPeriod bool
+
+ // Whether CPU CFS(Completely Fair Scheduler) quota is supported or not
+ CPUCfsQuota bool
+
+ // Whether CPU real-time period is supported or not
+ CPURealtimePeriod bool
+
+ // Whether CPU real-time runtime is supported or not
+ CPURealtimeRuntime bool
+}
+
+type cgroupBlkioInfo struct {
+ // Whether Block IO weight is supported or not
+ BlkioWeight bool
+
+ // Whether Block IO weight_device is supported or not
+ BlkioWeightDevice bool
+
+ // Whether Block IO read limit in bytes per second is supported or not
+ BlkioReadBpsDevice bool
+
+ // Whether Block IO write limit in bytes per second is supported or not
+ BlkioWriteBpsDevice bool
+
+ // Whether Block IO read limit in IO per second is supported or not
+ BlkioReadIOpsDevice bool
+
+ // Whether Block IO write limit in IO per second is supported or not
+ BlkioWriteIOpsDevice bool
+}
+
+type cgroupCpusetInfo struct {
+ // Whether Cpuset is supported or not
+ Cpuset bool
+
+ // Available Cpuset's cpus
+ Cpus string
+
+ // Available Cpuset's memory nodes
+ Mems string
+}
+
+type cgroupPids struct {
+ // Whether Pids Limit is supported or not
+ PidsLimit bool
+}
+
+// IsCpusetCpusAvailable returns `true` if the provided string set is contained
+// in cgroup's cpuset.cpus set, `false` otherwise.
+// If error is not nil a parsing error occurred.
+func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) {
+ return isCpusetListAvailable(provided, c.Cpus)
+}
+
+// IsCpusetMemsAvailable returns `true` if the provided string set is contained
+// in cgroup's cpuset.mems set, `false` otherwise.
+// If error is not nil a parsing error occurred.
+func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) {
+ return isCpusetListAvailable(provided, c.Mems)
+}
+
+func isCpusetListAvailable(provided, available string) (bool, error) {
+ parsedProvided, err := parsers.ParseUintList(provided)
+ if err != nil {
+ return false, err
+ }
+ parsedAvailable, err := parsers.ParseUintList(available)
+ if err != nil {
+ return false, err
+ }
+ for k := range parsedProvided {
+ if !parsedAvailable[k] {
+ return false, nil
+ }
+ }
+ return true, nil
+}
+
+// Returns bit count of 1, used by NumCPU
+func popcnt(x uint64) (n byte) {
+ x -= (x >> 1) & 0x5555555555555555
+ x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
+ x += x >> 4
+ x &= 0x0f0f0f0f0f0f0f0f
+ x *= 0x0101010101010101
+ return byte(x >> 56)
+}
diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go
new file mode 100644
index 000000000..f4047b63c
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_linux.go
@@ -0,0 +1,254 @@
+package sysinfo
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+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)
+ }
+ mps := make(map[string]string)
+ for _, m := range cgMounts {
+ for _, ss := range m.Subsystems {
+ mps[ss] = m.Mountpoint
+ }
+ }
+ return mps, nil
+}
+
+// New returns a new SysInfo, using the filesystem to detect which features
+// the kernel supports. If `quiet` is `false` warnings are printed in logs
+// whenever an error occurs or misconfigurations are present.
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ cgMounts, err := findCgroupMountpoints()
+ if err != nil {
+ logrus.Warnf("Failed to parse cgroup information: %v", err)
+ } else {
+ sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet)
+ sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet)
+ sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet)
+ sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet)
+ sysInfo.cgroupPids = checkCgroupPids(quiet)
+ }
+
+ _, ok := cgMounts["devices"]
+ sysInfo.CgroupDevicesEnabled = ok
+
+ sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
+ sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
+ sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
+
+ // Check if AppArmor is supported.
+ if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
+ sysInfo.AppArmor = true
+ }
+
+ // Check if Seccomp is supported, via CONFIG_SECCOMP.
+ if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
+ // Make sure the kernel has CONFIG_SECCOMP_FILTER.
+ if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
+ sysInfo.Seccomp = true
+ }
+ }
+
+ return sysInfo
+}
+
+// checkCgroupMem reads the memory information from the memory cgroup mount point.
+func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo {
+ mountPoint, ok := cgMounts["memory"]
+ if !ok {
+ if !quiet {
+ logrus.Warn("Your kernel does not support cgroup memory limit")
+ }
+ return cgroupMemInfo{}
+ }
+
+ swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
+ if !quiet && !swapLimit {
+ logrus.Warn("Your kernel does not support swap memory limit")
+ }
+ memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
+ if !quiet && !memoryReservation {
+ logrus.Warn("Your kernel does not support memory reservation")
+ }
+ oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control")
+ if !quiet && !oomKillDisable {
+ logrus.Warn("Your kernel does not support oom control")
+ }
+ memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness")
+ if !quiet && !memorySwappiness {
+ logrus.Warn("Your kernel does not support memory swappiness")
+ }
+ kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
+ if !quiet && !kernelMemory {
+ logrus.Warn("Your kernel does not support kernel memory limit")
+ }
+
+ return cgroupMemInfo{
+ MemoryLimit: true,
+ SwapLimit: swapLimit,
+ MemoryReservation: memoryReservation,
+ OomKillDisable: oomKillDisable,
+ MemorySwappiness: memorySwappiness,
+ KernelMemory: kernelMemory,
+ }
+}
+
+// checkCgroupCPU reads the cpu information from the cpu cgroup mount point.
+func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo {
+ mountPoint, ok := cgMounts["cpu"]
+ if !ok {
+ if !quiet {
+ logrus.Warn("Unable to find cpu cgroup in mounts")
+ }
+ return cgroupCPUInfo{}
+ }
+
+ cpuShares := cgroupEnabled(mountPoint, "cpu.shares")
+ if !quiet && !cpuShares {
+ logrus.Warn("Your kernel does not support cgroup cpu shares")
+ }
+
+ cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us")
+ if !quiet && !cpuCfsPeriod {
+ logrus.Warn("Your kernel does not support cgroup cfs period")
+ }
+
+ cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
+ if !quiet && !cpuCfsQuota {
+ logrus.Warn("Your kernel does not support cgroup cfs quotas")
+ }
+
+ cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us")
+ if !quiet && !cpuRealtimePeriod {
+ logrus.Warn("Your kernel does not support cgroup rt period")
+ }
+
+ cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us")
+ if !quiet && !cpuRealtimeRuntime {
+ logrus.Warn("Your kernel does not support cgroup rt runtime")
+ }
+
+ return cgroupCPUInfo{
+ CPUShares: cpuShares,
+ CPUCfsPeriod: cpuCfsPeriod,
+ CPUCfsQuota: cpuCfsQuota,
+ CPURealtimePeriod: cpuRealtimePeriod,
+ CPURealtimeRuntime: cpuRealtimeRuntime,
+ }
+}
+
+// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point.
+func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo {
+ mountPoint, ok := cgMounts["blkio"]
+ if !ok {
+ if !quiet {
+ logrus.Warn("Unable to find blkio cgroup in mounts")
+ }
+ return cgroupBlkioInfo{}
+ }
+
+ weight := cgroupEnabled(mountPoint, "blkio.weight")
+ if !quiet && !weight {
+ logrus.Warn("Your kernel does not support cgroup blkio weight")
+ }
+
+ weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device")
+ if !quiet && !weightDevice {
+ logrus.Warn("Your kernel does not support cgroup blkio weight_device")
+ }
+
+ readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
+ if !quiet && !readBpsDevice {
+ logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device")
+ }
+
+ writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
+ if !quiet && !writeBpsDevice {
+ logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device")
+ }
+ readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
+ if !quiet && !readIOpsDevice {
+ logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device")
+ }
+
+ writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
+ if !quiet && !writeIOpsDevice {
+ logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device")
+ }
+ return cgroupBlkioInfo{
+ BlkioWeight: weight,
+ BlkioWeightDevice: weightDevice,
+ BlkioReadBpsDevice: readBpsDevice,
+ BlkioWriteBpsDevice: writeBpsDevice,
+ BlkioReadIOpsDevice: readIOpsDevice,
+ BlkioWriteIOpsDevice: writeIOpsDevice,
+ }
+}
+
+// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point.
+func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo {
+ mountPoint, ok := cgMounts["cpuset"]
+ if !ok {
+ if !quiet {
+ logrus.Warn("Unable to find cpuset cgroup in mounts")
+ }
+ return cgroupCpusetInfo{}
+ }
+
+ cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
+ if err != nil {
+ return cgroupCpusetInfo{}
+ }
+
+ mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems"))
+ if err != nil {
+ return cgroupCpusetInfo{}
+ }
+
+ return cgroupCpusetInfo{
+ Cpuset: true,
+ Cpus: strings.TrimSpace(string(cpus)),
+ Mems: strings.TrimSpace(string(mems)),
+ }
+}
+
+// checkCgroupPids reads the pids information from the pids cgroup mount point.
+func checkCgroupPids(quiet bool) cgroupPids {
+ _, err := cgroups.FindCgroupMountpoint("", "pids")
+ if err != nil {
+ if !quiet {
+ logrus.Warn(err)
+ }
+ return cgroupPids{}
+ }
+
+ return cgroupPids{
+ PidsLimit: true,
+ }
+}
+
+func cgroupEnabled(mountPoint, name string) bool {
+ _, err := os.Stat(path.Join(mountPoint, name))
+ return err == nil
+}
+
+func readProcBool(path string) bool {
+ val, err := ioutil.ReadFile(path)
+ if err != nil {
+ return false
+ }
+ return strings.TrimSpace(string(val)) == "1"
+}
diff --git a/pkg/sysinfo/sysinfo_linux_test.go b/pkg/sysinfo/sysinfo_linux_test.go
new file mode 100644
index 000000000..860784f2a
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_linux_test.go
@@ -0,0 +1,104 @@
+package sysinfo
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "golang.org/x/sys/unix"
+)
+
+func TestReadProcBool(t *testing.T) {
+ tmpDir, err := ioutil.TempDir("", "test-sysinfo-proc")
+ require.NoError(t, err)
+ defer os.RemoveAll(tmpDir)
+
+ procFile := filepath.Join(tmpDir, "read-proc-bool")
+ err = ioutil.WriteFile(procFile, []byte("1"), 0644)
+ require.NoError(t, err)
+
+ if !readProcBool(procFile) {
+ t.Fatal("expected proc bool to be true, got false")
+ }
+
+ if err := ioutil.WriteFile(procFile, []byte("0"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if readProcBool(procFile) {
+ t.Fatal("expected proc bool to be false, got true")
+ }
+
+ if readProcBool(path.Join(tmpDir, "no-exist")) {
+ t.Fatal("should be false for non-existent entry")
+ }
+
+}
+
+func TestCgroupEnabled(t *testing.T) {
+ cgroupDir, err := ioutil.TempDir("", "cgroup-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(cgroupDir)
+
+ if cgroupEnabled(cgroupDir, "test") {
+ t.Fatal("cgroupEnabled should be false")
+ }
+
+ err = ioutil.WriteFile(path.Join(cgroupDir, "test"), []byte{}, 0644)
+ require.NoError(t, err)
+
+ if !cgroupEnabled(cgroupDir, "test") {
+ t.Fatal("cgroupEnabled should be true")
+ }
+}
+
+func TestNew(t *testing.T) {
+ sysInfo := New(false)
+ require.NotNil(t, sysInfo)
+ checkSysInfo(t, sysInfo)
+
+ sysInfo = New(true)
+ require.NotNil(t, sysInfo)
+ checkSysInfo(t, sysInfo)
+}
+
+func checkSysInfo(t *testing.T, sysInfo *SysInfo) {
+ // Check if Seccomp is supported, via CONFIG_SECCOMP.then sysInfo.Seccomp must be TRUE , else FALSE
+ if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
+ // Make sure the kernel has CONFIG_SECCOMP_FILTER.
+ if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
+ require.True(t, sysInfo.Seccomp)
+ }
+ } else {
+ require.False(t, sysInfo.Seccomp)
+ }
+}
+
+func TestNewAppArmorEnabled(t *testing.T) {
+ // Check if AppArmor is supported. then it must be TRUE , else FALSE
+ if _, err := os.Stat("/sys/kernel/security/apparmor"); err != nil {
+ t.Skip("App Armor Must be Enabled")
+ }
+
+ sysInfo := New(true)
+ require.True(t, sysInfo.AppArmor)
+}
+
+func TestNewAppArmorDisabled(t *testing.T) {
+ // Check if AppArmor is supported. then it must be TRUE , else FALSE
+ if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
+ t.Skip("App Armor Must be Disabled")
+ }
+
+ sysInfo := New(true)
+ require.False(t, sysInfo.AppArmor)
+}
+
+func TestNumCPU(t *testing.T) {
+ cpuNumbers := NumCPU()
+ if cpuNumbers <= 0 {
+ t.Fatal("CPU returned must be greater than zero")
+ }
+}
diff --git a/pkg/sysinfo/sysinfo_solaris.go b/pkg/sysinfo/sysinfo_solaris.go
new file mode 100644
index 000000000..c858d57e0
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_solaris.go
@@ -0,0 +1,121 @@
+// +build solaris,cgo
+
+package sysinfo
+
+import (
+ "bytes"
+ "os/exec"
+ "strconv"
+ "strings"
+)
+
+/*
+#cgo LDFLAGS: -llgrp
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/lgrp_user.h>
+int getLgrpCount() {
+ lgrp_cookie_t lgrpcookie = LGRP_COOKIE_NONE;
+ uint_t nlgrps;
+
+ if ((lgrpcookie = lgrp_init(LGRP_VIEW_OS)) == LGRP_COOKIE_NONE) {
+ return -1;
+ }
+ nlgrps = lgrp_nlgrps(lgrpcookie);
+ return nlgrps;
+}
+*/
+import "C"
+
+// IsCPUSharesAvailable returns whether CPUShares setting is supported.
+// We need FSS to be set as default scheduling class to support CPU Shares
+func IsCPUSharesAvailable() bool {
+ cmd := exec.Command("/usr/sbin/dispadmin", "-d")
+ outBuf := new(bytes.Buffer)
+ errBuf := new(bytes.Buffer)
+ cmd.Stderr = errBuf
+ cmd.Stdout = outBuf
+
+ if err := cmd.Run(); err != nil {
+ return false
+ }
+ return (strings.Contains(outBuf.String(), "FSS"))
+}
+
+// New returns a new SysInfo, using the filesystem to detect which features
+// the kernel supports.
+//NOTE Solaris: If we change the below capabilities be sure
+// to update verifyPlatformContainerSettings() in daemon_solaris.go
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ sysInfo.cgroupMemInfo = setCgroupMem(quiet)
+ sysInfo.cgroupCPUInfo = setCgroupCPU(quiet)
+ sysInfo.cgroupBlkioInfo = setCgroupBlkioInfo(quiet)
+ sysInfo.cgroupCpusetInfo = setCgroupCPUsetInfo(quiet)
+
+ sysInfo.IPv4ForwardingDisabled = false
+
+ sysInfo.AppArmor = false
+
+ return sysInfo
+}
+
+// setCgroupMem reads the memory information for Solaris.
+func setCgroupMem(quiet bool) cgroupMemInfo {
+
+ return cgroupMemInfo{
+ MemoryLimit: true,
+ SwapLimit: true,
+ MemoryReservation: false,
+ OomKillDisable: false,
+ MemorySwappiness: false,
+ KernelMemory: false,
+ }
+}
+
+// setCgroupCPU reads the cpu information for Solaris.
+func setCgroupCPU(quiet bool) cgroupCPUInfo {
+
+ return cgroupCPUInfo{
+ CPUShares: true,
+ CPUCfsPeriod: false,
+ CPUCfsQuota: true,
+ CPURealtimePeriod: false,
+ CPURealtimeRuntime: false,
+ }
+}
+
+// blkio switches are not supported in Solaris.
+func setCgroupBlkioInfo(quiet bool) cgroupBlkioInfo {
+
+ return cgroupBlkioInfo{
+ BlkioWeight: false,
+ BlkioWeightDevice: false,
+ }
+}
+
+// setCgroupCPUsetInfo reads the cpuset information for Solaris.
+func setCgroupCPUsetInfo(quiet bool) cgroupCpusetInfo {
+
+ return cgroupCpusetInfo{
+ Cpuset: true,
+ Cpus: getCPUCount(),
+ Mems: getLgrpCount(),
+ }
+}
+
+func getCPUCount() string {
+ ncpus := C.sysconf(C._SC_NPROCESSORS_ONLN)
+ if ncpus <= 0 {
+ return ""
+ }
+ return strconv.FormatInt(int64(ncpus), 16)
+}
+
+func getLgrpCount() string {
+ nlgrps := C.getLgrpCount()
+ if nlgrps <= 0 {
+ return ""
+ }
+ return strconv.FormatInt(int64(nlgrps), 16)
+}
diff --git a/pkg/sysinfo/sysinfo_test.go b/pkg/sysinfo/sysinfo_test.go
new file mode 100644
index 000000000..b61fbcf54
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_test.go
@@ -0,0 +1,26 @@
+package sysinfo
+
+import "testing"
+
+func TestIsCpusetListAvailable(t *testing.T) {
+ cases := []struct {
+ provided string
+ available string
+ res bool
+ err bool
+ }{
+ {"1", "0-4", true, false},
+ {"01,3", "0-4", true, false},
+ {"", "0-7", true, false},
+ {"1--42", "0-7", false, true},
+ {"1-42", "00-1,8,,9", false, true},
+ {"1,41-42", "43,45", false, false},
+ {"0-3", "", false, false},
+ }
+ 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)
+ }
+ }
+}
diff --git a/pkg/sysinfo/sysinfo_unix.go b/pkg/sysinfo/sysinfo_unix.go
new file mode 100644
index 000000000..45f3ef1c6
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_unix.go
@@ -0,0 +1,9 @@
+// +build !linux,!solaris,!windows
+
+package sysinfo
+
+// New returns an empty SysInfo for non linux nor solaris for now.
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ return sysInfo
+}
diff --git a/pkg/sysinfo/sysinfo_windows.go b/pkg/sysinfo/sysinfo_windows.go
new file mode 100644
index 000000000..4e6255bc5
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_windows.go
@@ -0,0 +1,9 @@
+// +build windows
+
+package sysinfo
+
+// New returns an empty SysInfo for windows for now.
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ return sysInfo
+}
diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go
index efc760364..31e41903e 100644
--- a/pkg/trust/trust.go
+++ b/pkg/trust/trust.go
@@ -2,6 +2,7 @@ package trust
import (
"bufio"
+ "bytes"
"encoding/base64"
"encoding/json"
"io/ioutil"
@@ -9,7 +10,6 @@ import (
"os/exec"
"path/filepath"
"strings"
- "unsafe"
"github.com/containers/image/types"
"github.com/pkg/errors"
@@ -52,6 +52,14 @@ type RegistryNamespace struct {
SigStoreStaging string `json:"sigstore-staging"` // For writing only.
}
+// ShowOutput keep the fields for image trust show command
+type ShowOutput struct {
+ Repo string
+ Trusttype string
+ GPGid string
+ Sigstore string
+}
+
// DefaultPolicyPath returns a path to the default policy of the system.
func DefaultPolicyPath(sys *types.SystemContext) string {
systemDefaultPolicyPath := "/etc/containers/policy.json"
@@ -167,84 +175,127 @@ func CreateTmpFile(dir, pattern string, content []byte) (string, error) {
return tmpfile.Name(), nil
}
-// GetGPGId return GPG identity, either bracketed <email> or ID string
-// comma separated if more than one key
-func GetGPGId(keys []string) string {
+func getGPGIdFromKeyPath(path []string) []string {
+ var uids []string
+ for _, k := range path {
+ cmd := exec.Command("gpg2", "--with-colons", k)
+ results, err := cmd.Output()
+ if err != nil {
+ logrus.Warnf("error get key identity: %s", err)
+ continue
+ }
+ uids = append(uids, parseUids(results)...)
+ }
+ return uids
+}
+
+func getGPGIdFromKeyData(keys []string) []string {
+ var uids []string
for _, k := range keys {
- if _, err := os.Stat(k); err != nil {
- decodeKey, err := base64.StdEncoding.DecodeString(k)
- if err != nil {
- logrus.Warnf("error decoding key data")
- continue
- }
- tmpfileName, err := CreateTmpFile("/run/", "", decodeKey)
- if err != nil {
- logrus.Warnf("error creating key date temp file %s", err)
- }
- defer os.Remove(tmpfileName)
- k = tmpfileName
+ decodeKey, err := base64.StdEncoding.DecodeString(k)
+ if err != nil {
+ logrus.Warnf("error decoding key data")
+ continue
}
+ tmpfileName, err := CreateTmpFile("", "", decodeKey)
+ if err != nil {
+ logrus.Warnf("error creating key date temp file %s", err)
+ }
+ defer os.Remove(tmpfileName)
+ k = tmpfileName
cmd := exec.Command("gpg2", "--with-colons", k)
results, err := cmd.Output()
if err != nil {
logrus.Warnf("error get key identity: %s", err)
continue
}
- resultsStr := *(*string)(unsafe.Pointer(&results))
- scanner := bufio.NewScanner(strings.NewReader(resultsStr))
- var parseduids []string
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
- uid := strings.Split(line, ":")[9]
- if uid == "" {
- continue
- }
- parseduid := uid
- if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
- parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
- }
- parseduids = append(parseduids, parseduid)
+ uids = append(uids, parseUids(results)...)
+ }
+ return uids
+}
+
+func parseUids(colonDelimitKeys []byte) []string {
+ var parseduids []string
+ scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
+ uid := strings.Split(line, ":")[9]
+ if uid == "" {
+ continue
+ }
+ parseduid := uid
+ if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
+ parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
}
+ parseduids = append(parseduids, parseduid)
}
- return strings.Join(parseduids, ",")
}
- return ""
+ return parseduids
}
-// GetPolicyJSON return the struct to show policy.json in json format
-func GetPolicyJSON(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) {
+var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"}
+
+func trustTypeDescription(trustType string) string {
+ trustDescription, exist := typeDescription[trustType]
+ if !exist {
+ logrus.Warnf("invalid trust type %s", trustType)
+ }
+ return trustDescription
+}
+
+// GetPolicy return the struct to show policy.json in json format and a map (reponame, ShowOutput) pair for image trust show command
+func GetPolicy(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, map[string]ShowOutput, error) {
registryConfigs, err := LoadAndMergeConfig(systemRegistriesDirPath)
if err != nil {
- return nil, err
+ return nil, nil, err
}
+ trustShowOutputMap := make(map[string]ShowOutput)
policyJSON := make(map[string]map[string]interface{})
if len(policyContentStruct.Default) > 0 {
policyJSON["* (default)"] = make(map[string]interface{})
policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type
+
+ var defaultPolicyStruct ShowOutput
+ defaultPolicyStruct.Repo = "default"
+ defaultPolicyStruct.Trusttype = trustTypeDescription(policyContentStruct.Default[0].Type)
+ trustShowOutputMap["* (default)"] = defaultPolicyStruct
}
for transname, transval := range policyContentStruct.Transports {
for repo, repoval := range transval {
+ tempTrustShowOutput := ShowOutput{
+ Repo: repo,
+ Trusttype: repoval[0].Type,
+ }
policyJSON[repo] = make(map[string]interface{})
policyJSON[repo]["type"] = repoval[0].Type
policyJSON[repo]["transport"] = transname
+ keyDataArr := []string{}
+ keyPathArr := []string{}
+ keyarr := []string{}
for _, repoele := range repoval {
- keyarr := []string{}
if len(repoele.KeyPath) > 0 {
keyarr = append(keyarr, repoele.KeyPath)
+ keyPathArr = append(keyPathArr, repoele.KeyPath)
}
if len(repoele.KeyData) > 0 {
keyarr = append(keyarr, string(repoele.KeyData))
+ keyDataArr = append(keyDataArr, string(repoele.KeyData))
}
- policyJSON[repo]["keys"] = keyarr
}
+ policyJSON[repo]["keys"] = keyarr
+ uids := append(getGPGIdFromKeyPath(keyPathArr), getGPGIdFromKeyData(keyDataArr)...)
+ tempTrustShowOutput.GPGid = strings.Join(uids, ",")
+
policyJSON[repo]["sigstore"] = ""
registryNamespace := HaveMatchRegistry(repo, registryConfigs)
if registryNamespace != nil {
policyJSON[repo]["sigstore"] = registryNamespace.SigStore
+ tempTrustShowOutput.Sigstore = registryNamespace.SigStore
}
+ trustShowOutputMap[repo] = tempTrustShowOutput
}
}
- return policyJSON, nil
+ return policyJSON, trustShowOutputMap, nil
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index a6f52cb3e..e0b94b011 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -314,9 +314,22 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) {
return storageOpts, volumePath, err
}
- storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf")
+ storageConf := StorageConfigFile()
if _, err := os.Stat(storageConf); err == nil {
+ defaultRootlessRunRoot := storageOpts.RunRoot
+ defaultRootlessGraphRoot := storageOpts.GraphRoot
+ storageOpts = storage.StoreOptions{}
storage.ReloadConfigurationFile(storageConf, &storageOpts)
+
+ // If the file did not specify a graphroot or runroot,
+ // set sane defaults so we don't try and use root-owned
+ // directories
+ if storageOpts.RunRoot == "" {
+ storageOpts.RunRoot = defaultRootlessRunRoot
+ }
+ if storageOpts.GraphRoot == "" {
+ storageOpts.GraphRoot = defaultRootlessGraphRoot
+ }
} else if os.IsNotExist(err) {
os.MkdirAll(filepath.Dir(storageConf), 0755)
file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
@@ -334,3 +347,11 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) {
}
return storageOpts, volumePath, nil
}
+
+// StorageConfigFile returns the path to the storage config file used
+func StorageConfigFile() string {
+ if rootless.IsRootless() {
+ return filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf")
+ }
+ return storage.DefaultConfigFile
+}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 07d981786..a01e3cc2b 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.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"
+ cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
)
@@ -68,7 +69,12 @@ func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) err
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- data, err := shared.GetCtrInspectInfo(ctr, inspectInfo)
+ artifact, err := getArtifact(ctr)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+
+ data, err := shared.GetCtrInspectInfo(ctr.Config(), inspectInfo, artifact)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -462,3 +468,81 @@ 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)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ config := ctr.Config()
+ b, err := json.Marshal(config)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to serialize container config")
+ }
+ return call.ReplyContainerConfig(string(b))
+}
+
+// ContainerArtifacts returns an untouched container's artifact in string format
+func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifactName string) error {
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ artifacts, err := ctr.GetArtifact(artifactName)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to get container artifacts")
+ }
+ b, err := json.Marshal(artifacts)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to serialize container artifacts")
+ }
+ return call.ReplyContainerArtifacts(string(b))
+}
+
+// ContainerInspectData returns the inspect data of a container in string format
+func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string) error {
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ data, err := ctr.Inspect(true)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to inspect container")
+ }
+ b, err := json.Marshal(data)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to serialize container inspect data")
+ }
+ return call.ReplyContainerInspectData(string(b))
+
+}
+
+// ContainerStateData returns a container's state data in string format
+func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) error {
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ data, err := ctr.ContainerState()
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to obtain container state")
+ }
+ b, err := json.Marshal(data)
+ if err != nil {
+ return call.ReplyErrorOccurred("unable to serialize container inspect data")
+ }
+ return call.ReplyContainerStateData(string(b))
+}
diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go
index 63bc93686..d72eaeb18 100644
--- a/pkg/varlinkapi/containers_create.go
+++ b/pkg/varlinkapi/containers_create.go
@@ -41,7 +41,9 @@ func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.C
return call.ReplyErrorOccurred(err.Error())
}
- options, err := createConfig.GetContainerCreateOptions(i.Runtime)
+ // TODO fix when doing remote client and dealing with the ability to create a container
+ // within a non-existing pod (i.e. --pod new:foobar)
+ options, err := createConfig.GetContainerCreateOptions(i.Runtime, nil)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index 5e4cb4ccb..744f031c0 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -41,18 +41,28 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
for _, image := range images {
labels, _ := image.Labels(getContext())
containers, _ := image.Containers()
+ repoDigests, err := image.RepoDigests()
+ if err != nil {
+ return err
+ }
+
size, _ := image.Size(getContext())
+ isParent, err := image.IsParent()
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
i := iopodman.ImageInList{
Id: image.ID(),
ParentId: image.Parent,
RepoTags: image.Names(),
- RepoDigests: image.RepoDigests(),
+ RepoDigests: repoDigests,
Created: image.Created().String(),
Size: int64(*size),
VirtualSize: image.VirtualSize,
Containers: int64(len(containers)),
Labels: labels,
+ IsParent: isParent,
}
imageList = append(imageList, i)
}
@@ -73,6 +83,10 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, name string) error {
if err != nil {
return err
}
+ repoDigests, err := newImage.RepoDigests()
+ if err != nil {
+ return err
+ }
size, err := newImage.Size(getContext())
if err != nil {
return err
@@ -82,7 +96,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, name string) error {
Id: newImage.ID(),
ParentId: newImage.Parent,
RepoTags: newImage.Names(),
- RepoDigests: newImage.RepoDigests(),
+ RepoDigests: repoDigests,
Created: newImage.Created().String(),
Size: int64(*size),
VirtualSize: newImage.VirtualSize,
@@ -611,3 +625,21 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.
}
return call.ReplyContainerRunlabel()
}
+
+// ImagesPrune ....
+func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall) error {
+ var (
+ pruned []string
+ )
+ pruneImages, err := i.Runtime.ImageRuntime().GetPruneImages()
+ if err != nil {
+ return err
+ }
+ for _, i := range pruneImages {
+ if err := i.Remove(true); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ pruned = append(pruned, i.ID())
+ }
+ return call.ReplyImagesPrune(pruned)
+}
diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go
index a29d22e7d..376502f21 100644
--- a/pkg/varlinkapi/system.go
+++ b/pkg/varlinkapi/system.go
@@ -16,11 +16,12 @@ func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error {
}
return call.ReplyGetVersion(iopodman.Version{
- Version: versionInfo.Version,
- Go_version: versionInfo.GoVersion,
- Git_commit: versionInfo.GitCommit,
- Built: versionInfo.Built,
- Os_arch: versionInfo.OsArch,
+ Remote_api_version: versionInfo.RemoteAPIVersion,
+ Version: versionInfo.Version,
+ Go_version: versionInfo.GoVersion,
+ Git_commit: versionInfo.GitCommit,
+ Built: versionInfo.Built,
+ Os_arch: versionInfo.OsArch,
})
}
@@ -102,6 +103,5 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error {
podmanInfo.Podman = pmaninfo
podmanInfo.Registries = registries
podmanInfo.Insecure_registries = insecureRegistries
-
return call.ReplyGetInfo(podmanInfo)
}