summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2017-12-18 12:05:06 -0500
committerAtomic Bot <atomic-devel@projectatomic.io>2018-01-03 12:38:18 +0000
commit00d38cb37958f3c636aa5837b8f01dfad891a0b5 (patch)
treee0eb9039266a725f5315ac05f3909f7a6a4a309e
parent8aeb38e4a718925a78606b8aa014bce6b4a4054c (diff)
downloadpodman-00d38cb37958f3c636aa5837b8f01dfad891a0b5.tar.gz
podman-00d38cb37958f3c636aa5837b8f01dfad891a0b5.tar.bz2
podman-00d38cb37958f3c636aa5837b8f01dfad891a0b5.zip
podman create/run need to load information from the image
We should be pulling information out of the image to set the defaults to use when setting up the container. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #110 Approved by: mheon
-rw-r--r--cmd/podman/create.go251
-rw-r--r--cmd/podman/inspect.go3
-rw-r--r--cmd/podman/parse.go73
-rw-r--r--cmd/podman/run.go59
-rw-r--r--cmd/podman/spec.go3
-rw-r--r--libpod/container.go23
-rw-r--r--libpod/options.go13
-rw-r--r--libpod/runtime_ctr.go4
-rw-r--r--libpod/storage.go4
-rw-r--r--pkg/chrootuser/user.go71
-rw-r--r--pkg/chrootuser/user_basic.go19
-rw-r--r--pkg/chrootuser/user_linux.go235
12 files changed, 553 insertions, 205 deletions
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 8b64a1cb0..182eb1e56 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/signal"
+ "github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
@@ -80,10 +81,11 @@ type createConfig struct {
DNSServers []string //dns
Entrypoint string //entrypoint
Env map[string]string //env
- Expose []string //expose
- GroupAdd []uint32 // group-add
- Hostname string //hostname
+ ExposedPorts map[nat.Port]struct{}
+ GroupAdd []uint32 // group-add
+ Hostname string //hostname
Image string
+ ImageID string
Interactive bool //interactive
IpcMode container.IpcMode //ipc
IP6Address string //ipv6
@@ -99,7 +101,8 @@ type createConfig struct {
NetworkAlias []string //network-alias
PidMode container.PidMode //pid
NsUser string
- Pod string //pod
+ Pod string //pod
+ PortBindings nat.PortMap
Privileged bool //privileged
Publish []string //publish
PublishAll bool //publish-all
@@ -115,8 +118,7 @@ type createConfig struct {
Sysctl map[string]string //sysctl
Tmpfs []string // tmpfs
Tty bool //tty
- User uint32 //user
- Group uint32 // group
+ User string //user
UtsMode container.UTSMode //uts
Volumes []string //volume
WorkDir string //workdir
@@ -148,7 +150,6 @@ var createCommand = cli.Command{
func createCmd(c *cli.Context) error {
// TODO should allow user to create based off a directory on the host not just image
// Need CLI support for this
- var imageName string
if err := validateFlags(c, createFlags); err != nil {
return err
}
@@ -164,54 +165,19 @@ func createCmd(c *cli.Context) error {
return err
}
- // Deal with the image after all the args have been checked
- createImage := runtime.NewImage(createConfig.Image)
- createImage.LocalName, _ = createImage.GetLocalImageName()
- if createImage.LocalName == "" {
- // The image wasnt found by the user input'd name or its fqname
- // Pull the image
- var writer io.Writer
- if !createConfig.Quiet {
- writer = os.Stdout
- }
- createImage.Pull(writer)
- }
-
runtimeSpec, err := createConfigToOCISpec(createConfig)
if err != nil {
return err
}
- if createImage.LocalName != "" {
- nameIsID, err := runtime.IsImageID(createImage.LocalName)
- if err != nil {
- return err
- }
- if nameIsID {
- // If the input from the user is an ID, then we need to get the image
- // name for cstorage
- createImage.LocalName, err = createImage.GetNameByID()
- if err != nil {
- return err
- }
- }
- imageName = createImage.LocalName
- } else {
- imageName, err = createImage.GetFQName()
- }
- if err != nil {
- return err
- }
- imageID, err := createImage.GetImageID()
- if err != nil {
- return err
- }
options, err := createConfig.GetContainerCreateOptions()
if err != nil {
return errors.Wrapf(err, "unable to parse new container options")
}
// Gather up the options for NewContainer which consist of With... funcs
- options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
+ options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
+ options = append(options, libpod.WithLabels(createConfig.Labels))
+ options = append(options, libpod.WithUser(createConfig.User))
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
ctr, err := runtime.NewContainer(runtimeSpec, options...)
if err != nil {
@@ -300,13 +266,101 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error {
return err
}
+func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
+ // TODO Handle exposed ports from image
+ // Currently ignoring imageExposedPorts
+
+ ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish"))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, e := range c.StringSlice("expose") {
+ // Merge in exposed ports to the map of published ports
+ if strings.Contains(e, ":") {
+ return nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
+ }
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ proto, port := nat.SplitProtoPort(e)
+ //parse the start and end port and create a sequence of ports to expose
+ //if expose a port, the start and end port are the same
+ start, end, err := nat.ParsePortRange(port)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
+ }
+ for i := start; i <= end; i++ {
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, exists := ports[p]; !exists {
+ ports[p] = struct{}{}
+ }
+ }
+ }
+ return ports, portBindings, nil
+}
+
+// imageData pulls down the image if not stored locally and extracts the
+// default container runtime data out of it. imageData returns the data
+// to the caller. Example Data: Entrypoint, Env, WorkingDir, Labels ...
+func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, string, *libpod.ImageData, error) {
+ var err error
+ // Deal with the image after all the args have been checked
+ createImage := runtime.NewImage(image)
+ createImage.LocalName, _ = createImage.GetLocalImageName()
+ if createImage.LocalName == "" {
+ // The image wasnt found by the user input'd name or its fqname
+ // Pull the image
+ var writer io.Writer
+ if !c.Bool("quiet") {
+ writer = os.Stdout
+ }
+ createImage.Pull(writer)
+ }
+
+ var imageName string
+ if createImage.LocalName != "" {
+ nameIsID, err := runtime.IsImageID(createImage.LocalName)
+ if err != nil {
+ return "", "", nil, err
+ }
+ if nameIsID {
+ // If the input from the user is an ID, then we need to get the image
+ // name for cstorage
+ createImage.LocalName, err = createImage.GetNameByID()
+ if err != nil {
+ return "", "", nil, err
+ }
+ }
+ imageName = createImage.LocalName
+ } else {
+ imageName, err = createImage.GetFQName()
+ }
+ if err != nil {
+ return "", "", nil, err
+ }
+ imageID, err := createImage.GetImageID()
+ if err != nil {
+ return "", "", nil, err
+ }
+ storageImage, err := runtime.GetImage(image)
+ if err != nil {
+ return "", "", nil, errors.Wrapf(err, "error getting storage image %q", image)
+ }
+ data, err := runtime.GetImageInspectInfo(*storageImage)
+ if err != nil {
+ return "", "", nil, errors.Wrapf(err, "error parsing image data %q", image)
+ }
+ return imageName, imageID, data, err
+}
+
// Parses CLI options related to container creation into a config which can be
// parsed into an OCI runtime spec
func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) {
var command []string
var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
var blkioWeight uint16
- var uid, gid uint32
if len(c.Args()) < 1 {
return nil, errors.Errorf("image name or ID is required")
@@ -317,33 +371,14 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
command = c.Args()[1:]
}
- // LABEL VARIABLES
- labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels"))
- if err != nil {
- return &createConfig{}, errors.Wrapf(err, "unable to process labels")
- }
- // ENVIRONMENT VARIABLES
- env := defaultEnvVariables
- if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil {
- return &createConfig{}, errors.Wrapf(err, "unable to process environment variables")
- }
-
sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=")
if err != nil {
- return &createConfig{}, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
+ return nil, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
}
groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add"))
if err != nil {
- return &createConfig{}, errors.Wrapf(err, "invalid value for groups provided")
- }
-
- if c.String("user") != "" {
- // TODO
- // We need to mount the imagefs and get the uid/gid
- // For now, user zeros
- uid = 0
- gid = 0
+ return nil, errors.Wrapf(err, "invalid value for groups provided")
}
if c.String("memory") != "" {
@@ -417,14 +452,79 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
}
shmDir = ctr.ShmDir()
}
- stopSignal := syscall.SIGTERM
+
+ imageName, imageID, data, err := imageData(c, runtime, image)
+ if err != nil {
+ return nil, err
+ }
+
+ // USER
+ user := c.String("user")
+ if user == "" {
+ user = data.Config.User
+ }
+
+ // STOP SIGNAL
+ stopSignal := syscall.SIGINT
+ signalString := data.Config.StopSignal
if c.IsSet("stop-signal") {
- stopSignal, err = signal.ParseSignal(c.String("stop-signal"))
+ signalString = c.String("stop-signal")
+ }
+ if signalString != "" {
+ stopSignal, err = signal.ParseSignal(signalString)
if err != nil {
return nil, err
}
}
+ // ENVIRONMENT VARIABLES
+ env := defaultEnvVariables
+ for _, e := range data.Config.Env {
+ split := strings.SplitN(e, "=", 2)
+ if len(split) > 1 {
+ env[split[0]] = split[1]
+ } else {
+ env[split[0]] = ""
+ }
+ }
+ if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil {
+ return nil, errors.Wrapf(err, "unable to process environment variables")
+ }
+
+ // LABEL VARIABLES
+ labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels"))
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to process labels")
+ }
+ for key, val := range data.Config.Labels {
+ if _, ok := labels[key]; !ok {
+ labels[key] = val
+ }
+ }
+
+ // WORKING DIRECTORY
+ workDir := c.String("workdir")
+ if workDir == "" {
+ workDir = data.Config.WorkingDir
+ }
+
+ // COMMAND
+ if len(command) == 0 {
+ command = data.Config.Cmd
+ }
+
+ // ENTRYPOINT
+ entrypoint := c.String("entrypoint")
+ if entrypoint == "" {
+ entrypoint = strings.Join(data.Config.Entrypoint, " ")
+ }
+
+ // EXPOSED PORTS
+ ports, portBindings, err := exposedPorts(c, data.Config.ExposedPorts)
+ if err != nil {
+ return nil, err
+ }
+
config := &createConfig{
Runtime: runtime,
CapAdd: c.StringSlice("cap-add"),
@@ -436,12 +536,13 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
DNSOpt: c.StringSlice("dns-opt"),
DNSSearch: c.StringSlice("dns-search"),
DNSServers: c.StringSlice("dns"),
- Entrypoint: c.String("entrypoint"),
+ Entrypoint: entrypoint,
Env: env,
- Expose: c.StringSlice("expose"),
+ ExposedPorts: ports,
GroupAdd: groupAdd,
Hostname: c.String("hostname"),
- Image: image,
+ Image: imageName,
+ ImageID: imageID,
Interactive: c.Bool("interactive"),
IP6Address: c.String("ipv6"),
IPAddress: c.String("ip"),
@@ -461,6 +562,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
Privileged: c.Bool("privileged"),
Publish: c.StringSlice("publish"),
PublishAll: c.Bool("publish-all"),
+ PortBindings: portBindings,
Quiet: c.Bool("quiet"),
ReadOnlyRootfs: c.Bool("read-only"),
Resources: createResourceConfig{
@@ -499,10 +601,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
Sysctl: sysctl,
Tmpfs: c.StringSlice("tmpfs"),
Tty: tty,
- User: uid,
- Group: gid,
+ User: user,
Volumes: c.StringSlice("volume"),
- WorkDir: c.String("workdir"),
+ WorkDir: workDir,
}
if !config.Privileged {
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index 7fd039b75..869d16911 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
+ "github.com/docker/go-connections/nat"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/formats"
@@ -287,7 +288,7 @@ type HostConfig struct {
ContainerIDFile string `json:"ContainerIDFile"`
LogConfig *LogConfig `json:"LogConfig"` //TODO
NetworkMode string `json:"NetworkMode"`
- PortBindings map[string]struct{} `json:"PortBindings"` //TODO
+ PortBindings nat.PortMap `json:"PortBindings"` //TODO
AutoRemove bool `json:"AutoRemove"`
CapAdd []string `json:"CapAdd"`
CapDrop []string `json:"CapDrop"`
diff --git a/cmd/podman/parse.go b/cmd/podman/parse.go
index 53d49c36c..bb45d08c4 100644
--- a/cmd/podman/parse.go
+++ b/cmd/podman/parse.go
@@ -11,14 +11,12 @@ import (
"io/ioutil"
"net"
"os"
- "os/user"
"path"
"regexp"
"strconv"
"strings"
units "github.com/docker/go-units"
- specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
)
@@ -713,77 +711,6 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolin
return m, nil
}
-// parseUser parses the the uid and gid in the format <name|uid>[:<group|gid>]
-// for user flag
-// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66
-func parseUser(rootdir, userspec string) (specs.User, error) { //nolint
- var gid64 uint64
- var gerr error = user.UnknownGroupError("error looking up group")
-
- spec := strings.SplitN(userspec, ":", 2)
- userspec = spec[0]
- groupspec := ""
- if userspec == "" {
- return specs.User{}, nil
- }
- if len(spec) > 1 {
- groupspec = spec[1]
- }
-
- uid64, uerr := strconv.ParseUint(userspec, 10, 32)
- if uerr == nil && groupspec == "" {
- // We parsed the user name as a number, and there's no group
- // component, so we need to look up the user's primary GID.
- var name string
- name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
- if gerr == nil {
- userspec = name
- } else {
- if userrec, err := user.LookupId(userspec); err == nil {
- gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
- userspec = userrec.Name
- }
- }
- }
- if uerr != nil {
- uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
- gerr = uerr
- }
- if uerr != nil {
- if userrec, err := user.Lookup(userspec); err == nil {
- uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
- gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
- }
- }
-
- if groupspec != "" {
- gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
- if gerr != nil {
- gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
- }
- if gerr != nil {
- if group, err := user.LookupGroup(groupspec); err == nil {
- gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
- }
- }
- }
-
- if uerr == nil && gerr == nil {
- u := specs.User{
- UID: uint32(uid64),
- GID: uint32(gid64),
- Username: userspec,
- }
- return u, nil
- }
-
- err := errors.Wrapf(uerr, "error determining run uid")
- if uerr == nil {
- err = errors.Wrapf(gerr, "error determining run gid")
- }
- return specs.User{}, err
-}
-
// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
func convertKVStringsToMap(values []string) map[string]string {
result := make(map[string]string, len(values))
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index bc93459ad..654b7a47e 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -1,9 +1,8 @@
package main
import (
+ "encoding/json"
"fmt"
- "io"
- "os"
"sync"
"github.com/pkg/errors"
@@ -26,7 +25,6 @@ var runCommand = cli.Command{
}
func runCmd(c *cli.Context) error {
- var imageName string
if err := validateFlags(c, createFlags); err != nil {
return err
}
@@ -41,51 +39,10 @@ func runCmd(c *cli.Context) error {
return err
}
- createImage := runtime.NewImage(createConfig.Image)
- createImage.LocalName, _ = createImage.GetLocalImageName()
- if createImage.LocalName == "" {
- // The image wasnt found by the user input'd name or its fqname
- // Pull the image
- var writer io.Writer
- if !createConfig.Quiet {
- writer = os.Stdout
- }
- createImage.Pull(writer)
- }
-
runtimeSpec, err := createConfigToOCISpec(createConfig)
if err != nil {
return err
}
- logrus.Debug("spec is ", runtimeSpec)
-
- if createImage.LocalName != "" {
- nameIsID, err := runtime.IsImageID(createImage.LocalName)
- if err != nil {
- return err
- }
- if nameIsID {
- // If the input from the user is an ID, then we need to get the image
- // name for cstorage
- createImage.LocalName, err = createImage.GetNameByID()
- if err != nil {
- return err
- }
- }
- imageName = createImage.LocalName
- } else {
- imageName, err = createImage.GetFQName()
- }
- if err != nil {
- return err
- }
- logrus.Debug("imageName is ", imageName)
-
- imageID, err := createImage.GetImageID()
- if err != nil {
- return err
- }
- logrus.Debug("imageID is ", imageID)
options, err := createConfig.GetContainerCreateOptions()
if err != nil {
@@ -93,8 +50,10 @@ func runCmd(c *cli.Context) error {
}
// Gather up the options for NewContainer which consist of With... funcs
- options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
+ options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
+ options = append(options, libpod.WithLabels(createConfig.Labels))
+ options = append(options, libpod.WithUser(createConfig.User))
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
ctr, err := runtime.NewContainer(runtimeSpec, options...)
if err != nil {
@@ -107,6 +66,16 @@ func runCmd(c *cli.Context) error {
}
logrus.Debugf("container storage created for %q", ctr.ID())
+ createConfigJSON, err := json.Marshal(createConfig)
+ if err != nil {
+ return err
+ }
+ if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil {
+ return err
+ }
+
+ logrus.Debug("new container created ", ctr.ID())
+
if c.String("cidfile") != "" {
libpod.WriteFile(ctr.ID(), c.String("cidfile"))
return nil
diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go
index e000467e2..96eb2f6ee 100644
--- a/cmd/podman/spec.go
+++ b/cmd/podman/spec.go
@@ -193,9 +193,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
g.SetProcessCwd(config.WorkDir)
g.SetProcessArgs(config.Command)
g.SetProcessTerminal(config.Tty)
- // User and Group must go together
- g.SetProcessUID(config.User)
- g.SetProcessGID(config.Group)
for _, gid := range config.GroupAdd {
g.AddProcessAdditionalGid(gid)
}
diff --git a/libpod/container.go b/libpod/container.go
index 454fe43ac..18f3ca5ae 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -28,6 +28,7 @@ import (
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod/driver"
crioAnnotations "github.com/projectatomic/libpod/pkg/annotations"
+ "github.com/projectatomic/libpod/pkg/chrootuser"
"github.com/sirupsen/logrus"
"github.com/ulule/deepcopier"
"golang.org/x/sys/unix"
@@ -153,7 +154,8 @@ type ContainerConfig struct {
SharedNamespaceMap map[string]string `json:"sharedNamespaces"`
// Time container was created
CreatedTime time.Time `json:"createdTime"`
-
+ // User/GID to use within the container
+ User string `json:"user"`
// TODO save log location here and pass into OCI code
// TODO allow overriding of log path
}
@@ -440,7 +442,6 @@ func newContainer(rspec *spec.Spec, lockDir string) (*Container, error) {
ctr.config.Spec = new(spec.Spec)
deepcopier.Copy(rspec).To(ctr.config.Spec)
-
ctr.config.CreatedTime = time.Now()
// Path our lock file will reside at
@@ -614,6 +615,20 @@ func (c *Container) Init() (err error) {
g.AddBindMount(runDirResolv, "/etc/resolv.conf", []string{"rw"})
// Bind mount hosts
g.AddBindMount(runDirHosts, "/etc/hosts", []string{"rw"})
+
+ if c.config.User != "" {
+ if !c.state.Mounted {
+ return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
+ }
+ uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User)
+ if err != nil {
+ return err
+ }
+ // User and Group must go together
+ g.SetProcessUID(uid)
+ g.SetProcessGID(gid)
+ }
+
c.runningSpec = g.Spec()
c.runningSpec.Root.Path = c.state.Mountpoint
c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano)
@@ -1078,7 +1093,7 @@ func (c *Container) mountStorage() (err error) {
}
}
- mountPoint, err := c.runtime.storageService.StartContainer(c.ID())
+ mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID())
if err != nil {
return errors.Wrapf(err, "error mounting storage for container %s", c.ID())
}
@@ -1124,7 +1139,7 @@ func (c *Container) cleanupStorage() error {
}
// Also unmount storage
- if err := c.runtime.storageService.StopContainer(c.ID()); err != nil {
+ if err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil {
return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID())
}
diff --git a/libpod/options.go b/libpod/options.go
index 4836e1d67..70db3bdae 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -307,6 +307,19 @@ func WithSELinuxLabels(processLabel, mountLabel string) CtrCreateOption {
}
}
+// WithUser sets the user identity field in configutation
+// Valid uses [user | user:group | uid | uid:gid | user:gid | uid:group ]
+func WithUser(user string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return ErrCtrFinalized
+ }
+
+ ctr.config.User = user
+ return nil
+ }
+}
+
// WithRootFSFromImage sets up a fresh root filesystem using the given image
// If useImageConfig is specified, image volumes, environment variables, and
// other configuration from the image will be added to the config
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 1f2b8945e..9e42ff8d1 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -25,14 +25,14 @@ type CtrCreateOption func(*Container) error
type ContainerFilter func(*Container) bool
// NewContainer creates a new container from a given OCI config
-func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) {
+func (r *Runtime) NewContainer(rSpec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) {
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
return nil, ErrRuntimeStopped
}
- ctr, err := newContainer(spec, r.lockDir)
+ ctr, err := newContainer(rSpec, r.lockDir)
if err != nil {
return nil, err
}
diff --git a/libpod/storage.go b/libpod/storage.go
index 5e18aaf5c..42b9a86ad 100644
--- a/libpod/storage.go
+++ b/libpod/storage.go
@@ -200,7 +200,7 @@ func (r *storageService) GetContainerMetadata(idOrName string) (RuntimeContainer
return metadata, nil
}
-func (r *storageService) StartContainer(idOrName string) (string, error) {
+func (r *storageService) MountContainerImage(idOrName string) (string, error) {
container, err := r.store.Container(idOrName)
if err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
@@ -221,7 +221,7 @@ func (r *storageService) StartContainer(idOrName string) (string, error) {
return mountPoint, nil
}
-func (r *storageService) StopContainer(idOrName string) error {
+func (r *storageService) UnmountContainerImage(idOrName string) error {
if idOrName == "" {
return ErrEmptyID
}
diff --git a/pkg/chrootuser/user.go b/pkg/chrootuser/user.go
new file mode 100644
index 000000000..a024877a5
--- /dev/null
+++ b/pkg/chrootuser/user.go
@@ -0,0 +1,71 @@
+package chrootuser
+
+import (
+ "os/user"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+// GetUser will return the uid, gid of the user specified in the userspec
+// it will use the /etc/password and /etc/shadow files inside of the rootdir
+// to return this information.
+// userspace format [user | user:group | uid | uid:gid | user:gid | uid:group ]
+func GetUser(rootdir, userspec string) (uint32, uint32, error) {
+ var gid64 uint64
+ var gerr error = user.UnknownGroupError("error looking up group")
+
+ spec := strings.SplitN(userspec, ":", 2)
+ userspec = spec[0]
+ groupspec := ""
+ if userspec == "" {
+ return 0, 0, nil
+ }
+ if len(spec) > 1 {
+ groupspec = spec[1]
+ }
+
+ uid64, uerr := strconv.ParseUint(userspec, 10, 32)
+ if uerr == nil && groupspec == "" {
+ // We parsed the user name as a number, and there's no group
+ // component, so try to look up the primary GID of the user who
+ // has this UID.
+ var name string
+ name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
+ if gerr == nil {
+ userspec = name
+ } else {
+ // Leave userspec alone, but swallow the error and just
+ // use GID 0.
+ gid64 = 0
+ gerr = nil
+ }
+ }
+ if uerr != nil {
+ // The user ID couldn't be parsed as a number, so try to look
+ // up the user's UID and primary GID.
+ uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
+ gerr = uerr
+ }
+
+ if groupspec != "" {
+ // We have a group name or number, so parse it.
+ gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
+ if gerr != nil {
+ // The group couldn't be parsed as a number, so look up
+ // the group's GID.
+ gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
+ }
+ }
+
+ if uerr == nil && gerr == nil {
+ return uint32(uid64), uint32(gid64), nil
+ }
+
+ err := errors.Wrapf(uerr, "error determining run uid")
+ if uerr == nil {
+ err = errors.Wrapf(gerr, "error determining run gid")
+ }
+ return 0, 0, err
+}
diff --git a/pkg/chrootuser/user_basic.go b/pkg/chrootuser/user_basic.go
new file mode 100644
index 000000000..4f89af557
--- /dev/null
+++ b/pkg/chrootuser/user_basic.go
@@ -0,0 +1,19 @@
+// +build !linux
+
+package chrootuser
+
+import (
+ "github.com/pkg/errors"
+)
+
+func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
+ return 0, 0, errors.New("user lookup not supported")
+}
+
+func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
+ return 0, errors.New("group lookup not supported")
+}
+
+func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
+ return "", 0, errors.New("primary group lookup by uid not supported")
+}
diff --git a/pkg/chrootuser/user_linux.go b/pkg/chrootuser/user_linux.go
new file mode 100644
index 000000000..2baf9ea33
--- /dev/null
+++ b/pkg/chrootuser/user_linux.go
@@ -0,0 +1,235 @@
+// +build linux
+
+package chrootuser
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "os/user"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/containers/storage/pkg/reexec"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ openChrootedCommand = "chrootuser-open"
+)
+
+func init() {
+ reexec.Register(openChrootedCommand, openChrootedFileMain)
+}
+
+func openChrootedFileMain() {
+ status := 0
+ flag.Parse()
+ if len(flag.Args()) < 1 {
+ os.Exit(1)
+ }
+ // Our first parameter is the directory to chroot into.
+ if err := unix.Chdir(flag.Arg(0)); err != nil {
+ fmt.Fprintf(os.Stderr, "chdir(): %v", err)
+ os.Exit(1)
+ }
+ if err := unix.Chroot(flag.Arg(0)); err != nil {
+ fmt.Fprintf(os.Stderr, "chroot(): %v", err)
+ os.Exit(1)
+ }
+ // Anything else is a file we want to dump out.
+ for _, filename := range flag.Args()[1:] {
+ f, err := os.Open(filename)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "open(%q): %v", filename, err)
+ status = 1
+ continue
+ }
+ _, err = io.Copy(os.Stdout, f)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "read(%q): %v", filename, err)
+ }
+ f.Close()
+ }
+ os.Exit(status)
+}
+
+func openChrootedFile(rootdir, filename string) (*exec.Cmd, io.ReadCloser, error) {
+ // The child process expects a chroot and one or more filenames that
+ // will be consulted relative to the chroot directory and concatenated
+ // to its stdout. Start it up.
+ cmd := reexec.Command(openChrootedCommand, rootdir, filename)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, nil, err
+ }
+ err = cmd.Start()
+ if err != nil {
+ return nil, nil, err
+ }
+ // Hand back the child's stdout for reading, and the child to reap.
+ return cmd, stdout, nil
+}
+
+var (
+ lookupUser, lookupGroup sync.Mutex
+)
+
+type lookupPasswdEntry struct {
+ name string
+ uid uint64
+ gid uint64
+}
+type lookupGroupEntry struct {
+ name string
+ gid uint64
+}
+
+func readWholeLine(rc *bufio.Reader) ([]byte, error) {
+ line, isPrefix, err := rc.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ for isPrefix {
+ // We didn't get a whole line. Keep reading chunks until we find an end of line, and discard them.
+ for isPrefix {
+ logrus.Debugf("discarding partial line %q", string(line))
+ _, isPrefix, err = rc.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ }
+ // That last read was the end of a line, so now we try to read the (beginning of?) the next line.
+ line, isPrefix, err = rc.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return line, nil
+}
+
+func parseNextPasswd(rc *bufio.Reader) *lookupPasswdEntry {
+ line, err := readWholeLine(rc)
+ if err != nil {
+ return nil
+ }
+ fields := strings.Split(string(line), ":")
+ if len(fields) < 7 {
+ return nil
+ }
+ uid, err := strconv.ParseUint(fields[2], 10, 32)
+ if err != nil {
+ return nil
+ }
+ gid, err := strconv.ParseUint(fields[3], 10, 32)
+ if err != nil {
+ return nil
+ }
+ return &lookupPasswdEntry{
+ name: fields[0],
+ uid: uid,
+ gid: gid,
+ }
+}
+
+func parseNextGroup(rc *bufio.Reader) *lookupGroupEntry {
+ line, err := readWholeLine(rc)
+ if err != nil {
+ return nil
+ }
+ fields := strings.Split(string(line), ":")
+ if len(fields) < 4 {
+ return nil
+ }
+ gid, err := strconv.ParseUint(fields[2], 10, 32)
+ if err != nil {
+ return nil
+ }
+ return &lookupGroupEntry{
+ name: fields[0],
+ gid: gid,
+ }
+}
+
+func lookupUserInContainer(rootdir, username string) (uid uint64, gid uint64, err error) {
+ cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
+ if err != nil {
+ return 0, 0, err
+ }
+ defer func() {
+ _ = cmd.Wait()
+ }()
+ rc := bufio.NewReader(f)
+ defer f.Close()
+
+ lookupUser.Lock()
+ defer lookupUser.Unlock()
+
+ pwd := parseNextPasswd(rc)
+ for pwd != nil {
+ if pwd.name != username {
+ pwd = parseNextPasswd(rc)
+ continue
+ }
+ return pwd.uid, pwd.gid, nil
+ }
+
+ return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
+}
+
+func lookupGroupForUIDInContainer(rootdir string, userid uint64) (username string, gid uint64, err error) {
+ cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
+ if err != nil {
+ return "", 0, err
+ }
+ defer func() {
+ _ = cmd.Wait()
+ }()
+ rc := bufio.NewReader(f)
+ defer f.Close()
+
+ lookupUser.Lock()
+ defer lookupUser.Unlock()
+
+ pwd := parseNextPasswd(rc)
+ for pwd != nil {
+ if pwd.uid != userid {
+ pwd = parseNextPasswd(rc)
+ continue
+ }
+ return pwd.name, pwd.gid, nil
+ }
+
+ return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
+}
+
+func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) {
+ cmd, f, err := openChrootedFile(rootdir, "/etc/group")
+ if err != nil {
+ return 0, err
+ }
+ defer func() {
+ _ = cmd.Wait()
+ }()
+ rc := bufio.NewReader(f)
+ defer f.Close()
+
+ lookupGroup.Lock()
+ defer lookupGroup.Unlock()
+
+ grp := parseNextGroup(rc)
+ for grp != nil {
+ if grp.name != groupname {
+ grp = parseNextGroup(rc)
+ continue
+ }
+ return grp.gid, nil
+ }
+
+ return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
+}