From 1dd7f13dfbc1dd377eabace0239b1c05cd60b144 Mon Sep 17 00:00:00 2001 From: baude Date: Thu, 25 Oct 2018 13:39:25 -0500 Subject: get user and group information using securejoin and runc's user library for the purposes of performance and security, we use securejoin to contstruct the root fs's path so that symlinks are what they appear to be and no pointing to something naughty. then instead of chrooting to parse /etc/passwd|/etc/group, we now use the runc user/group methods which saves us quite a bit of performance. Signed-off-by: baude --- pkg/lookup/lookup.go | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 pkg/lookup/lookup.go (limited to 'pkg') diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go new file mode 100644 index 000000000..b27e2a724 --- /dev/null +++ b/pkg/lookup/lookup.go @@ -0,0 +1,156 @@ +package lookup + +import ( + "github.com/cyphar/filepath-securejoin" + "github.com/opencontainers/runc/libcontainer/user" + "github.com/sirupsen/logrus" + "strconv" +) + +const ( + etcpasswd = "/etc/passwd" + etcgroup = "/etc/group" +) + +// Overrides allows you to override defaults in GetUserGroupInfo +type Overrides struct { + DefaultUser *user.ExecUser + ContainerEtcPasswdPath string + ContainerEtcGroupPath string +} + +// GetUserGroupInfo takes string forms of the the container's mount path and the container user and +// returns a ExecUser with uid, gid, sgids, and home. And override can be provided for defaults. +func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) { + var ( + passwdDest, groupDest string + defaultExecUser *user.ExecUser + err error + ) + passwdPath := etcpasswd + groupPath := etcgroup + + if override != nil { + // Check for an override /etc/passwd path + if override.ContainerEtcPasswdPath != "" { + passwdPath = override.ContainerEtcPasswdPath + } + // Check for an override for /etc/group path + if override.ContainerEtcGroupPath != "" { + groupPath = override.ContainerEtcGroupPath + } + } + + // Check for an override default user + if override != nil && override.DefaultUser != nil { + defaultExecUser = override.DefaultUser + } else { + // Define a default container user + //defaultExecUser = &user.ExecUser{ + // Uid: 0, + // Gid: 0, + // Home: "/", + defaultExecUser = nil + + } + + // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty + if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { + logrus.Debug(err) + return nil, err + } + if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { + logrus.Debug(err) + return nil, err + } + return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) +} + +// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc +// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found, +// or the given group data is nil, the id will be returned as-is provided it is in the legal range. +func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) { + var ( + groupDest string + err error + uintgids []uint32 + ) + + groupPath := etcgroup + if override != nil && override.ContainerEtcGroupPath != "" { + groupPath = override.ContainerEtcGroupPath + } + + if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { + logrus.Debug(err) + return nil, err + } + + gids, err := user.GetAdditionalGroupsPath(groups, groupDest) + if err != nil { + return nil, err + } + // For libpod, we want []uint32s + for _, gid := range gids { + uintgids = append(uintgids, uint32(gid)) + } + return uintgids, nil +} + +// GetUser takes a containermount path and user name or id and returns +// a matching User structure from /etc/passwd. If it cannot locate a user +// with the provided information, an ErrNoPasswdEntries is returned. +func GetUser(containerMount, userIDorName string) (*user.User, error) { + var inputIsName bool + uid, err := strconv.Atoi(userIDorName) + if err != nil { + inputIsName = true + } + passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd) + if err != nil { + return nil, err + } + users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool { + if inputIsName { + return u.Name == userIDorName + } + return u.Uid == uid + }) + if err != nil { + return nil, err + } + if len(users) > 0 { + return &users[0], nil + } + return nil, user.ErrNoPasswdEntries +} + +// GetGroup takes ac ontainermount path and a group name or id and returns +// a match Group struct from /etc/group. if it cannot locate a group, +// an ErrNoGroupEntries error is returned. +func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { + var inputIsName bool + gid, err := strconv.Atoi(groupIDorName) + if err != nil { + inputIsName = true + } + + groupDest, err := securejoin.SecureJoin(containerMount, etcgroup) + if err != nil { + return nil, err + } + + groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool { + if inputIsName { + return g.Name == groupIDorName + } + return g.Gid == gid + }) + if err != nil { + return nil, err + } + if len(groups) > 0 { + return &groups[0], nil + } + return nil, user.ErrNoGroupEntries +} -- cgit v1.2.3-54-g00ecf From b2fef1a8badb875339d6329c095842baee4b3a17 Mon Sep 17 00:00:00 2001 From: Anders F Björklund Date: Tue, 30 Oct 2018 23:55:48 +0100 Subject: Fix setting of version information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was setting the wrong variable (CamelCase) in the wrong module ("main", not "libpod")... Signed-off-by: Anders F Björklund --- Makefile | 3 ++- cmd/podman/info.go | 3 ++- cmd/podman/version.go | 2 +- libpod/version.go | 10 +++++----- pkg/varlinkapi/system.go | 13 ++++++++----- 5 files changed, 18 insertions(+), 13 deletions(-) (limited to 'pkg') diff --git a/Makefile b/Makefile index b3e2fcc26..d8a870fa2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,8 @@ PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/c COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}") BUILD_INFO ?= $(shell date +%s) -LDFLAGS_PODMAN ?= $(LDFLAGS) -X main.gitCommit=$(GIT_COMMIT) -X main.buildInfo=$(BUILD_INFO) +LIBPOD := ${PROJECT}/libpod +LDFLAGS_PODMAN ?= $(LDFLAGS) -X $(LIBPOD).gitCommit=$(GIT_COMMIT) -X $(LIBPOD).buildInfo=$(BUILD_INFO) ISODATE ?= $(shell date --iso-8601) LIBSECCOMP_COMMIT := release-2.3 diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 563e63ba3..c0639725e 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -81,6 +81,7 @@ func debugInfo(c *cli.Context) map[string]interface{} { info["compiler"] = runtime.Compiler info["go version"] = runtime.Version() info["podman version"] = c.App.Version - info["git commit"] = libpod.GitCommit + version, _ := libpod.GetVersion() + info["git commit"] = version.GitCommit return info } diff --git a/cmd/podman/version.go b/cmd/podman/version.go index f896229c4..d80f24a14 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -21,7 +21,7 @@ func versionCmd(c *cli.Context) error { fmt.Println("Git Commit: ", output.GitCommit) } // Prints out the build time in readable format - if libpod.BuildInfo != "" { + if output.Built != 0 { fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) } diff --git a/libpod/version.go b/libpod/version.go index 5e7cd83c9..966588ae9 100644 --- a/libpod/version.go +++ b/libpod/version.go @@ -11,10 +11,10 @@ import ( var ( // GitCommit is the commit that the binary is being built from. // It will be populated by the Makefile. - GitCommit string + gitCommit string // BuildInfo is the time at which the binary was built // It will be populated by the Makefile. - BuildInfo string + buildInfo string ) //Version is an output struct for varlink @@ -30,9 +30,9 @@ type Version struct { func GetVersion() (Version, error) { var err error var buildTime int64 - if BuildInfo != "" { + if buildInfo != "" { // Converts unix time from string to int64 - buildTime, err = strconv.ParseInt(BuildInfo, 10, 64) + buildTime, err = strconv.ParseInt(buildInfo, 10, 64) if err != nil { return Version{}, err @@ -41,7 +41,7 @@ func GetVersion() (Version, error) { return Version{ Version: podmanVersion.Version, GoVersion: runtime.Version(), - GitCommit: GitCommit, + GitCommit: gitCommit, Built: buildTime, OsArch: runtime.GOOS + "/" + runtime.GOARCH, }, nil diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 287f42209..a29d22e7d 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -34,6 +34,10 @@ func (i *LibpodAPI) Ping(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() + if err != nil { + return err + } var ( registries, insecureRegistries []string ) @@ -64,11 +68,10 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Host = infoHost store := info[1].Data pmaninfo := iopodman.InfoPodmanBinary{ - Compiler: goruntime.Compiler, - Go_version: goruntime.Version(), - // TODO : How are we going to get this here? - //Podman_version: - Git_commit: libpod.GitCommit, + Compiler: goruntime.Compiler, + Go_version: goruntime.Version(), + Podman_version: versionInfo.Version, + Git_commit: versionInfo.GitCommit, } graphStatus := iopodman.InfoGraphStatus{ -- cgit v1.2.3-54-g00ecf From 89e9067decc05aca92c47ab73af1f7ea95080d0e Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 31 Oct 2018 13:50:25 +0100 Subject: rootless: do not add an additional /run to runroot we are currently using something like /run/user/UID/run as runroot, as it is already done by Buildah. This ends up with /run/user/UID/run/runc for the runc directory. Change to drop the additional /run so that runc will use /run/user/UID/runc. Signed-off-by: Giuseppe Scrivano --- pkg/util/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 9107eec5c..69f49e72a 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -256,7 +256,7 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { if err != nil { return opts, err } - opts.RunRoot = filepath.Join(rootlessRuntime, "run") + opts.RunRoot = rootlessRuntime dataDir := os.Getenv("XDG_DATA_HOME") if dataDir == "" { -- cgit v1.2.3-54-g00ecf From ae68bec75cf59e8a530dbc55f320f7bb0be9a62b Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 6 Nov 2018 06:26:35 -0500 Subject: Don't fail if /etc/passwd or /etc/group does not exists Container images can be created without passwd or group file, currently if one of these containers gets run with a --user flag the container blows up complaining about t a missing /etc/passwd file. We just need to check if the error on read is ENOEXIST then allow the read to return, not fail. Signed-off-by: Daniel J Walsh --- libpod/container_internal.go | 4 ++-- pkg/lookup/lookup.go | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'pkg') diff --git a/libpod/container_internal.go b/libpod/container_internal.go index d928c4aed..558099e82 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/opencontainers/runc/libcontainer/user" "io" "io/ioutil" "os" @@ -25,6 +24,7 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/mount" + "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -1069,7 +1069,7 @@ func (c *Container) generatePasswd() (string, error) { } originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") orig, err := ioutil.ReadFile(originPasswdFile) - if err != nil { + if err != nil && !os.IsNotExist(err) { return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) } diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go index b27e2a724..a9d975b4b 100644 --- a/pkg/lookup/lookup.go +++ b/pkg/lookup/lookup.go @@ -1,10 +1,12 @@ package lookup import ( + "os" + "strconv" + "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" "github.com/sirupsen/logrus" - "strconv" ) const ( @@ -116,7 +118,7 @@ func GetUser(containerMount, userIDorName string) (*user.User, error) { } return u.Uid == uid }) - if err != nil { + if err != nil && !os.IsNotExist(err) { return nil, err } if len(users) > 0 { @@ -146,7 +148,7 @@ func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { } return g.Gid == gid }) - if err != nil { + if err != nil && !os.IsNotExist(err) { return nil, err } if len(groups) > 0 { -- cgit v1.2.3-54-g00ecf From c7926aa7cae3ec950fba290af662e693313854e0 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Tue, 30 Oct 2018 12:34:06 +0100 Subject: rootless: default to fuse-overlayfs when available If fuse-overlayfs is present, rootless containers default to use it. This can still be overriden either via the command line with --storage-driver or in the ~/.config/containers/storage.conf configuration file. Signed-off-by: Giuseppe Scrivano --- docs/podman.1.md | 2 +- pkg/util/utils.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'pkg') diff --git a/docs/podman.1.md b/docs/podman.1.md index 085af97ff..c66a9cf05 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -56,7 +56,7 @@ Path to the OCI compatible binary used to run containers **--storage-driver, -s**=**value** -Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf (`$HOME/.config/containers/storage.conf` in rootless mode), and is *vfs* for other users. The `STORAGE_DRIVER` environment variable overrides the default. The --storage-driver specified driver overrides all. +Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf (`$HOME/.config/containers/storage.conf` in rootless mode), and is *vfs* for non-root users when *fuse-overlayfs* is not available. The `STORAGE_DRIVER` environment variable overrides the default. The --storage-driver specified driver overrides all. Overriding this option will cause the *storage-opt* settings in /etc/containers/storage.conf to be ignored. The user must specify additional options via the `--storage-opt` flag. diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 69f49e72a..3b43489b2 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -3,6 +3,7 @@ package util import ( "fmt" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -273,7 +274,12 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { dataDir = filepath.Join(resolvedHome, ".local", "share") } opts.GraphRoot = filepath.Join(dataDir, "containers", "storage") - opts.GraphDriverName = "vfs" + if path, err := exec.LookPath("fuse-overlayfs"); err == nil { + opts.GraphDriverName = "overlay" + opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)} + } else { + opts.GraphDriverName = "vfs" + } return opts, nil } -- cgit v1.2.3-54-g00ecf From 542d8fe95f66c0a3aabb9d964c260a0b3b9aaae8 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 9 Nov 2018 11:22:44 -0500 Subject: Better document rootless containers Need to return an error pointing user in right direction if rootless podman fails, because of no /etc/subuid or /etc/subgid files. Also fix up man pages to better describe rootless podman. Signed-off-by: Daniel J Walsh --- docs/podman.1.md | 9 +++++---- pkg/rootless/rootless_linux.go | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'pkg') diff --git a/docs/podman.1.md b/docs/podman.1.md index c66a9cf05..b7433d850 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -192,7 +192,7 @@ the exit codes follow the `chroot` standard, see below: When Podman runs in rootless mode, the file `$HOME/.config/containers/storage.conf` is also loaded. ## Rootless mode -Podman can also be used as non-root user. When podman runs in rootless mode, an user namespace is automatically created. +Podman can also be used as non-root user. When podman runs in rootless mode, a user namespace is automatically created for the user, defined in /etc/subuid and /etc/subgid. Containers created by a non-root user are not visible to other users and are not seen or managed by podman running as root. @@ -209,13 +209,14 @@ Or just add the content manually. $ echo USERNAME:10000:65536 >> /etc/subuid $ echo USERNAME:10000:65536 >> /etc/subgid +See the `subuid(5)` and `subgid(5)` man pages for more information. + Images are pulled under `XDG_DATA_HOME` when specified, otherwise in the home directory of the user under `.local/share/containers/storage`. -Currently it is not possible to create a network device, so rootless containers need to run in the host network namespace. If a rootless container creates a network namespace, -then only the loopback device will be available. +Currently the slirp4netns package is required to be installed to create a network device, otherwise rootless containers need to run in the network namespace of the host. ## SEE ALSO -`containers-mounts.conf(5)`, `containers-registries.conf(5)`, `containers-storage.conf(5)`, `crio(8)`, `libpod.conf(5)`, `oci-hooks(5)`, `policy.json(5)` +`containers-mounts.conf(5)`, `containers-registries.conf(5)`, `containers-storage.conf(5)`, `crio(8)`, `libpod.conf(5)`, `oci-hooks(5)`, `policy.json(5)`, `subuid(5)`, `subgid(5)`, `slirp4netns(1)` ## HISTORY Dec 2016, Originally compiled by Dan Walsh diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 5c45f2694..ff8c8fe34 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -187,6 +187,9 @@ func BecomeRootInUserNS() (bool, int, error) { if username == "" { user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) if err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" { + if os.IsNotExist(err) { + return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files") + } return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set") } if err == nil { -- cgit v1.2.3-54-g00ecf From 9497b2254ce516d54649592c22a2338dcb2300eb Mon Sep 17 00:00:00 2001 From: Šimon Lukašík Date: Mon, 5 Nov 2018 18:59:57 +0100 Subject: Lint: InspectImage varlink api should return errors that occurred MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not just nil. Addressing: pkg/varlinkapi/images.go:273:15:warning: ineffectual assignment to err (ineffassign) Signed-off-by: Šimon Lukašík --- pkg/varlinkapi/images.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pkg') diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index d14c61c39..42e285b53 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -271,6 +271,9 @@ func (i *LibpodAPI) InspectImage(call iopodman.VarlinkCall, name string) error { return call.ReplyImageNotFound(name) } inspectInfo, err := newImage.Inspect(getContext()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } b, err := json.Marshal(inspectInfo) if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize")) -- cgit v1.2.3-54-g00ecf From 1e3ff6950269cb7fdb1c9ac161cf1aabf4933326 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 14 Nov 2018 10:13:47 +0100 Subject: rootless: call IsRootless just once we are calling this function several times, it is worth to store its result and re-use it. Signed-off-by: Giuseppe Scrivano --- pkg/rootless/rootless_linux.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index ff8c8fe34..85b0ef392 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -12,6 +12,7 @@ import ( "runtime" "strconv" "strings" + "sync" "syscall" "unsafe" @@ -33,9 +34,17 @@ func runInUser() error { return nil } +var ( + isRootlessOnce sync.Once + isRootless bool +) + // IsRootless tells us if we are running in rootless mode func IsRootless() bool { - return os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" + isRootlessOnce.Do(func() { + isRootless = os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" + }) + return isRootless } var ( -- cgit v1.2.3-54-g00ecf From 937eb8413c24392e6c2d5818bfa2ddddf8e84b95 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Tue, 13 Nov 2018 09:57:46 +0100 Subject: rootless: create storage.conf when it doesn't exist Signed-off-by: Giuseppe Scrivano --- pkg/util/utils.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'pkg') diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 3b43489b2..c5ba38b9f 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -9,6 +9,7 @@ import ( "strings" "syscall" + "github.com/BurntSushi/toml" "github.com/containers/image/types" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -296,6 +297,18 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") if _, err := os.Stat(storageConf); err == nil { storage.ReloadConfigurationFile(storageConf, &storageOpts) + } 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) + if err != nil { + return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) + } + + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(storageOpts); err != nil { + os.Remove(storageConf) + } } } return storageOpts, nil -- cgit v1.2.3-54-g00ecf From a0079d76572612e0f620cb4d3f481cef1e91e762 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Tue, 13 Nov 2018 10:00:41 +0100 Subject: registries: check user registries file only in rootless mode Signed-off-by: Giuseppe Scrivano --- pkg/registries/registries.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'pkg') diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 73aa93d68..c26f15cb6 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -38,8 +38,10 @@ func GetRegistries() ([]string, error) { func GetInsecureRegistries() ([]string, error) { registryConfigPath := "" - if _, err := os.Stat(userRegistriesFile); err == nil { - registryConfigPath = userRegistriesFile + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + registryConfigPath = userRegistriesFile + } } envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") -- cgit v1.2.3-54-g00ecf From 027d6ca6de3644414ee1f847d161184d027e4137 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Tue, 13 Nov 2018 10:11:39 +0100 Subject: rootless: create empty mounts.conf if it doesn't exist Signed-off-by: Giuseppe Scrivano --- pkg/secrets/secrets.go | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'pkg') diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go index 7208f53b7..242953609 100644 --- a/pkg/secrets/secrets.go +++ b/pkg/secrets/secrets.go @@ -149,6 +149,15 @@ func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPre mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...) if rootless.IsRootless() { mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...) + _, err := os.Stat(UserOverrideMountsFile) + if err != nil && os.IsNotExist(err) { + os.MkdirAll(filepath.Dir(UserOverrideMountsFile), 0755) + if f, err := os.Create(UserOverrideMountsFile); err != nil { + logrus.Warnf("could not create file %s: %v", UserOverrideMountsFile, err) + } else { + f.Close() + } + } } } else { mountFiles = append(mountFiles, mountFile) -- cgit v1.2.3-54-g00ecf From 690c52a113124efcedccb84e44198e7602f064ec Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 19 Nov 2018 13:20:56 -0600 Subject: Allow users to expose ports from the pod to the host we need to allow users to expose ports to the host for the purposes of networking, like a webserver. the port exposure must be done at the time the pod is created. strictly speaking, the port exposure occurs on the infra container. Signed-off-by: baude --- cmd/podman/pod_create.go | 59 ++++++++++++++++++ completions/bash/podman | 6 +- docs/podman-pod-create.1.md | 9 +++ libpod/options.go | 11 ++++ libpod/pod.go | 4 +- libpod/pod_easyjson.go | 128 ++++++++++++++++++++++++++++++++++++++ libpod/runtime_pod_infra_linux.go | 4 +- pkg/spec/createconfig.go | 1 - test/e2e/pod_create_test.go | 39 ++++++++++++ 9 files changed, 254 insertions(+), 7 deletions(-) (limited to 'pkg') diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 63fa6b294..a3364ac4b 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,11 +3,15 @@ package main import ( "fmt" "os" + "strconv" "strings" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{ Name: "pod-id-file", Usage: "Write the pod ID to the file", }, + cli.StringSliceFlag{ + Name: "publish, p", + Usage: "Publish a container's port, or a range of ports, to the host (default [])", + }, cli.StringFlag{ Name: "share", Usage: "A comma delimited list of kernel namespaces the pod will share", @@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error { defer podIdFile.Close() defer podIdFile.Sync() } + + if len(c.StringSlice("publish")) > 0 { + if !c.BoolT("infra") { + return errors.Errorf("you must have an infra container to publish port bindings to the host") + } + if rootless.IsRootless() { + return errors.Errorf("rootless networking does not allow port binding to the host") + } + } + if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } @@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error { options = append(options, nsOptions...) } + if len(c.StringSlice("publish")) > 0 { + portBindings, err := CreatePortBindings(c.StringSlice("publish")) + if err != nil { + return err + } + options = append(options, libpod.WithInfraContainerPorts(portBindings)) + + } // always have containers use pod cgroups // User Opt out is not yet supported options = append(options, libpod.WithPodCgroups()) @@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error { return nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + // The conversion from []string to natBindings is temporary while mheon reworks the port + // deduplication code. Eventually that step will not be required. + _, natBindings, err := nat.ParsePortSpecs(ports) + if err != nil { + return nil, err + } + for containerPb, hostPb := range natBindings { + var pm ocicni.PortMapping + pm.ContainerPort = int32(containerPb.Int()) + for _, i := range hostPb { + var hostPort int + var err error + pm.HostIP = i.HostIP + if i.HostPort == "" { + hostPort = containerPb.Int() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} diff --git a/completions/bash/podman b/completions/bash/podman index c029f893a..222511a3c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2178,12 +2178,14 @@ _podman_pod_create() { --cgroup-parent --infra-command --infra-image - --share - --podidfile --label-file --label -l --name + --podidfile + --publish + -p + --share " local boolean_options=" diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md index 673ad9a8c..a63b12d73 100644 --- a/docs/podman-pod-create.1.md +++ b/docs/podman-pod-create.1.md @@ -51,6 +51,15 @@ Assign a name to the pod Write the pod ID to the file +**-p**, **--publish**=[] + +Publish a port or range of ports from the pod to the host + +Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` +Both hostPort and containerPort can be specified as a range of ports. +When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` + **--share**="" A comma deliminated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. diff --git a/libpod/options.go b/libpod/options.go index 8d044313b..507847d65 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1295,3 +1295,14 @@ func WithInfraContainer() PodCreateOption { return nil } } + +// WithInfraContainerPorts tells the pod to add port bindings to the pause container +func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return ErrPodFinalized + } + pod.config.InfraContainer.PortBindings = bindings + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 8ac976f6a..07f41f5c6 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -4,6 +4,7 @@ import ( "time" "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" ) @@ -96,7 +97,8 @@ type PodContainerInfo struct { // InfraContainerConfig is the configuration for the pod's infra container type InfraContainerConfig struct { - HasInfraContainer bool `json:"makeInfraContainer"` + HasInfraContainer bool `json:"makeInfraContainer"` + PortBindings []ocicni.PortMapping `json:"infraPortBindings"` } // ID retrieves the pod's ID diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go index 6c1c939f3..8ea9a5e72 100644 --- a/libpod/pod_easyjson.go +++ b/libpod/pod_easyjson.go @@ -6,6 +6,7 @@ package libpod import ( json "encoding/json" + ocicni "github.com/cri-o/ocicni/pkg/ocicni" easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" @@ -721,6 +722,29 @@ func easyjsonBe091417DecodeGithubComContainersLibpodLibpod5(in *jlexer.Lexer, ou switch key { case "makeInfraContainer": out.HasInfraContainer = bool(in.Bool()) + case "infraPortBindings": + if in.IsNull() { + in.Skip() + out.PortBindings = nil + } else { + in.Delim('[') + if out.PortBindings == nil { + if !in.IsDelim(']') { + out.PortBindings = make([]ocicni.PortMapping, 0, 1) + } else { + out.PortBindings = []ocicni.PortMapping{} + } + } else { + out.PortBindings = (out.PortBindings)[:0] + } + for !in.IsDelim(']') { + var v6 ocicni.PortMapping + easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in, &v6) + out.PortBindings = append(out.PortBindings, v6) + in.WantComma() + } + in.Delim(']') + } default: in.SkipRecursive() } @@ -745,5 +769,109 @@ func easyjsonBe091417EncodeGithubComContainersLibpodLibpod5(out *jwriter.Writer, } out.Bool(bool(in.HasInfraContainer)) } + { + const prefix string = ",\"infraPortBindings\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + if in.PortBindings == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v7, v8 := range in.PortBindings { + if v7 > 0 { + out.RawByte(',') + } + easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out, v8) + } + out.RawByte(']') + } + } + out.RawByte('}') +} +func easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "hostPort": + out.HostPort = int32(in.Int32()) + case "containerPort": + out.ContainerPort = int32(in.Int32()) + case "protocol": + out.Protocol = string(in.String()) + case "hostIP": + out.HostIP = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out *jwriter.Writer, in ocicni.PortMapping) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"hostPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.HostPort)) + } + { + const prefix string = ",\"containerPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.ContainerPort)) + } + { + const prefix string = ",\"protocol\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Protocol)) + } + { + const prefix string = ",\"hostIP\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.HostIP)) + } out.RawByte('}') } diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index fea79e994..450a2fb32 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" ) @@ -50,9 +49,8 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID options = append(options, withIsInfra()) // Since user namespace sharing is not implemented, we only need to check if it's rootless - portMappings := make([]ocicni.PortMapping, 0) networks := make([]string, 0) - options = append(options, WithNetNS(portMappings, isRootless, networks)) + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks)) return r.newContainer(ctx, g.Config, options...) } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6ac9d82da..6a0642ee7 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib } options = append(options, runtime.WithPod(pod)) } - if len(c.PortBindings) > 0 { portBindings, err = c.CreatePortBindings() if err != nil { diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 51522ffd1..5abf9613b 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -80,4 +80,43 @@ var _ = Describe("Podman pod create", func() { check.WaitWithDefaultTimeout() Expect(len(check.OutputToStringArray())).To(Equal(0)) }) + + It("podman create pod without network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(1)) + }) + + It("podman create pod with network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(0)) + }) + + It("podman create pod with no infra but portbindings should fail", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--infra=false", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) }) -- cgit v1.2.3-54-g00ecf From 5438ec19181cc5855e6b54e152e0fe3161a75913 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Thu, 22 Nov 2018 21:49:30 -0500 Subject: Add history and namespaceoptions to image inspect Signed-off-by: Qi Wang --- libpod/image/image.go | 1 + pkg/inspect/inspect.go | 1 + 2 files changed, 2 insertions(+) (limited to 'pkg') diff --git a/libpod/image/image.go b/libpod/image/image.go index 7e520d97e..2d03786a9 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -869,6 +869,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { GraphDriver: driver, ManifestType: manifestType, User: ociv1Img.Config.User, + History: ociv1Img.History, } return data, nil } diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 62ba53147..5bdcf677f 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -126,6 +126,7 @@ type ImageData struct { Annotations map[string]string `json:"Annotations"` ManifestType string `json:"ManifestType"` User string `json:"User"` + History []v1.History `json:"History"` } // RootFS holds the root fs information of an image -- cgit v1.2.3-54-g00ecf From 4203df69aca13f14e43ad32a9b7ffb6cfb8c1016 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 21 Nov 2018 17:38:28 +0100 Subject: rootless: add new netmode "slirp4netns" so that inspect reports the correct network configuration. Closes: https://github.com/containers/libpod/issues/1453 Signed-off-by: Giuseppe Scrivano --- cmd/podman/common.go | 10 +++++++++- pkg/namespaces/namespaces.go | 7 ++++++- pkg/spec/spec.go | 3 +++ pkg/varlinkapi/containers_create.go | 7 ++++++- test/e2e/rootless_test.go | 8 ++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) (limited to 'pkg') diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f9e746b28..c4016698a 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" @@ -161,6 +162,13 @@ func getContext() context.Context { return context.TODO() } +func getDefaultNetwork() string { + if rootless.IsRootless() { + return "slirp4netns" + } + return "bridge" +} + // Common flags shared between commands var createFlags = []cli.Flag{ cli.StringSliceFlag{ @@ -372,7 +380,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{ Name: "net, network", Usage: "Connect a container to a network", - Value: "bridge", + Value: getDefaultNetwork(), }, cli.BoolFlag{ Name: "oom-kill-disable", diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index bee833fa9..832efd554 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -223,7 +223,12 @@ func (n NetworkMode) IsBridge() bool { return n == "bridge" } +// IsSlirp4netns indicates if we are running a rootless network stack +func (n NetworkMode) IsSlirp4netns() bool { + return n == "slirp4netns" +} + // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { - return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() + return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns() } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b1cca2c9e..05be00864 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -453,6 +453,9 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error { } else if IsPod(string(netMode)) { logrus.Debug("Using pod netmode, unless pod is not sharing") return nil + } else if netMode.IsSlirp4netns() { + logrus.Debug("Using slirp4netns netmode") + return nil } else if netMode.IsUserDefined() { logrus.Debug("Using user defined netmode") return nil diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index ca1a57048..f9a2db9c8 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/docker/docker/pkg/signal" @@ -126,7 +127,11 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru // NETWORK MODE networkMode := create.Net_mode if networkMode == "" { - networkMode = "bridge" + if rootless.IsRootless() { + networkMode = "slirp4netns" + } else { + networkMode = "bridge" + } } // WORKING DIR diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index 995744ae5..9f84d4c13 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -217,6 +217,14 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) + if len(args) == 0 { + cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l"}, 1000, 1000, env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) + data := cmd.InspectContainerToJSON() + Expect(data[0].HostConfig.NetworkMode).To(ContainSubstring("slirp4netns")) + } + if !canUseExec { Skip("ioctl(NS_GET_PARENT) not supported.") } -- cgit v1.2.3-54-g00ecf From 870eed9378c025f3684aa8baf3db6de969da3c5d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 21 Nov 2018 09:30:03 -0500 Subject: Use host's resolv.conf if no network namespace enabled My host system runs Fedora Silverblue 29 and I have NetworkManager's `dns=dnsmasq` setting enabled, so my `/etc/resolv.conf` only has `127.0.0.1`. I also run my development podman containers with `--net=host` for various reasons. If we have a host network namespace, there's no reason not to just use the host's nameserver configuration either. This fixes e.g. accessing content on a VPN, and is also faster since the container is using cached DNS. I know this doesn't solve the bigger picture issue of localhost-DNS conflicting with bridged networking, but that's far more involved, probably requiring a DNS proxy in the container. This patch makes my workflow a lot nicer and was easy to write. Signed-off-by: Colin Walters --- libpod/container_internal_linux.go | 5 +++-- pkg/resolvconf/resolvconf.go | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'pkg') diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index ffb82cc94..677645e7d 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -729,9 +729,10 @@ func (c *Container) generateResolvConf() (string, error) { return "", errors.Wrapf(err, "unable to read %s", resolvPath) } - // Process the file to remove localhost nameservers + // Ensure that the container's /etc/resolv.conf is compatible with its + // network configuration. // TODO: set ipv6 enable bool more sanely - resolv, err := resolvconf.FilterResolvDNS(contents, true) + resolv, err := resolvconf.FilterResolvDNS(contents, true, c.config.CreateNetNS) if err != nil { return "", errors.Wrapf(err, "error parsing host resolv.conf") } diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index fccd60093..e85bcb377 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -103,13 +103,21 @@ func GetLastModified() *File { } // FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: -// 1. It looks for localhost (127.*|::1) entries in the provided +// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided // resolv.conf, removing local nameserver entries, and, if the resulting // cleaned config has no defined nameservers left, adds default DNS entries // 2. Given the caller provides the enable/disable state of IPv6, the filter // code will remove all IPv6 nameservers if it is not enabled for containers // -func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) { +func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) { + // If we're using the host netns, we have nothing to do besides hash the file. + if !netnsEnabled { + hash, err := ioutils.HashData(bytes.NewReader(resolvConf)) + if err != nil { + return nil, err + } + return &File{Content: resolvConf, Hash: hash}, nil + } cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) // if IPv6 is not enabled, also clean out any IPv6 address nameserver if !ipv6Enabled { -- cgit v1.2.3-54-g00ecf From 95f22a2ca055d6dec0281cee109375dc4fd9b78b Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 26 Nov 2018 21:31:06 +0100 Subject: network: allow slirp4netns mode also for root containers Signed-off-by: Giuseppe Scrivano --- docs/podman-create.1.md | 3 ++- docs/podman-run.1.md | 3 ++- libpod/container.go | 3 +++ libpod/container_easyjson.go | 13 +++++++++++++ libpod/container_internal.go | 2 +- libpod/oci.go | 2 +- libpod/options.go | 4 +++- libpod/runtime_pod_infra_linux.go | 6 +++++- pkg/spec/createconfig.go | 4 ++-- 9 files changed, 32 insertions(+), 8 deletions(-) (limited to 'pkg') diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 68c00685b..474796a35 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -426,7 +426,8 @@ Set the Network mode for the container 'container:': reuse another container's network stack 'host': use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. '|': connect to a user-defined network - 'ns:' path to a network namespace to join + 'ns:': path to a network namespace to join + 'slirp4netns': use slirp4netns to create a user network stack. This is the default for rootless containers **--network-alias**=[] diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 912026a55..202091b07 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -408,7 +408,8 @@ Set the Network mode for the container: - `container:`: reuse another container's network stack - `host`: use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. - `|`: connect to a user-defined network -- `ns:` path to a network namespace to join +- `ns:`: path to a network namespace to join +- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers **--network-alias**=[] diff --git a/libpod/container.go b/libpod/container.go index 16f61d021..a8a58f4d8 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -9,6 +9,7 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -296,6 +297,8 @@ type ContainerConfig struct { HostAdd []string `json:"hostsAdd,omitempty"` // Network names (CNI) to add container to. Empty to use default network. Networks []string `json:"networks,omitempty"` + // Network mode specified for the default network. + NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` // Image Config diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index 041cc08ac..8bf5cb64f 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -8,6 +8,7 @@ import ( json "encoding/json" types "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/current" + namespaces "github.com/containers/libpod/pkg/namespaces" storage "github.com/containers/storage" idtools "github.com/containers/storage/pkg/idtools" ocicni "github.com/cri-o/ocicni/pkg/ocicni" @@ -1550,6 +1551,8 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou } in.Delim(']') } + case "networkMode": + out.NetMode = namespaces.NetworkMode(in.String()) case "userVolumes": if in.IsNull() { in.Skip() @@ -2177,6 +2180,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, out.RawByte(']') } } + if in.NetMode != "" { + const prefix string = ",\"networkMode\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.NetMode)) + } if len(in.UserVolumes) != 0 { const prefix string = ",\"userVolumes\":" if first { diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 051e0aeb7..e44ec76ec 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -586,7 +586,7 @@ func (c *Container) completeNetworkSetup() error { if err := c.syncContainer(); err != nil { return err } - if rootless.IsRootless() { + if c.config.NetMode == "slirp4netns" { return c.runtime.setupRootlessNetNS(c) } return c.runtime.setupNetNS(c) diff --git a/libpod/oci.go b/libpod/oci.go index a7aec06e5..e9cceda82 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -329,7 +329,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) } - if rootless.IsRootless() { + if ctr.config.NetMode.IsSlirp4netns() { ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() if err != nil { return errors.Wrapf(err, "failed to create rootless network sync pipe") diff --git a/libpod/options.go b/libpod/options.go index 507847d65..7f4e3ac6b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -7,6 +7,7 @@ import ( "regexp" "syscall" + "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" @@ -817,7 +818,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { // namespace with a minimal configuration. // An optional array of port mappings can be provided. // Conflicts with WithNetNSFrom(). -func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, networks []string) CtrCreateOption { +func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized @@ -831,6 +832,7 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo ctr.config.CreateNetNS = true ctr.config.PortMappings = portMappings ctr.config.Networks = networks + ctr.config.NetMode = namespaces.NetworkMode(netmode) return nil } diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 450a2fb32..8a5dbef56 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -50,7 +50,11 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID // Since user namespace sharing is not implemented, we only need to check if it's rootless networks := make([]string, 0) - options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks)) + netmode := "bridge" + if isRootless { + netmode = "slirp4netns" + } + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, netmode, networks)) return r.newContainer(ctx, g.Config, options...) } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6a0642ee7..a0fd40318 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -391,11 +391,11 @@ 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 := isRootless || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() + 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, networks)) + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } if c.PidMode.IsContainer() { -- cgit v1.2.3-54-g00ecf From 078cb630d3959200b4f9a14763714cf77258e8a2 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 21 Nov 2018 10:31:12 +0100 Subject: rootless: store only subset of storage.conf do not store the entire file but only the subset of what we have modified. Also, we were not writing the correct data. Since it is not trivial to serialize storage.conf correctly and all the various supported options, serialize only what we care about. Signed-off-by: Giuseppe Scrivano --- pkg/util/utils.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/util/utils.go b/pkg/util/utils.go index c5ba38b9f..de29bc5d8 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -284,6 +284,35 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { return opts, nil } +type tomlOptionsConfig struct { + MountProgram string `toml:"mount_program"` +} + +type tomlConfig struct { + Storage struct { + Driver string `toml:"driver"` + RunRoot string `toml:"runroot"` + GraphRoot string `toml:"graphroot"` + Options struct{ tomlOptionsConfig } `toml:"options"` + } `toml:"storage"` +} + +func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { + config := new(tomlConfig) + + config.Storage.Driver = storeOptions.GraphDriverName + config.Storage.RunRoot = storeOptions.RunRoot + config.Storage.GraphRoot = storeOptions.GraphRoot + for _, i := range storeOptions.GraphDriverOptions { + s := strings.Split(i, "=") + if s[0] == "overlay.mount_program" { + config.Storage.Options.MountProgram = s[1] + } + } + + return config +} + // GetDefaultStoreOptions returns the storage ops for containers func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageOpts := storage.DefaultStoreOptions @@ -304,9 +333,10 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) { return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) } + tomlConfiguration := getTomlStorage(&storageOpts) defer file.Close() enc := toml.NewEncoder(file) - if err := enc.Encode(storageOpts); err != nil { + if err := enc.Encode(tomlConfiguration); err != nil { os.Remove(storageConf) } } -- cgit v1.2.3-54-g00ecf From 562fa57dc9f497db772baa03bfa052082db68646 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Sun, 2 Dec 2018 15:21:35 -0500 Subject: Move rootless storage config into libpod Previous commits ensured that we would use database-configured paths if not explicitly overridden. However, our runtime generation did unconditionally override storage config, which made this useless. Move rootless storage configuration setup to libpod, and change storage setup so we only override if a setting is explicitly set, so we can still override what we want. Signed-off-by: Matthew Heon --- cmd/podman/create.go | 2 +- cmd/podman/libpodruntime/runtime.go | 34 +++++++++------------------- cmd/podman/run.go | 2 +- libpod/options.go | 24 ++++++++++++-------- libpod/runtime.go | 9 ++++++++ pkg/util/utils.go | 44 ++++++++++++++++++------------------- 6 files changed, 57 insertions(+), 58 deletions(-) (limited to 'pkg') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bcf830c7c..1ef9fa47a 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -66,7 +66,7 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index a4b3581be..13a821b9f 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -11,32 +11,18 @@ import ( // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetContainerRuntime generates a new libpod runtime configured by command line options for containers -func GetContainerRuntime(c *cli.Context) (*libpod.Runtime, error) { - mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) - if err != nil { - return nil, err - } - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - storageOpts.UIDMap = mappings.UIDMap - storageOpts.GIDMap = mappings.GIDMap - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetRuntime generates a new libpod runtime configured by command line options -func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) { + storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} + if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") { + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + if err != nil { + return nil, err + } + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + } + if c.GlobalIsSet("root") { storageOpts.GraphRoot = c.GlobalString("root") } diff --git a/cmd/podman/run.go b/cmd/podman/run.go index af6ced45d..a4b5c918e 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -44,7 +44,7 @@ func runCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/libpod/options.go b/libpod/options.go index 6783e2a39..661bd8d91 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -29,18 +29,18 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { return ErrRuntimeFinalized } - rt.config.StorageConfig.RunRoot = config.RunRoot if config.RunRoot != "" { + rt.config.StorageConfig.RunRoot = config.RunRoot rt.configuredFrom.storageRunRootSet = true } - rt.config.StorageConfig.GraphRoot = config.GraphRoot if config.GraphRoot != "" { + rt.config.StorageConfig.GraphRoot = config.GraphRoot rt.configuredFrom.storageGraphRootSet = true } - rt.config.StorageConfig.GraphDriverName = config.GraphDriverName if config.GraphDriverName != "" { + rt.config.StorageConfig.GraphDriverName = config.GraphDriverName rt.configuredFrom.storageGraphDriverSet = true } @@ -51,14 +51,20 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { rt.configuredFrom.libpodStaticDirSet = true } - rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) - copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + if config.GraphDriverOptions != nil { + rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) + copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + } - rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) - copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + if config.UIDMap != nil { + rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) + copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + } - rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) - copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + if config.GIDMap != nil { + rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) + copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + } return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index e01fa781b..6a5d2ad39 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -278,6 +278,15 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { deepcopier.Copy(defaultRuntimeConfig).To(runtime.config) runtime.config.TmpDir = tmpDir + if rootless.IsRootless() { + // If we're rootless, override the default storage config + storageConf, err := util.GetDefaultRootlessStoreOptions() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving rootless storage config") + } + runtime.config.StorageConfig = storageConf + } + configPath := ConfigPath foundConfig := true rootlessConfigPath := "" diff --git a/pkg/util/utils.go b/pkg/util/utils.go index de29bc5d8..78484eb78 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -313,33 +313,31 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { return config } -// GetDefaultStoreOptions returns the storage ops for containers -func GetDefaultStoreOptions() (storage.StoreOptions, error) { - storageOpts := storage.DefaultStoreOptions - if rootless.IsRootless() { - var err error - storageOpts, err = GetRootlessStorageOpts() +// GetDefaultStoreOptions returns the storage ops for containers. +func GetDefaultRootlessStoreOptions() (storage.StoreOptions, error) { + var err error + storageOpts, err := GetRootlessStorageOpts() + if err != nil { + return storageOpts, err + } + + storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + if _, err := os.Stat(storageConf); err == nil { + storage.ReloadConfigurationFile(storageConf, &storageOpts) + } 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) if err != nil { - return storageOpts, err + return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) } - storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") - if _, err := os.Stat(storageConf); err == nil { - storage.ReloadConfigurationFile(storageConf, &storageOpts) - } 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) - if err != nil { - return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) - } - - tomlConfiguration := getTomlStorage(&storageOpts) - defer file.Close() - enc := toml.NewEncoder(file) - if err := enc.Encode(tomlConfiguration); err != nil { - os.Remove(storageConf) - } + tomlConfiguration := getTomlStorage(&storageOpts) + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tomlConfiguration); err != nil { + os.Remove(storageConf) } } + return storageOpts, nil } -- cgit v1.2.3-54-g00ecf From b104a45f35a437593774f851b0a3b45fd692b263 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Sun, 2 Dec 2018 16:40:38 -0500 Subject: Fix gofmt and lint Signed-off-by: Matthew Heon --- libpod/state.go | 6 +++--- pkg/util/utils.go | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'pkg') diff --git a/libpod/state.go b/libpod/state.go index 99e2435a2..53b66cdb3 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -3,10 +3,10 @@ package libpod // DBConfig is a set of Libpod runtime configuration settings that are saved // in a State when it is first created, and can subsequently be retrieved. type DBConfig struct { - LibpodRoot string - LibpodTmp string + LibpodRoot string + LibpodTmp string StorageRoot string - StorageTmp string + StorageTmp string GraphDriver string } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 78484eb78..ed79c4b46 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -313,7 +313,8 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { return config } -// GetDefaultStoreOptions returns the storage ops for containers. +// GetDefaultRootlessStoreOptions returns the storage opts for rootless +// containers. func GetDefaultRootlessStoreOptions() (storage.StoreOptions, error) { var err error storageOpts, err := GetRootlessStorageOpts() -- cgit v1.2.3-54-g00ecf From 727b6a78ee5767646d74e04722ee1cb5f8d5594b Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 3 Dec 2018 12:14:52 +0100 Subject: rootless: raise error if newuidmap/newgidmap are not installed it was reported on IRC that Podman on Ubuntu failed as newuidmap/newgidmap were not installed by default. Raise an error if we are not allowing single mappings (used only by the tests suite) and any of the binaries is not present. Signed-off-by: Giuseppe Scrivano --- pkg/rootless/rootless_linux.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'pkg') diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 85b0ef392..07002da3f 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -74,7 +74,7 @@ func GetRootlessUID() int { func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { path, err := exec.LookPath(tool) if err != nil { - return err + return errors.Wrapf(err, "cannot find %s", tool) } appendTriplet := func(l []string, a, b, c int) []string { @@ -92,7 +92,11 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) Path: path, Args: args, } - return cmd.Run() + + if err := cmd.Run(); err != nil { + return errors.Wrapf(err, "cannot setup namespace using %s", tool) + } + return nil } // JoinNS re-exec podman in a new userNS and join the user namespace of the specified @@ -191,11 +195,13 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.Errorf("cannot re-exec process") } + allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != "" + var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) - if err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" { + if err != nil && !allowSingleIDMapping { if os.IsNotExist(err) { return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files") } @@ -206,7 +212,7 @@ func BecomeRootInUserNS() (bool, int, error) { } } mappings, err := idtools.NewIDMappings(username, username) - if os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" { + if !allowSingleIDMapping { if err != nil { return false, -1, err } @@ -236,7 +242,11 @@ func BecomeRootInUserNS() (bool, int, error) { uidsMapped := false if mappings != nil && uids != nil { - uidsMapped = tryMappingTool("newuidmap", pid, os.Getuid(), uids) == nil + err := tryMappingTool("newuidmap", pid, os.Getuid(), uids) + if !allowSingleIDMapping && err != nil { + return false, 0, err + } + uidsMapped = err == nil } if !uidsMapped { setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) @@ -254,7 +264,11 @@ func BecomeRootInUserNS() (bool, int, error) { gidsMapped := false if mappings != nil && gids != nil { - gidsMapped = tryMappingTool("newgidmap", pid, os.Getgid(), gids) == nil + err := tryMappingTool("newgidmap", pid, os.Getgid(), gids) + if !allowSingleIDMapping && err != nil { + return false, 0, err + } + gidsMapped = err == nil } if !gidsMapped { gidMap := fmt.Sprintf("/proc/%d/gid_map", pid) -- cgit v1.2.3-54-g00ecf From 5c02dda869390725a799339b094f548d327c9122 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 3 Dec 2018 09:15:29 -0600 Subject: Adding more varlink endpoints * runlabel * checkpoint * restore * container|image exists * mount * unmount Signed-off-by: baude --- API.md | 103 ++++++++++++++++++++++++++++++++++- cmd/podman/runlabel.go | 81 ++++----------------------- cmd/podman/shared/container.go | 81 +++++++++++++++++++++++++++ cmd/podman/varlink/io.podman.varlink | 52 ++++++++++++++++++ pkg/varlinkapi/containers.go | 49 +++++++++++++++++ pkg/varlinkapi/images.go | 45 +++++++++++++++ pkg/varlinkapi/mount.go | 49 +++++++++++++++++ 7 files changed, 389 insertions(+), 71 deletions(-) create mode 100644 pkg/varlinkapi/mount.go (limited to 'pkg') diff --git a/API.md b/API.md index 34d401aca..4d48e8245 100755 --- a/API.md +++ b/API.md @@ -9,6 +9,14 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) +[func ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) string](#ContainerCheckpoint) + +[func ContainerExists(name: string) int](#ContainerExists) + +[func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) + +[func ContainerRunlabel(runlabel: Runlabel) ](#ContainerRunlabel) + [func CreateContainer(create: Create) string](#CreateContainer) [func CreateImage() NotImplemented](#CreateImage) @@ -43,6 +51,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func HistoryImage(name: string) ImageHistory](#HistoryImage) +[func ImageExists(name: string) int](#ImageExists) + [func ImportImage(source: string, reference: string, message: string, changes: []string) string](#ImportImage) [func InspectContainer(name: string) string](#InspectContainer) @@ -57,6 +67,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListContainerChanges(name: string) ContainerChanges](#ListContainerChanges) +[func ListContainerMounts() []string](#ListContainerMounts) + +[func ListContainerPorts(name: string) NotImplemented](#ListContainerPorts) + [func ListContainerProcesses(name: string, opts: []string) []string](#ListContainerProcesses) [func ListContainers() ListContainerData](#ListContainers) @@ -65,6 +79,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListPods() ListPodData](#ListPods) +[func MountContainer(name: string) string](#MountContainer) + [func PauseContainer(name: string) string](#PauseContainer) [func PausePod(name: string) string](#PausePod) @@ -103,6 +119,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func TopPod() NotImplemented](#TopPod) +[func UnmountContainer(name: string, force: bool) ](#UnmountContainer) + [func UnpauseContainer(name: string) string](#UnpauseContainer) [func UnpausePod(name: string) string](#UnpausePod) @@ -165,6 +183,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type PodmanInfo](#PodmanInfo) +[type Runlabel](#Runlabel) + [type Sockets](#Sockets) [type StringResponse](#StringResponse) @@ -211,6 +231,31 @@ attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOL container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, the resulting image's ID will be returned as a string. +### func ContainerCheckpoint +
+ +method ContainerCheckpoint(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), leaveRunning: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +ID. On successful checkpoint, the id of the checkpointed container is returned. +### func ContainerExists +
+ +method ContainerExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ContainerExists takes a full or partial container ID or name and returns an int as to +whether the container exists in local storage. A result of 0 means the container does +exists; whereas a result of 1 means it could not be found. +### func ContainerRestore +
+ +method ContainerRestore(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerRestore restores a container that has been checkpointed. The container to be restored can +be identified by its name or full/partial container ID. A successful restore will result in the return +of the container's ID. +### func ContainerRunlabel +
+ +method ContainerRunlabel(runlabel: [Runlabel](#Runlabel))
+ContainerRunlabel runs executes a command as described by a given container image label. ### func CreateContainer
@@ -403,6 +448,13 @@ method HistoryImage(name: [string](https://godoc.org/builtin#string)) [ImageHist HistoryImage takes the name or ID of an image and returns information about its history and layers. The returned history is in the form of an array of ImageHistory structures. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error is returned. +### func ImageExists +
+ +method ImageExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ImageExists talks a full or partial image ID or name and returns an int as to whether +the image exists in local storage. An int result of 0 means the image does exist in +local storage; whereas 1 indicates the image does not exists in local storage. ### func ImportImage
@@ -453,6 +505,17 @@ See also [StopPod](StopPod). method ListContainerChanges(name: [string](https://godoc.org/builtin#string)) [ContainerChanges](#ContainerChanges)
ListContainerChanges takes a name or ID of a container and returns changes between the container and its base image. It returns a struct of changed, deleted, and added path names. +### func ListContainerMounts +
+ +method ListContainerMounts() [[]string](#[]string)
+ListContainerMounts gathers all the mounted container mount points and returns them as an array +of strings +### func ListContainerPorts +
+ +method ListContainerPorts(name: [string](https://godoc.org/builtin#string)) [NotImplemented](#NotImplemented)
+This function is not implemented yet. ### func ListContainerProcesses
@@ -491,6 +554,12 @@ an image currently in storage. See also [InspectImage](InspectImage). method ListPods() [ListPodData](#ListPodData)
ListPods returns a list of pods in no particular order. They are returned as an array of ListPodData structs. See also [GetPod](#GetPod). +### func MountContainer +
+ +method MountContainer(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)
+MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +mount is returned as a string. ### func PauseContainer
@@ -696,6 +765,11 @@ be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, method TopPod() [NotImplemented](#NotImplemented)
This method has not been implemented yet. +### func UnmountContainer +
+ +method UnmountContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool))
+UnmountContainer umounts a container by its name or full/partial container ID. ### func UnpauseContainer
@@ -1293,6 +1367,33 @@ insecure_registries [[]string](#[]string) store [InfoStore](#InfoStore) podman [InfoPodmanBinary](#InfoPodmanBinary) +### type Runlabel + +Runlabel describes the required input for container runlabel + +image [string](https://godoc.org/builtin#string) + +authfile [string](https://godoc.org/builtin#string) + +certDir [string](https://godoc.org/builtin#string) + +creds [string](https://godoc.org/builtin#string) + +display [bool](https://godoc.org/builtin#bool) + +name [string](https://godoc.org/builtin#string) + +pull [bool](https://godoc.org/builtin#bool) + +signaturePolicyPath [string](https://godoc.org/builtin#string) + +tlsVerify [bool](https://godoc.org/builtin#bool) + +label [string](https://godoc.org/builtin#string) + +extraArgs [[]string](#[]string) + +opts [map[string]](#map[string]) ### type Sockets Sockets describes sockets location for a container @@ -1336,7 +1437,7 @@ ImageNotFound means the image could not be found by the provided name or ID in l NoContainerRunning means none of the containers requested are running in a command that requires a running container. ### type NoContainersInPod -NoContainersInPod means a pod has no containers on which to perform operation. It contains +NoContainersInPod means a pod has no containers on which to perform the operation. It contains the pod ID. ### type PodContainerError diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index e1dee1fb2..b0d87d0d9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -6,11 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +92,7 @@ func runlabelCmd(c *cli.Context) error { imageName string stdErr, stdOut io.Writer stdIn io.Reader - newImage *image.Image + extraArgs []string ) // Evil images could trick into recursively executing the runlabel @@ -124,6 +122,9 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("the display and quiet flags cannot be used together.") } + if len(args) > 2 { + extraArgs = args[2:] + } pull := c.Bool("pull") label := args[0] @@ -151,75 +152,24 @@ func runlabelCmd(c *cli.Context) error { stdIn = nil } - if pull { - var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - } - authfile := getAuthFile(c.String("authfile")) - - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false) - } else { - newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) - } - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: c.String("cert-dir"), + DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), } - runLabel, err := newImage.GetLabel(ctx, label) + authfile := getAuthFile(c.String("authfile")) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) if err != nil { return err } - - // If no label to execute, we return if runLabel == "" { return nil } - // The user provided extra arguments that need to be tacked onto the label's command - if len(args) > 2 { - runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " ")) - } - - cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name")) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) if err != nil { - return errors.Wrapf(err, "unable to generate command") - } - env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts) - env = append(env, "PODMAN_RUNLABEL_NESTED=1") - - envmap := envSliceToMap(env) - - envmapper := func(k string) string { - switch k { - case "OPT1": - return envmap["OPT1"] - case "OPT2": - return envmap["OPT2"] - case "OPT3": - return envmap["OPT3"] - } - return "" + return err } - - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") - if !c.Bool("quiet") { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) if c.Bool("display") { @@ -228,12 +178,3 @@ func runlabelCmd(c *cli.Context) error { } return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } - -func envSliceToMap(env []string) map[string]string { - m := make(map[string]string) - for _, i := range env { - split := strings.Split(i, "=") - m[split[0]] = strings.Join(split[1:], " ") - } - return m -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4404268d4..d0e892961 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -1,10 +1,15 @@ package shared import ( + "context" "encoding/json" "fmt" + "github.com/containers/image/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "io" "os" "path/filepath" "regexp" @@ -589,3 +594,79 @@ func portsToString(ports []ocicni.PortMapping) string { } return strings.Join(portDisplay, ", ") } + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// contruction of the runlabel output and environment variables +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, false) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // The user provided extra arguments that need to be tacked onto the label's command + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd = strings.Split(newS, " ") + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4a1854c..b081b60a3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -371,6 +371,22 @@ type PodContainerErrorData ( reason: string ) +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + certDir: string, + creds: string, + display: bool, + name: string, + pull: bool, + signaturePolicyPath: string, + tlsVerify: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + # Ping provides a response for developers to ensure their varlink setup is working. # #### Example # ~~~ @@ -804,6 +820,42 @@ method TopPod() -> (notimplemented: NotImplemented) # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +method ImageExists(name: string) -> (exists: int) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +method ListContainerMounts() -> (mounts: []string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +method UnmountContainer(name: string, force: bool) -> () + +# This function is not implemented yet. +method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index f517e9b6e..07d981786 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -278,6 +278,18 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim return call.ReplyRestartContainer(ctr.ID()) } +// 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 { + return call.ReplyContainerExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerExists(0) +} + // KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1 // for the signal arg. func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { @@ -413,3 +425,40 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } return call.ReplyGetAttachSockets(s) } + +// ContainerCheckpoint ... +func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + KeepRunning: leaveRunning, + } + if err := ctr.Checkpoint(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerCheckpoint(ctr.ID()) +} + +// ContainerRestore ... +func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + } + if err := ctr.Restore(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRestore(ctr.ID()) +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 42e285b53..6d3f19422 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/containers/libpod/cmd/podman/shared" "io" + "os" "path/filepath" "strings" "time" @@ -19,6 +21,7 @@ import ( "github.com/containers/libpod/libpod/image" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/docker/go-units" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" @@ -500,3 +503,45 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { } return call.ReplyPullImage(newImage.ID()) } + +// ImageExists returns bool as to whether the input image exists in local storage +func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if errors.Cause(err) == libpod.ErrNoSuchImage { + return call.ReplyImageExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyImageExists(0) +} + +// ContainerRunlabel ... +func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { + ctx := getContext() + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: input.CertDir, + DockerInsecureSkipTLSVerify: !input.TlsVerify, + } + + stdErr := os.Stderr + stdOut := os.Stdout + stdIn := os.Stdin + + runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil) + if err != nil { + return err + } + if runLabel == "" { + return nil + } + + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs) + if err != nil { + return err + } + if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRunlabel() +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go new file mode 100644 index 000000000..84e6b2709 --- /dev/null +++ b/pkg/varlinkapi/mount.go @@ -0,0 +1,49 @@ +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// ListContainerMounts ... +func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { + var mounts []string + allContainers, err := i.Runtime.GetAllContainers() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, container := range allContainers { + mounted, mountPoint, err := container.Mounted() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if mounted { + mounts = append(mounts, mountPoint) + } + } + return call.ReplyListContainerMounts(mounts) +} + +// MountContainer ... +func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + path, err := container.Mount() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyMountContainer(path) +} + +// UnmountContainer ... +func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := container.Unmount(force); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyUnmountContainer() +} -- cgit v1.2.3-54-g00ecf From 795fbba7695b03736acaf9abe75922404f5eea44 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 3 Dec 2018 15:38:35 -0500 Subject: Revert changes to GetDefaultStoreOptions We don't need this for anything more than rootless work in Libpod now, but Buildah still uses it as it was originally written, so leave it intact as part of our API. Signed-off-by: Matthew Heon --- libpod/runtime.go | 2 +- pkg/util/utils.go | 45 +++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 23 deletions(-) (limited to 'pkg') diff --git a/libpod/runtime.go b/libpod/runtime.go index 8b5bc32b4..e69b63a24 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -280,7 +280,7 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if rootless.IsRootless() { // If we're rootless, override the default storage config - storageConf, err := util.GetDefaultRootlessStoreOptions() + storageConf, err := util.GetDefaultStoreOptions() if err != nil { return nil, errors.Wrapf(err, "error retrieving rootless storage config") } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index ed79c4b46..e483253a4 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -313,32 +313,33 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { return config } -// GetDefaultRootlessStoreOptions returns the storage opts for rootless -// containers. -func GetDefaultRootlessStoreOptions() (storage.StoreOptions, error) { - var err error - storageOpts, err := GetRootlessStorageOpts() - if err != nil { - return storageOpts, err - } - - storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") - if _, err := os.Stat(storageConf); err == nil { - storage.ReloadConfigurationFile(storageConf, &storageOpts) - } 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) +// GetDefaultStoreOptions returns the default storage options for containers. +func GetDefaultStoreOptions() (storage.StoreOptions, error) { + storageOpts := storage.DefaultStoreOptions + if rootless.IsRootless() { + var err error + storageOpts, err = GetRootlessStorageOpts() if err != nil { - return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) + return storageOpts, err } - tomlConfiguration := getTomlStorage(&storageOpts) - defer file.Close() - enc := toml.NewEncoder(file) - if err := enc.Encode(tomlConfiguration); err != nil { - os.Remove(storageConf) + storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + if _, err := os.Stat(storageConf); err == nil { + storage.ReloadConfigurationFile(storageConf, &storageOpts) + } 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) + if err != nil { + return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) + } + + tomlConfiguration := getTomlStorage(&storageOpts) + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tomlConfiguration); err != nil { + os.Remove(storageConf) + } } } - return storageOpts, nil } -- cgit v1.2.3-54-g00ecf From 9c359a31d542074ff686a2f9ad29deee73e92d79 Mon Sep 17 00:00:00 2001 From: baude Date: Fri, 30 Nov 2018 10:23:28 -0600 Subject: create pod on the fly when a user specifies --pod to podman create|run, we should create that pod automatically. the port bindings from the container are then inherited by the infra container. this signicantly improves the workflow of running containers inside pods with podman. the user is still encouraged to use podman pod create to have more granular control of the pod create options. Signed-off-by: baude --- cmd/podman/create.go | 65 +++++++++++++++++++++++++++++++++++++----------- docs/podman-create.1.md | 3 ++- docs/podman-run.1.md | 3 ++- pkg/spec/createconfig.go | 7 +++++- test/e2e/create_test.go | 11 ++++++++ test/e2e/run_test.go | 11 ++++++++ 6 files changed, 82 insertions(+), 18 deletions(-) (limited to 'pkg') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bcf830c7c..c40650f83 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" @@ -375,8 +376,8 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string) (map[string]string, error) { - pod, err := runtime.LookupPod(c.String("pod")) +func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { + pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err } @@ -409,6 +410,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 blkioWeight uint16 + namespaces map[string]string ) idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) if err != nil { @@ -492,12 +494,21 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) + if err != nil { + return nil, err + } + } + // Kernel Namespaces // TODO Fix handling of namespace from pod // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated - namespaces := map[string]string{ + namespaces = map[string]string{ "pid": c.String("pid"), "net": c.String("net"), "ipc": c.String("ipc"), @@ -505,8 +516,41 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim "uts": c.String("uts"), } + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } if c.IsSet("pod") { - namespaces, err = configurePod(c, runtime, namespaces) + if strings.HasPrefix(originalPodName, "new:") { + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, err = configurePod(c, runtime, namespaces, podName) if err != nil { return nil, err } @@ -535,7 +579,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Make sure if network is set to container namespace, port binding is not also being asked for netMode := ns.NetworkMode(namespaces["net"]) if netMode.IsContainer() { - if len(c.StringSlice("publish")) > 0 || c.Bool("publish-all") { + if len(portBindings) > 0 { return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") } } @@ -644,15 +688,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") } - // EXPOSED PORTS - var portBindings map[nat.Port][]nat.PortBinding - if data != nil { - portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) - if err != nil { - return nil, err - } - } - // SHM Size shmSize, err := units.FromHumanSize(c.String("shm-size")) if err != nil { @@ -746,7 +781,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim NetMode: netMode, UtsMode: utsMode, PidMode: pidMode, - Pod: c.String("pod"), + Pod: podName, Privileged: c.Bool("privileged"), Publish: c.StringSlice("publish"), PublishAll: c.Bool("publish-all"), diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 6fbdd0d03..f1409a554 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -455,7 +455,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index a6761a393..5917f6f7a 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -439,7 +439,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index a0fd40318..25f8cd7a1 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -496,8 +496,13 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib // CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { + return NatToOCIPortBindings(c.PortBindings) +} + +// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice +func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { var portBindings []ocicni.PortMapping - for containerPb, hostPb := range c.PortBindings { + for containerPb, hostPb := range ports { var pm ocicni.PortMapping pm.ContainerPort = int32(containerPb.Int()) for _, i := range hostPb { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 366a58f02..684a7cd88 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -189,4 +189,15 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test rw,nosuid,nodev,noexec,relatime - tmpfs")) }) + + It("podman create --pod automatically", func() { + session := podmanTest.Podman([]string{"create", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 38504828b..aaee7fa53 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -628,4 +628,15 @@ USER mail` isSharedOnly := !strings.Contains(shared[0], "shared,") Expect(isSharedOnly).Should(BeTrue()) }) + + It("podman run --pod automatically", func() { + session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) + }) }) -- cgit v1.2.3-54-g00ecf From 39df2093e89a2816f317b72a73106166031045e6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 4 Dec 2018 12:50:59 -0800 Subject: pkg/lookup: Return ID-only pointers on ErrNo*Entries Callers that only care about the IDs should try to convert the identifier to an integer before calling the Get* functions, so they can save the cost of hitting the filesystem and maybe or maybe not finding the other fields (User.Name, etc.). But callers that *want* the other fields but only actually need the ID can, with this commit, just call the Get* function and ignore ErrNo*Entries responses: user, err := lookup.GetUser(mount, userIDorName) if err != nil && err != ErrNoPasswdEntries { return err } Previously, they'd have to perform their own integer-conversion attempt in Get* error handling, with logic like: user, err := lookup.GetUser(mount, userIDorName) if err == ErrNoPasswdEntries { uuid, err := strconv.ParseUint(userIDorName, 10, 32) if err == nil { user.Uid = int(uuid) } } else if err != nil { return err } Signed-off-by: W. Trevor King --- pkg/lookup/lookup.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'pkg') diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go index a9d975b4b..70b97144f 100644 --- a/pkg/lookup/lookup.go +++ b/pkg/lookup/lookup.go @@ -99,9 +99,11 @@ func GetContainerGroups(groups []string, containerMount string, override *Overri return uintgids, nil } -// GetUser takes a containermount path and user name or id and returns +// GetUser takes a containermount path and user name or ID and returns // a matching User structure from /etc/passwd. If it cannot locate a user // with the provided information, an ErrNoPasswdEntries is returned. +// When the provided user name was an ID, a User structure with Uid +// set is returned along with ErrNoPasswdEntries. func GetUser(containerMount, userIDorName string) (*user.User, error) { var inputIsName bool uid, err := strconv.Atoi(userIDorName) @@ -124,12 +126,17 @@ func GetUser(containerMount, userIDorName string) (*user.User, error) { if len(users) > 0 { return &users[0], nil } + if !inputIsName { + return &user.User{Uid: uid}, user.ErrNoPasswdEntries + } return nil, user.ErrNoPasswdEntries } -// GetGroup takes ac ontainermount path and a group name or id and returns -// a match Group struct from /etc/group. if it cannot locate a group, -// an ErrNoGroupEntries error is returned. +// GetGroup takes a containermount path and a group name or ID and returns +// a match Group struct from /etc/group. If it cannot locate a group, +// an ErrNoGroupEntries error is returned. When the provided group name +// was an ID, a Group structure with Gid set is returned along with +// ErrNoGroupEntries. func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { var inputIsName bool gid, err := strconv.Atoi(groupIDorName) @@ -154,5 +161,8 @@ func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { if len(groups) > 0 { return &groups[0], nil } + if !inputIsName { + return &user.Group{Gid: gid}, user.ErrNoGroupEntries + } return nil, user.ErrNoGroupEntries } -- cgit v1.2.3-54-g00ecf