summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/create.go5
-rw-r--r--contrib/python/podman/Makefile2
-rw-r--r--contrib/python/pypodman/Makefile2
-rw-r--r--libpod/container.go12
-rw-r--r--libpod/container_internal.go228
-rw-r--r--libpod/container_internal_linux.go261
-rw-r--r--libpod/mounts_linux.go18
-rw-r--r--libpod/oci.go2
-rw-r--r--test/e2e/create_staticip_test.go86
-rw-r--r--test/e2e/run_staticip_test.go9
-rw-r--r--test/e2e/run_test.go5
11 files changed, 402 insertions, 228 deletions
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 9f6825c95..bcf830c7c 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -670,6 +670,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 {
return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
}
+ if !netMode.IsPrivate() {
+ if c.IsSet("dns-search") || c.IsSet("dns") || c.IsSet("dns-opt") {
+ return nil, errors.Errorf("specifying DNS flags when network mode is shared with the host or another container is not allowed")
+ }
+ }
// Validate domains are good
for _, dom := range c.StringSlice("dns-search") {
diff --git a/contrib/python/podman/Makefile b/contrib/python/podman/Makefile
index 6ec4159f2..11a7568d1 100644
--- a/contrib/python/podman/Makefile
+++ b/contrib/python/podman/Makefile
@@ -4,6 +4,7 @@ PODMAN_VERSION ?= '0.0.4'
.PHONY: python-podman
python-podman:
+ PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py sdist bdist
.PHONY: lint
@@ -16,6 +17,7 @@ integration:
.PHONY: install
install:
+ PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py install --root ${DESTDIR}
.PHONY: upload
diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile
index cd0fcf1de..272145c5d 100644
--- a/contrib/python/pypodman/Makefile
+++ b/contrib/python/pypodman/Makefile
@@ -4,6 +4,7 @@ PODMAN_VERSION ?= '0.0.4'
.PHONY: python-pypodman
python-pypodman:
+ PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py sdist bdist
.PHONY: lint
@@ -16,6 +17,7 @@ integration:
.PHONY: install
install:
+ PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py install --root ${DESTDIR}
.PHONY: upload
diff --git a/libpod/container.go b/libpod/container.go
index 7bb5b2687..16f61d021 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -996,3 +996,15 @@ func (c *Container) IsInfra() bool {
func (c *Container) IsReadOnly() bool {
return c.config.Spec.Root.Readonly
}
+
+// NetworkDisabled returns whether the container is running with a disabled network
+func (c *Container) NetworkDisabled() bool {
+ if !c.config.PostConfigureNetNS {
+ for _, ns := range c.config.Spec.Linux.Namespaces {
+ if ns.Type == spec.NetworkNamespace {
+ return ns.Path == ""
+ }
+ }
+ }
+ return false
+}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index d2f48d661..051e0aeb7 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -19,14 +19,11 @@ import (
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
"github.com/containers/libpod/pkg/lookup"
- "github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/secrets"
"github.com/containers/storage"
"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"
@@ -583,7 +580,7 @@ func (c *Container) checkDependenciesRunningLocked(depCtrs map[string]*Container
}
func (c *Container) completeNetworkSetup() error {
- if !c.config.PostConfigureNetNS {
+ if !c.config.PostConfigureNetNS || c.NetworkDisabled() {
return nil
}
if err := c.syncContainer(); err != nil {
@@ -597,10 +594,6 @@ func (c *Container) completeNetworkSetup() error {
// Initialize a container, creating it in the runtime
func (c *Container) init(ctx context.Context) error {
- if err := c.makeBindMounts(); err != nil {
- return err
- }
-
// Generate the OCI spec
spec, err := c.generateSpec(ctx)
if err != nil {
@@ -987,86 +980,6 @@ func (c *Container) postDeleteHooks(ctx context.Context) (err error) {
return nil
}
-// Make standard bind mounts to include in the container
-func (c *Container) makeBindMounts() error {
- if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
- return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir)
- }
-
- if c.state.BindMounts == nil {
- c.state.BindMounts = make(map[string]string)
- }
-
- // SHM is always added when we mount the container
- c.state.BindMounts["/dev/shm"] = c.config.ShmDir
-
- // Make /etc/resolv.conf
- if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
- // If it already exists, delete so we can recreate
- delete(c.state.BindMounts, "/etc/resolv.conf")
- }
- newResolv, err := c.generateResolvConf()
- if err != nil {
- return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
- }
- c.state.BindMounts["/etc/resolv.conf"] = newResolv
-
- newPasswd, err := c.generatePasswd()
- if err != nil {
- return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID())
- }
- if newPasswd != "" {
- // Make /etc/passwd
- if _, ok := c.state.BindMounts["/etc/passwd"]; ok {
- // If it already exists, delete so we can recreate
- delete(c.state.BindMounts, "/etc/passwd")
- }
- logrus.Debugf("adding entry to /etc/passwd for non existent default user")
- c.state.BindMounts["/etc/passwd"] = newPasswd
- }
- // Make /etc/hosts
- if _, ok := c.state.BindMounts["/etc/hosts"]; ok {
- // If it already exists, delete so we can recreate
- delete(c.state.BindMounts, "/etc/hosts")
- }
- newHosts, err := c.generateHosts()
- if err != nil {
- return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
- }
- c.state.BindMounts["/etc/hosts"] = newHosts
-
- // Make /etc/hostname
- // This should never change, so no need to recreate if it exists
- if _, ok := c.state.BindMounts["/etc/hostname"]; !ok {
- hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname())
- if err != nil {
- return errors.Wrapf(err, "error creating hostname file for container %s", c.ID())
- }
- c.state.BindMounts["/etc/hostname"] = hostnamePath
- }
-
- // Make .containerenv
- // Empty file, so no need to recreate if it exists
- if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
- // Empty string for now, but we may consider populating this later
- containerenvPath, err := c.writeStringToRundir(".containerenv", "")
- if err != nil {
- return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
- }
- c.state.BindMounts["/run/.containerenv"] = containerenvPath
- }
-
- // Add Secret Mounts
- secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID())
- for _, mount := range secretMounts {
- if _, ok := c.state.BindMounts[mount.Destination]; !ok {
- c.state.BindMounts[mount.Destination] = mount.Source
- }
- }
-
- return nil
-}
-
// writeStringToRundir copies the provided file to the runtimedir
func (c *Container) writeStringToRundir(destFile, output string) (string, error) {
destFileName := filepath.Join(c.state.RunDir, destFile)
@@ -1095,145 +1008,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
-// generatePasswd generates a container specific passwd file,
-// iff g.config.User is a number
-func (c *Container) generatePasswd() (string, error) {
- var (
- groupspec string
- group *user.Group
- gid int
- )
- if c.config.User == "" {
- return "", nil
- }
- spec := strings.SplitN(c.config.User, ":", 2)
- userspec := spec[0]
- if len(spec) > 1 {
- groupspec = spec[1]
- }
- // If a non numeric User, then don't generate passwd
- uid, err := strconv.ParseUint(userspec, 10, 32)
- if err != nil {
- return "", nil
- }
- // Lookup the user to see if it exists in the container image
- _, err = lookup.GetUser(c.state.Mountpoint, userspec)
- if err != nil && err != user.ErrNoPasswdEntries {
- return "", err
- }
- if err == nil {
- return "", nil
- }
- if groupspec != "" {
- if !c.state.Mounted {
- return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID())
- }
- group, err = lookup.GetGroup(c.state.Mountpoint, groupspec)
- if err != nil {
- if err == user.ErrNoGroupEntries {
- return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
- }
- return "", err
- }
- gid = group.Gid
- }
- originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
- orig, err := ioutil.ReadFile(originPasswdFile)
- if err != nil && !os.IsNotExist(err) {
- return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
- }
-
- pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir())
- passwdFile, err := c.writeStringToRundir("passwd", pwd)
- if err != nil {
- return "", errors.Wrapf(err, "failed to create temporary passwd file")
- }
- if os.Chmod(passwdFile, 0644); err != nil {
- return "", err
- }
- return passwdFile, nil
-}
-
-// generateResolvConf generates a containers resolv.conf
-func (c *Container) generateResolvConf() (string, error) {
- // Determine the endpoint for resolv.conf in case it is a symlink
- resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf")
- if err != nil {
- return "", err
- }
-
- contents, err := ioutil.ReadFile(resolvPath)
- if err != nil {
- return "", errors.Wrapf(err, "unable to read %s", resolvPath)
- }
-
- // Process the file to remove localhost nameservers
- // TODO: set ipv6 enable bool more sanely
- resolv, err := resolvconf.FilterResolvDNS(contents, true)
- if err != nil {
- return "", errors.Wrapf(err, "error parsing host resolv.conf")
- }
-
- // Make a new resolv.conf
- nameservers := resolvconf.GetNameservers(resolv.Content)
- if len(c.config.DNSServer) > 0 {
- // We store DNS servers as net.IP, so need to convert to string
- nameservers = []string{}
- for _, server := range c.config.DNSServer {
- nameservers = append(nameservers, server.String())
- }
- }
-
- search := resolvconf.GetSearchDomains(resolv.Content)
- if len(c.config.DNSSearch) > 0 {
- search = c.config.DNSSearch
- }
-
- options := resolvconf.GetOptions(resolv.Content)
- if len(c.config.DNSOption) > 0 {
- options = c.config.DNSOption
- }
-
- destPath := filepath.Join(c.state.RunDir, "resolv.conf")
-
- if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
- return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID())
- }
-
- // Build resolv.conf
- if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
- return "", errors.Wrapf(err, "error building resolv.conf for container %s")
- }
-
- // Relabel resolv.conf for the container
- if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil {
- return "", err
- }
-
- return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil
-}
-
-// generateHosts creates a containers hosts file
-func (c *Container) generateHosts() (string, error) {
- orig, err := ioutil.ReadFile("/etc/hosts")
- if err != nil {
- return "", errors.Wrapf(err, "unable to read /etc/hosts")
- }
- hosts := string(orig)
- if len(c.config.HostAdd) > 0 {
- for _, host := range c.config.HostAdd {
- // the host format has already been verified at this point
- fields := strings.SplitN(host, ":", 2)
- hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0])
- }
- }
- if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
- ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
- hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname())
- }
- return c.writeStringToRundir("hosts", hosts)
-}
-
func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error {
var uid, gid int
mountPoint := c.state.Mountpoint
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index e6071945d..ffb82cc94 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -11,6 +11,7 @@ import (
"os"
"path"
"path/filepath"
+ "strconv"
"strings"
"sync"
"syscall"
@@ -21,8 +22,11 @@ import (
crioAnnotations "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/criu"
"github.com/containers/libpod/pkg/lookup"
+ "github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/secrets"
"github.com/containers/storage/pkg/idtools"
+ "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"
@@ -132,6 +136,9 @@ func (c *Container) prepare() (err error) {
// cleanupNetwork unmounts and cleans up the container's network
func (c *Container) cleanupNetwork() error {
+ if c.NetworkDisabled() {
+ return nil
+ }
if c.state.NetNS == nil {
logrus.Debugf("Network is already cleaned up, skipping...")
return nil
@@ -169,6 +176,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path())
}
}
+
+ if err := c.makeBindMounts(); err != nil {
+ return nil, err
+ }
+
// Check if the spec file mounts contain the label Relabel flags z or Z.
// If they do, relabel the source directory and then remove the option.
for _, m := range g.Mounts() {
@@ -347,8 +359,34 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
// Mounts need to be sorted so paths will not cover other paths
mounts := sortMounts(g.Mounts())
g.ClearMounts()
+
+ // Determine property of RootPropagation based on volume properties. If
+ // a volume is shared, then keep root propagation shared. This should
+ // work for slave and private volumes too.
+ //
+ // For slave volumes, it can be either [r]shared/[r]slave.
+ //
+ // For private volumes any root propagation value should work.
+ rootPropagation := ""
for _, m := range mounts {
g.AddMount(m)
+ for _, opt := range m.Options {
+ switch opt {
+ case MountShared, MountRShared:
+ if rootPropagation != MountShared && rootPropagation != MountRShared {
+ rootPropagation = MountShared
+ }
+ case MountSlave, MountRSlave:
+ if rootPropagation != MountShared && rootPropagation != MountRShared && rootPropagation != MountSlave && rootPropagation != MountRSlave {
+ rootPropagation = MountRSlave
+ }
+ }
+ }
+ }
+
+ if rootPropagation != "" {
+ logrus.Debugf("set root propagation to %q", rootPropagation)
+ g.SetLinuxRootPropagation(rootPropagation)
}
return g.Config, nil
}
@@ -593,3 +631,226 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) {
return c.save()
}
+
+// Make standard bind mounts to include in the container
+func (c *Container) makeBindMounts() error {
+ if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
+ return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir)
+ }
+
+ if c.state.BindMounts == nil {
+ c.state.BindMounts = make(map[string]string)
+ }
+
+ if !c.NetworkDisabled() {
+ // Make /etc/resolv.conf
+ if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
+ // If it already exists, delete so we can recreate
+ delete(c.state.BindMounts, "/etc/resolv.conf")
+ }
+ newResolv, err := c.generateResolvConf()
+ if err != nil {
+ return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
+ }
+ c.state.BindMounts["/etc/resolv.conf"] = newResolv
+
+ // Make /etc/hosts
+ if _, ok := c.state.BindMounts["/etc/hosts"]; ok {
+ // If it already exists, delete so we can recreate
+ delete(c.state.BindMounts, "/etc/hosts")
+ }
+ newHosts, err := c.generateHosts()
+ if err != nil {
+ return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
+ }
+ c.state.BindMounts["/etc/hosts"] = newHosts
+
+ }
+
+ // SHM is always added when we mount the container
+ c.state.BindMounts["/dev/shm"] = c.config.ShmDir
+
+ newPasswd, err := c.generatePasswd()
+ if err != nil {
+ return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID())
+ }
+ if newPasswd != "" {
+ // Make /etc/passwd
+ if _, ok := c.state.BindMounts["/etc/passwd"]; ok {
+ // If it already exists, delete so we can recreate
+ delete(c.state.BindMounts, "/etc/passwd")
+ }
+ logrus.Debugf("adding entry to /etc/passwd for non existent default user")
+ c.state.BindMounts["/etc/passwd"] = newPasswd
+ }
+
+ // Make /etc/hostname
+ // This should never change, so no need to recreate if it exists
+ if _, ok := c.state.BindMounts["/etc/hostname"]; !ok {
+ hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname())
+ if err != nil {
+ return errors.Wrapf(err, "error creating hostname file for container %s", c.ID())
+ }
+ c.state.BindMounts["/etc/hostname"] = hostnamePath
+ }
+
+ // Make .containerenv
+ // Empty file, so no need to recreate if it exists
+ if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
+ // Empty string for now, but we may consider populating this later
+ containerenvPath, err := c.writeStringToRundir(".containerenv", "")
+ if err != nil {
+ return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
+ }
+ c.state.BindMounts["/run/.containerenv"] = containerenvPath
+ }
+
+ // Add Secret Mounts
+ secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID())
+ for _, mount := range secretMounts {
+ if _, ok := c.state.BindMounts[mount.Destination]; !ok {
+ c.state.BindMounts[mount.Destination] = mount.Source
+ }
+ }
+
+ return nil
+}
+
+// generateResolvConf generates a containers resolv.conf
+func (c *Container) generateResolvConf() (string, error) {
+ // Determine the endpoint for resolv.conf in case it is a symlink
+ resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf")
+ if err != nil {
+ return "", err
+ }
+
+ contents, err := ioutil.ReadFile(resolvPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to read %s", resolvPath)
+ }
+
+ // Process the file to remove localhost nameservers
+ // TODO: set ipv6 enable bool more sanely
+ resolv, err := resolvconf.FilterResolvDNS(contents, true)
+ if err != nil {
+ return "", errors.Wrapf(err, "error parsing host resolv.conf")
+ }
+
+ // Make a new resolv.conf
+ nameservers := resolvconf.GetNameservers(resolv.Content)
+ if len(c.config.DNSServer) > 0 {
+ // We store DNS servers as net.IP, so need to convert to string
+ nameservers = []string{}
+ for _, server := range c.config.DNSServer {
+ nameservers = append(nameservers, server.String())
+ }
+ }
+
+ search := resolvconf.GetSearchDomains(resolv.Content)
+ if len(c.config.DNSSearch) > 0 {
+ search = c.config.DNSSearch
+ }
+
+ options := resolvconf.GetOptions(resolv.Content)
+ if len(c.config.DNSOption) > 0 {
+ options = c.config.DNSOption
+ }
+
+ destPath := filepath.Join(c.state.RunDir, "resolv.conf")
+
+ if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
+ return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID())
+ }
+
+ // Build resolv.conf
+ if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
+ return "", errors.Wrapf(err, "error building resolv.conf for container %s")
+ }
+
+ // Relabel resolv.conf for the container
+ if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil {
+ return "", err
+ }
+
+ return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil
+}
+
+// generateHosts creates a containers hosts file
+func (c *Container) generateHosts() (string, error) {
+ orig, err := ioutil.ReadFile("/etc/hosts")
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to read /etc/hosts")
+ }
+ hosts := string(orig)
+ if len(c.config.HostAdd) > 0 {
+ for _, host := range c.config.HostAdd {
+ // the host format has already been verified at this point
+ fields := strings.SplitN(host, ":", 2)
+ hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0])
+ }
+ }
+ if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
+ ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
+ hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname())
+ }
+ return c.writeStringToRundir("hosts", hosts)
+}
+
+// generatePasswd generates a container specific passwd file,
+// iff g.config.User is a number
+func (c *Container) generatePasswd() (string, error) {
+ var (
+ groupspec string
+ group *user.Group
+ gid int
+ )
+ if c.config.User == "" {
+ return "", nil
+ }
+ spec := strings.SplitN(c.config.User, ":", 2)
+ userspec := spec[0]
+ if len(spec) > 1 {
+ groupspec = spec[1]
+ }
+ // If a non numeric User, then don't generate passwd
+ uid, err := strconv.ParseUint(userspec, 10, 32)
+ if err != nil {
+ return "", nil
+ }
+ // Lookup the user to see if it exists in the container image
+ _, err = lookup.GetUser(c.state.Mountpoint, userspec)
+ if err != nil && err != user.ErrNoPasswdEntries {
+ return "", err
+ }
+ if err == nil {
+ return "", nil
+ }
+ if groupspec != "" {
+ if !c.state.Mounted {
+ return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID())
+ }
+ group, err = lookup.GetGroup(c.state.Mountpoint, groupspec)
+ if err != nil {
+ if err == user.ErrNoGroupEntries {
+ return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
+ }
+ return "", err
+ }
+ gid = group.Gid
+ }
+ originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
+ orig, err := ioutil.ReadFile(originPasswdFile)
+ if err != nil && !os.IsNotExist(err) {
+ return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
+ }
+
+ pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir())
+ passwdFile, err := c.writeStringToRundir("passwd", pwd)
+ if err != nil {
+ return "", errors.Wrapf(err, "failed to create temporary passwd file")
+ }
+ if os.Chmod(passwdFile, 0644); err != nil {
+ return "", err
+ }
+ return passwdFile, nil
+}
diff --git a/libpod/mounts_linux.go b/libpod/mounts_linux.go
new file mode 100644
index 000000000..e6aa09eac
--- /dev/null
+++ b/libpod/mounts_linux.go
@@ -0,0 +1,18 @@
+// +build linux
+
+package libpod
+
+const (
+ // MountPrivate represents the private mount option.
+ MountPrivate = "private"
+ // MountRPrivate represents the rprivate mount option.
+ MountRPrivate = "rprivate"
+ // MountShared represents the shared mount option.
+ MountShared = "shared"
+ // MountRShared represents the rshared mount option.
+ MountRShared = "rshared"
+ // MountSlave represents the slave mount option.
+ MountSlave = "slave"
+ // MountRSlave represents the rslave mount option.
+ MountRSlave = "rslave"
+)
diff --git a/libpod/oci.go b/libpod/oci.go
index 8ee2c948f..a7aec06e5 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -741,6 +741,8 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty
if tty {
args = append(args, "--tty")
+ } else {
+ args = append(args, "--tty=false")
}
if user != "" {
diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go
new file mode 100644
index 000000000..ed6498b43
--- /dev/null
+++ b/test/e2e/create_staticip_test.go
@@ -0,0 +1,86 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman create with --ip flag", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("Podman create --ip with garbage address", func() {
+ result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman create --ip with v6 address", func() {
+ result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman create --ip with non-allocatable IP", func() {
+ result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "203.0.113.124", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"start", "test"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman create with specified static IP has correct IP", func() {
+ result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "10.88.64.128", ALPINE, "ip", "addr"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"start", "test"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"logs", "test"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16"))
+ })
+
+ It("Podman create two containers with the same IP", func() {
+ result := podmanTest.Podman([]string{"create", "--name", "test1", "--ip", "10.88.64.128", ALPINE, "sleep", "999"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ result = podmanTest.Podman([]string{"create", "--name", "test2", "--ip", "10.88.64.128", ALPINE, "ip", "addr"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ result = podmanTest.Podman([]string{"start", "test1"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ result = podmanTest.Podman([]string{"start", "test2"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+})
diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go
index b9fc00fce..ede7dd3de 100644
--- a/test/e2e/run_staticip_test.go
+++ b/test/e2e/run_staticip_test.go
@@ -56,4 +56,13 @@ var _ = Describe("Podman run with --ip flag", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16"))
})
+
+ It("Podman run two containers with the same IP", func() {
+ result := podmanTest.Podman([]string{"run", "-d", "--ip", "10.88.64.128", ALPINE, "sleep", "999"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ result = podmanTest.Podman([]string{"run", "-ti", "--ip", "10.88.64.128", ALPINE, "ip", "addr"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 528ec02b2..4ed1fbed7 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -608,7 +608,10 @@ USER mail`
session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:z", "--volume", vol2 + ":/myvol2:shared,z", fedoraMinimal, "findmnt", "-o", "TARGET,PROPAGATION"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- match, _ := session.GrepString("shared")
+ match, shared := session.GrepString("shared")
Expect(match).Should(BeTrue())
+ // make sure it's only shared (and not 'shared,slave')
+ isSharedOnly := !strings.Contains(shared[0], "shared,")
+ Expect(isSharedOnly).Should(BeTrue())
})
})