summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/container.go1
-rw-r--r--cmd/podman/create.go5
-rw-r--r--cmd/podman/exists.go83
-rw-r--r--cmd/podman/image.go1
-rw-r--r--cmd/podman/version.go33
-rw-r--r--completions/bash/podman31
-rw-r--r--docs/podman-container-exists.1.md40
-rw-r--r--docs/podman-container.1.md1
-rw-r--r--docs/podman-image-exists.1.md40
-rw-r--r--docs/podman-image.1.md1
-rw-r--r--docs/podman-version.1.md23
-rw-r--r--docs/tutorials/podman_tutorial.md2
-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/image/errors.go15
-rw-r--r--libpod/image/image.go5
-rw-r--r--libpod/mounts_linux.go18
-rw-r--r--libpod/oci.go2
-rw-r--r--pkg/inspect/inspect.go1
-rw-r--r--test/e2e/create_staticip_test.go86
-rw-r--r--test/e2e/exists_test.go85
-rw-r--r--test/e2e/run_staticip_test.go9
-rw-r--r--test/e2e/run_test.go5
-rw-r--r--vendor.conf2
-rw-r--r--vendor/github.com/containers/buildah/chroot/run.go43
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/build.go32
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go7
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/util.go25
-rw-r--r--vendor/github.com/containers/buildah/run.go15
-rw-r--r--vendor/github.com/containers/buildah/vendor.conf8
-rw-r--r--vendor/github.com/openshift/imagebuilder/builder.go39
-rw-r--r--vendor/github.com/openshift/imagebuilder/dispatchers.go23
-rw-r--r--vendor/github.com/openshift/imagebuilder/evaluator.go3
34 files changed, 900 insertions, 285 deletions
diff --git a/cmd/podman/container.go b/cmd/podman/container.go
index ff634278f..b6262f890 100644
--- a/cmd/podman/container.go
+++ b/cmd/podman/container.go
@@ -9,6 +9,7 @@ var (
attachCommand,
checkpointCommand,
cleanupCommand,
+ containerExistsCommand,
commitCommand,
createCommand,
diffCommand,
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/cmd/podman/exists.go b/cmd/podman/exists.go
new file mode 100644
index 000000000..2f7b7c185
--- /dev/null
+++ b/cmd/podman/exists.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "os"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var (
+ imageExistsDescription = `
+ podman image exists
+
+ Check if an image exists in local storage
+`
+
+ imageExistsCommand = cli.Command{
+ Name: "exists",
+ Usage: "Check if an image exists in local storage",
+ Description: imageExistsDescription,
+ Action: imageExistsCmd,
+ ArgsUsage: "IMAGE-NAME",
+ OnUsageError: usageErrorHandler,
+ }
+)
+
+var (
+ containerExistsDescription = `
+ podman container exists
+
+ Check if a container exists in local storage
+`
+
+ containerExistsCommand = cli.Command{
+ Name: "exists",
+ Usage: "Check if a container exists in local storage",
+ Description: containerExistsDescription,
+ Action: containerExistsCmd,
+ ArgsUsage: "CONTAINER-NAME",
+ OnUsageError: usageErrorHandler,
+ }
+)
+
+func imageExistsCmd(c *cli.Context) error {
+ args := c.Args()
+ if len(args) > 1 || len(args) < 1 {
+ return errors.New("you may only check for the existence of one image at a time")
+ }
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+ if _, err := runtime.ImageRuntime().NewFromLocal(args[0]); err != nil {
+ if errors.Cause(err) == image.ErrNoSuchImage {
+ os.Exit(1)
+ }
+ return err
+ }
+ return nil
+}
+
+func containerExistsCmd(c *cli.Context) error {
+ args := c.Args()
+ if len(args) > 1 || len(args) < 1 {
+ return errors.New("you may only check for the existence of one container at a time")
+ }
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+ if _, err := runtime.LookupContainer(args[0]); err != nil {
+ if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ os.Exit(1)
+ }
+ return err
+ }
+ return nil
+}
diff --git a/cmd/podman/image.go b/cmd/podman/image.go
index e67f61799..418b442e3 100644
--- a/cmd/podman/image.go
+++ b/cmd/podman/image.go
@@ -9,6 +9,7 @@ var (
buildCommand,
historyCommand,
importCommand,
+ imageExistsCommand,
inspectCommand,
loadCommand,
lsImagesCommand,
diff --git a/cmd/podman/version.go b/cmd/podman/version.go
index d80f24a14..d81deb696 100644
--- a/cmd/podman/version.go
+++ b/cmd/podman/version.go
@@ -4,6 +4,7 @@ import (
"fmt"
"time"
+ "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
@@ -15,6 +16,19 @@ func versionCmd(c *cli.Context) error {
if err != nil {
errors.Wrapf(err, "unable to determine version")
}
+
+ versionOutputFormat := c.String("format")
+ if versionOutputFormat != "" {
+ var out formats.Writer
+ switch versionOutputFormat {
+ case formats.JSONString:
+ out = formats.JSONStruct{Output: output}
+ default:
+ out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat}
+ }
+ formats.Writer(out).Out()
+ return nil
+ }
fmt.Println("Version: ", output.Version)
fmt.Println("Go Version: ", output.GoVersion)
if output.GitCommit != "" {
@@ -30,8 +44,17 @@ func versionCmd(c *cli.Context) error {
}
// Cli command to print out the full version of podman
-var versionCommand = cli.Command{
- Name: "version",
- Usage: "Display the PODMAN Version Information",
- Action: versionCmd,
-}
+var (
+ versionCommand = cli.Command{
+ Name: "version",
+ Usage: "Display the Podman Version Information",
+ Action: versionCmd,
+ Flags: versionFlags,
+ }
+ versionFlags = []cli.Flag{
+ cli.StringFlag{
+ Name: "format",
+ Usage: "Change the output format to JSON or a Go template",
+ },
+ }
+)
diff --git a/completions/bash/podman b/completions/bash/podman
index 222511a3c..3c6b6ec50 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1906,11 +1906,16 @@ _podman_top() {
}
_podman_version() {
- local options_with_args="
- "
- local boolean_options="
- "
- _complete_ "$options_with_args" "$boolean_options"
+ local boolean_options="
+ --help
+ -h
+ "
+ local options_with_args="
+ --format
+ "
+ local all_options="$options_with_args $boolean_options"
+
+ _complete_ "$options_with_args" "$boolean_options"
}
_podman_save() {
@@ -2173,6 +2178,22 @@ _podman_container_runlabel() {
esac
}
+_podman_container_exists() {
+ local options_with_args="
+ "
+
+ local boolean_options="
+ "
+}
+
+_podman_image_exists() {
+ local options_with_args="
+ "
+
+ local boolean_options="
+ "
+}
+
_podman_pod_create() {
local options_with_args="
--cgroup-parent
diff --git a/docs/podman-container-exists.1.md b/docs/podman-container-exists.1.md
new file mode 100644
index 000000000..76701e2c2
--- /dev/null
+++ b/docs/podman-container-exists.1.md
@@ -0,0 +1,40 @@
+% PODMAN(1) Podman Man Pages
+% Brent Baude
+% November 2018
+# NAME
+podman-container-exists- Check if a container exists in local storage
+
+# SYNOPSIS
+**podman container exists**
+[**-h**|**--help**]
+CONTAINER
+
+# DESCRIPTION
+**podman container exists** checks if a container exists in local storage. The **ID** or **Name**
+of the container may be used as input. Podman will return an exit code
+of `0` when the container is found. A `1` will be returned otherwise. An exit code of `125` indicates there
+was an issue accessing the local storage.
+
+## Examples ##
+
+Check if an container called `webclient` exists in local storage (the container does actually exist).
+```
+$ sudo podman container exists webclient
+$ echo $?
+0
+$
+```
+
+Check if an container called `webbackend` exists in local storage (the container does not actually exist).
+```
+$ sudo podman container exists webbackend
+$ echo $?
+1
+$
+```
+
+## SEE ALSO
+podman(1)
+
+# HISTORY
+November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md
index 67d42bfef..aa5dfa82c 100644
--- a/docs/podman-container.1.md
+++ b/docs/podman-container.1.md
@@ -20,6 +20,7 @@ The container command allows you to manage containers
| create | [podman-create(1)](podman-create.1.md) | Create a new container. |
| diff | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. |
| exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
+| exists | [podman-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage |
| export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
| inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. |
| kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. |
diff --git a/docs/podman-image-exists.1.md b/docs/podman-image-exists.1.md
new file mode 100644
index 000000000..e04c23721
--- /dev/null
+++ b/docs/podman-image-exists.1.md
@@ -0,0 +1,40 @@
+% PODMAN(1) Podman Man Pages
+% Brent Baude
+% November 2018
+# NAME
+podman-image-exists- Check if an image exists in local storage
+
+# SYNOPSIS
+**podman image exists**
+[**-h**|**--help**]
+IMAGE
+
+# DESCRIPTION
+**podman image exists** checks if an image exists in local storage. The **ID** or **Name**
+of the image may be used as input. Podman will return an exit code
+of `0` when the image is found. A `1` will be returned otherwise. An exit code of `125` indicates there
+was an issue accessing the local storage.
+
+## Examples ##
+
+Check if an image called `webclient` exists in local storage (the image does actually exist).
+```
+$ sudo podman image exists webclient
+$ echo $?
+0
+$
+```
+
+Check if an image called `webbackend` exists in local storage (the image does not actually exist).
+```
+$ sudo podman image exists webbackend
+$ echo $?
+1
+$
+```
+
+## SEE ALSO
+podman(1)
+
+# HISTORY
+November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md
index 33de0456f..446f8667d 100644
--- a/docs/podman-image.1.md
+++ b/docs/podman-image.1.md
@@ -14,6 +14,7 @@ The image command allows you to manage images
| Command | Man Page | Description |
| -------- | ----------------------------------------- | ------------------------------------------------------------------------------ |
| build | [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. |
+| exists | [podman-exists(1)](podman-image-exists.1.md) | Check if a image exists in local storage |
| history | [podman-history(1)](podman-history.1.md) | Show the history of an image. |
| import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. |
| inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. |
diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md
index 0c9b9ceed..749a33afd 100644
--- a/docs/podman-version.1.md
+++ b/docs/podman-version.1.md
@@ -16,8 +16,31 @@ OS, and Architecture.
Print usage statement
+**--format**
+
+Change output format to "json" or a Go template.
+
+## Example
+
+A sample output of the `version` command:
+```
+$ podman version
+Version: 0.11.1
+Go Version: go1.11
+Git Commit: "8967a1d691ed44896b81ad48c863033f23c65eb0-dirty"
+Built: Thu Nov 8 22:35:40 2018
+OS/Arch: linux/amd64
+```
+
+Filtering out only the version:
+```
+$ podman version --format '{{.Version}}'
+0.11.2
+```
+
## SEE ALSO
podman(1), crio(8)
## HISTORY
+November 2018, Added --format flag by Tomas Tomecek <ttomecek@redhat.com>
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md
index 5a8f997b8..ce94d7d15 100644
--- a/docs/tutorials/podman_tutorial.md
+++ b/docs/tutorials/podman_tutorial.md
@@ -129,7 +129,7 @@ $ sudo podman inspect -l | grep IPAddress\":
"IPAddress": "10.88.6.140",
```
-Note: The -l is convenience arguement for **latest container**. You can also use the container's ID instead
+Note: The -l is a convenience argument for **latest container**. You can also use the container's ID instead
of -l.
### Testing the httpd server
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/image/errors.go b/libpod/image/errors.go
new file mode 100644
index 000000000..4088946cb
--- /dev/null
+++ b/libpod/image/errors.go
@@ -0,0 +1,15 @@
+package image
+
+import (
+ "errors"
+)
+
+// Copied directly from libpod errors to avoid circular imports
+var (
+ // ErrNoSuchCtr indicates the requested container does not exist
+ ErrNoSuchCtr = errors.New("no such container")
+ // ErrNoSuchPod indicates the requested pod does not exist
+ ErrNoSuchPod = errors.New("no such pod")
+ // ErrNoSuchImage indicates the requested image does not exist
+ ErrNoSuchImage = errors.New("no such image")
+)
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 7e520d97e..434f9031e 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -252,7 +252,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) {
// The image has a registry name in it and we made sure we looked for it locally
// with a tag. It cannot be local.
if decomposedImage.hasRegistry {
- return nil, errors.Errorf("%s", imageError)
+ return nil, errors.Wrapf(ErrNoSuchImage, imageError)
}
@@ -275,7 +275,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) {
return repoImage, nil
}
- return nil, errors.Wrapf(err, imageError)
+ return nil, errors.Wrapf(ErrNoSuchImage, err.Error())
}
// ID returns the image ID as a string
@@ -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/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/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
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/exists_test.go b/test/e2e/exists_test.go
new file mode 100644
index 000000000..9165e8902
--- /dev/null
+++ b/test/e2e/exists_test.go
@@ -0,0 +1,85 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman image|container exists", 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 image exists in local storage by fq name", func() {
+ session := podmanTest.Podman([]string{"image", "exists", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+ It("podman image exists in local storage by short name", func() {
+ session := podmanTest.Podman([]string{"image", "exists", "alpine"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+ It("podman image does not exist in local storage", func() {
+ session := podmanTest.Podman([]string{"image", "exists", "alpine9999"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ })
+ It("podman container exists in local storage by name", func() {
+ setup := podmanTest.RunTopContainer("foobar")
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"container", "exists", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+ It("podman container exists in local storage by container ID", func() {
+ setup := podmanTest.RunTopContainer("")
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+ cid := setup.OutputToString()
+
+ session := podmanTest.Podman([]string{"container", "exists", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+ It("podman container exists in local storage by short container ID", func() {
+ setup := podmanTest.RunTopContainer("")
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+ cid := setup.OutputToString()[0:12]
+
+ session := podmanTest.Podman([]string{"container", "exists", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+ It("podman container does not exist in local storage", func() {
+ session := podmanTest.Podman([]string{"container", "exists", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ })
+
+})
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 beb408fd4..ff166f466 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -609,7 +609,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())
})
})
diff --git a/vendor.conf b/vendor.conf
index c8e968648..0c05e792c 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -92,7 +92,7 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils
github.com/mrunalp/fileutils master
github.com/varlink/go master
-github.com/containers/buildah 795d43e60e5a1ab283981b79eeda1dd14a65a0bd
+github.com/containers/buildah 2ac987a52ff8412fb8f2908a191009751a6a1c62
github.com/Nvveen/Gotty master
github.com/fsouza/go-dockerclient master
github.com/openshift/imagebuilder master
diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go
index 8cfefb3de..6a1400e61 100644
--- a/vendor/github.com/containers/buildah/chroot/run.go
+++ b/vendor/github.com/containers/buildah/chroot/run.go
@@ -955,6 +955,20 @@ func setRlimits(spec *specs.Spec, onlyLower, onlyRaise bool) error {
return nil
}
+func makeReadOnly(mntpoint string, flags uintptr) error {
+ var fs unix.Statfs_t
+ // Make sure it's read-only.
+ if err := unix.Statfs(mntpoint, &fs); err != nil {
+ return errors.Wrapf(err, "error checking if directory %q was bound read-only", mntpoint)
+ }
+ if fs.Flags&unix.ST_RDONLY == 0 {
+ if err := unix.Mount(mntpoint, mntpoint, "bind", flags|unix.MS_REMOUNT, ""); err != nil {
+ return errors.Wrapf(err, "error remounting %s in mount namespace read-only", mntpoint)
+ }
+ }
+ return nil
+}
+
// setupChrootBindMounts actually bind mounts things under the rootfs, and returns a
// callback that will clean up its work.
func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func() error, err error) {
@@ -976,7 +990,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
bindFlags := commonFlags | unix.MS_NODEV
devFlags := commonFlags | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY
procFlags := devFlags | unix.MS_NODEV
- sysFlags := devFlags | unix.MS_NODEV | unix.MS_RDONLY
+ sysFlags := devFlags | unix.MS_NODEV
// Bind /dev read-only.
subDev := filepath.Join(spec.Root.Path, "/dev")
@@ -1030,13 +1044,22 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, errors.Wrapf(err, "error bind mounting /sys from host into mount namespace")
}
}
- // Make sure it's read-only.
- if err = unix.Statfs(subSys, &fs); err != nil {
- return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", subSys)
+ if err := makeReadOnly(subSys, sysFlags); err != nil {
+ return undoBinds, err
}
- if fs.Flags&unix.ST_RDONLY == 0 {
- if err := unix.Mount(subSys, subSys, "bind", sysFlags|unix.MS_REMOUNT, ""); err != nil {
- return undoBinds, errors.Wrapf(err, "error remounting /sys in mount namespace read-only")
+
+ mnts, _ := mount.GetMounts()
+ for _, m := range mnts {
+ if !strings.HasPrefix(m.Mountpoint, "/sys/") &&
+ m.Mountpoint != "/sys" {
+ continue
+ }
+ subSys := filepath.Join(spec.Root.Path, m.Mountpoint)
+ if err := unix.Mount(m.Mountpoint, subSys, "bind", sysFlags, ""); err != nil {
+ return undoBinds, errors.Wrapf(err, "error bind mounting /sys from host into mount namespace")
+ }
+ if err := makeReadOnly(subSys, sysFlags); err != nil {
+ return undoBinds, err
}
}
logrus.Debugf("bind mounted %q to %q", "/sys", filepath.Join(spec.Root.Path, "/sys"))
@@ -1044,10 +1067,6 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
// Add /sys/fs/selinux to the set of masked paths, to ensure that we don't have processes
// attempting to interact with labeling, when they aren't allowed to do so.
spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/selinux")
- // Add /sys/fs/cgroup to the set of masked paths, to ensure that we don't have processes
- // attempting to mess with cgroup configuration, when they aren't allowed to do so.
- spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/cgroup")
-
// Bind mount in everything we've been asked to mount.
for _, m := range spec.Mounts {
// Skip anything that we just mounted.
@@ -1143,7 +1162,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
logrus.Debugf("mounted a tmpfs to %q", target)
}
if err = unix.Statfs(target, &fs); err != nil {
- return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", subSys)
+ return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", target)
}
if uintptr(fs.Flags)&expectedFlags != expectedFlags {
if err := unix.Mount(target, target, "bind", requestFlags|unix.MS_REMOUNT, ""); err != nil {
diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go
index 292ff9541..701241683 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/build.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/build.go
@@ -222,7 +222,7 @@ type Executor struct {
forceRmIntermediateCtrs bool
containerIDs []string // Stores the IDs of the successful intermediate containers used during layer build
imageMap map[string]string // Used to map images that we create to handle the AS construct.
-
+ copyFrom string // Used to keep track of the --from flag from COPY and ADD
}
// withName creates a new child executor that will be used whenever a COPY statement uses --from=NAME.
@@ -826,6 +826,18 @@ func (b *Executor) Execute(ctx context.Context, stage imagebuilder.Stage) error
err error
imgID string
)
+
+ b.copyFrom = ""
+ // Check if --from exists in the step command of COPY or ADD
+ // If it exists, set b.copyfrom to that value
+ for _, n := range step.Flags {
+ if strings.Contains(n, "--from") && (step.Command == "copy" || step.Command == "add") {
+ arr := strings.Split(n, "=")
+ b.copyFrom = b.named[arr[1]].mountPoint
+ break
+ }
+ }
+
// checkForLayers will be true if b.layers is true and a cached intermediate image is found.
// checkForLayers is set to false when either there is no cached image or a break occurs where
// the instructions in the Dockerfile change from a previous build.
@@ -848,6 +860,7 @@ func (b *Executor) Execute(ctx context.Context, stage imagebuilder.Stage) error
if err := b.copyExistingImage(ctx, cacheID); err != nil {
return err
}
+ b.containerIDs = append(b.containerIDs, b.builder.ContainerID)
break
}
@@ -1009,6 +1022,11 @@ func (b *Executor) getFilesToCopy(node *parser.Node) ([]string, error) {
currNode = currNode.Next
continue
}
+ if b.copyFrom != "" {
+ src = append(src, filepath.Join(b.copyFrom, currNode.Value))
+ currNode = currNode.Next
+ continue
+ }
matches, err := filepath.Glob(filepath.Join(b.contextDir, currNode.Value))
if err != nil {
return nil, errors.Wrapf(err, "error finding match for pattern %q", currNode.Value)
@@ -1049,7 +1067,12 @@ func (b *Executor) copiedFilesMatch(node *parser.Node, historyTime *time.Time) (
// Change the time format to ensure we don't run into a parsing error when converting again from string
// to time.Time. It is a known Go issue that the conversions cause errors sometimes, so specifying a particular
// time format here when converting to a string.
- timeIsGreater, err := resolveModifiedTime(b.contextDir, item, historyTime.Format(time.RFC3339Nano))
+ // If the COPY has --from in the command, change the rootdir to mountpoint of the container it is copying from
+ rootdir := b.contextDir
+ if b.copyFrom != "" {
+ rootdir = b.copyFrom
+ }
+ timeIsGreater, err := resolveModifiedTime(rootdir, item, historyTime.Format(time.RFC3339Nano))
if err != nil {
return false, errors.Wrapf(err, "error resolving symlinks and comparing modified times: %q", item)
}
@@ -1342,7 +1365,10 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt
return "", nil, errors.Wrapf(err, "error creating build executor")
}
b := imagebuilder.NewBuilder(options.Args)
- stages := imagebuilder.NewStages(mainNode, b)
+ stages, err := imagebuilder.NewStages(mainNode, b)
+ if err != nil {
+ return "", nil, errors.Wrap(err, "error reading multiple stages")
+ }
return exec.Build(ctx, stages)
}
diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
index 2269b8dcc..edb5837db 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
@@ -140,6 +140,13 @@ func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) {
// Since we are chroot in rootdir, only want the path of the actual filename, i.e path - rootdir.
// +1 to account for the extra "/" (e.g rootdir=/home/user/mydir, path=/home/user/mydir/myfile.json)
err = filepath.Walk(path[len(rootdir)+1:], func(path string, info os.FileInfo, err error) error {
+ // If using cached images, it is possible for files that are being copied to come from
+ // previous build stages. But if using cached images, then the copied file won't exist
+ // since a container won't have been created for the previous build stage and info will be nil.
+ // In that case just return nil and continue on with using the cached image for the whole build process.
+ if info == nil {
+ return nil
+ }
modTime := info.ModTime()
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
// Evaluate any symlink that occurs to get updated modified information
diff --git a/vendor/github.com/containers/buildah/imagebuildah/util.go b/vendor/github.com/containers/buildah/imagebuildah/util.go
index 35dc5438a..4f5301b73 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/util.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/util.go
@@ -111,3 +111,28 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err
func InitReexec() bool {
return buildah.InitReexec()
}
+
+// ReposToMap parses the specified repotags and returns a map with repositories
+// as keys and the corresponding arrays of tags as values.
+func ReposToMap(repotags []string) map[string][]string {
+ // map format is repo -> tag
+ repos := make(map[string][]string)
+ for _, repo := range repotags {
+ var repository, tag string
+ if strings.Contains(repo, ":") {
+ li := strings.LastIndex(repo, ":")
+ repository = repo[0:li]
+ tag = repo[li+1:]
+ } else if len(repo) > 0 {
+ repository = repo
+ tag = "<none>"
+ } else {
+ logrus.Warnf("Found image with empty name")
+ }
+ repos[repository] = append(repos[repository], tag)
+ }
+ if len(repos) == 0 {
+ repos["<none>"] = []string{"<none>"}
+ }
+ return repos
+}
diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go
index 636a204b3..5d2cd6a32 100644
--- a/vendor/github.com/containers/buildah/run.go
+++ b/vendor/github.com/containers/buildah/run.go
@@ -1104,14 +1104,6 @@ func (b *Builder) Run(command []string, options RunOptions) error {
switch isolation {
case IsolationOCI:
- // The default is --rootless=auto, which makes troubleshooting a bit harder.
- // rootlessFlag := []string{"--rootless=false"}
- // for _, arg := range options.Args {
- // if strings.HasPrefix(arg, "--rootless") {
- // rootlessFlag = nil
- // }
- // }
- // options.Args = append(options.Args, rootlessFlag...)
var moreCreateArgs []string
if options.NoPivot {
moreCreateArgs = []string{"--no-pivot"}
@@ -1125,13 +1117,6 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if err := setupRootlessSpecChanges(spec, path, rootUID, rootGID); err != nil {
return err
}
- rootlessFlag := []string{"--rootless=true"}
- for _, arg := range options.Args {
- if strings.HasPrefix(arg, "--rootless") {
- rootlessFlag = nil
- }
- }
- options.Args = append(options.Args, rootlessFlag...)
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path))
default:
err = errors.Errorf("don't know how to run this command")
diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf
index c4410d0af..185cde449 100644
--- a/vendor/github.com/containers/buildah/vendor.conf
+++ b/vendor/github.com/containers/buildah/vendor.conf
@@ -3,9 +3,9 @@ github.com/blang/semver master
github.com/BurntSushi/toml master
github.com/containerd/continuity master
github.com/containernetworking/cni v0.7.0-alpha1
-github.com/containers/image 5e5b67d6b1cf43cc349128ec3ed7d5283a6cc0d1
-github.com/containers/libpod e75469ab99c48e9fbe2b36ade229d384bdea9144
-github.com/containers/storage 09abf3a26b8a3aa69e29fd7faeb260b98d675759
+github.com/containers/image de7be82ee3c7fb676bf6cfdc9090be7cc28f404c
+github.com/containers/libpod fe4f09493f41f675d24c969d1b60d1a6a45ddb9e
+github.com/containers/storage 3161726d1db0d0d4e86a9667dd476f09b997f497
github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1
@@ -38,7 +38,7 @@ github.com/opencontainers/runtime-spec v1.0.0
github.com/opencontainers/runtime-tools master
github.com/opencontainers/selinux master
github.com/openshift/imagebuilder master
-github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460
+github.com/ostreedev/ostree-go 9ab99253d365aac3a330d1f7281cf29f3d22820b
github.com/pborman/uuid master
github.com/pkg/errors master
github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac
diff --git a/vendor/github.com/openshift/imagebuilder/builder.go b/vendor/github.com/openshift/imagebuilder/builder.go
index 1c1afb119..d37965df6 100644
--- a/vendor/github.com/openshift/imagebuilder/builder.go
+++ b/vendor/github.com/openshift/imagebuilder/builder.go
@@ -172,8 +172,11 @@ type Stage struct {
Node *parser.Node
}
-func NewStages(node *parser.Node, b *Builder) Stages {
+func NewStages(node *parser.Node, b *Builder) (Stages, error) {
var stages Stages
+ if err := b.extractHeadingArgsFromNode(node); err != nil {
+ return stages, err
+ }
for i, root := range SplitBy(node, command.From) {
name, _ := extractNameFromNode(root.Children[0])
if len(name) == 0 {
@@ -189,7 +192,36 @@ func NewStages(node *parser.Node, b *Builder) Stages {
Node: root,
})
}
- return stages
+ return stages, nil
+}
+
+func (b *Builder) extractHeadingArgsFromNode(node *parser.Node) error {
+ var args []*parser.Node
+ var children []*parser.Node
+ extract := true
+ for _, child := range node.Children {
+ if extract && child.Value == command.Arg {
+ args = append(args, child)
+ } else {
+ if child.Value == command.From {
+ extract = false
+ }
+ children = append(children, child)
+ }
+ }
+
+ for _, c := range args {
+ step := b.Step()
+ if err := step.Resolve(c); err != nil {
+ return err
+ }
+ if err := b.Run(step, NoopExecutor, false); err != nil {
+ return err
+ }
+ }
+
+ node.Children = children
+ return nil
}
func extractNameFromNode(node *parser.Node) (string, bool) {
@@ -345,6 +377,9 @@ var ErrNoFROM = fmt.Errorf("no FROM statement found")
// is set to the first From found, or left unchanged if already
// set.
func (b *Builder) From(node *parser.Node) (string, error) {
+ if err := b.extractHeadingArgsFromNode(node); err != nil {
+ return "", err
+ }
children := SplitChildren(node, command.From)
switch {
case len(children) == 0:
diff --git a/vendor/github.com/openshift/imagebuilder/dispatchers.go b/vendor/github.com/openshift/imagebuilder/dispatchers.go
index 068d5cc6f..f6510c2fd 100644
--- a/vendor/github.com/openshift/imagebuilder/dispatchers.go
+++ b/vendor/github.com/openshift/imagebuilder/dispatchers.go
@@ -27,11 +27,6 @@ var (
obRgex = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`)
)
-// dispatch with no layer / parsing. This is effectively not a command.
-func nullDispatch(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
- return nil
-}
-
// ENV foo bar
//
// Sets the environment variable foo to bar, also makes interpolation
@@ -181,6 +176,17 @@ func from(b *Builder, args []string, attributes map[string]bool, flagArgs []stri
}
name := args[0]
+
+ // Support ARG before from
+ argStrs := []string{}
+ for n, v := range b.Args {
+ argStrs = append(argStrs, n+"="+v)
+ }
+ var err error
+ if name, err = ProcessWord(name, argStrs); err != nil {
+ return err
+ }
+
// Windows cannot support a container with no base image.
if name == NoBaseImageSpecifier {
if runtime.GOOS == "windows" {
@@ -438,6 +444,7 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs
healthcheck := docker.HealthConfig{}
flags := flag.NewFlagSet("", flag.ContinueOnError)
+ flags.String("start-period", "", "")
flags.String("interval", "", "")
flags.String("timeout", "", "")
flRetries := flags.String("retries", "", "")
@@ -462,6 +469,12 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs
return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
}
+ period, err := parseOptInterval(flags.Lookup("start-period"))
+ if err != nil {
+ return err
+ }
+ healthcheck.StartPeriod = period
+
interval, err := parseOptInterval(flags.Lookup("interval"))
if err != nil {
return err
diff --git a/vendor/github.com/openshift/imagebuilder/evaluator.go b/vendor/github.com/openshift/imagebuilder/evaluator.go
index 83263127e..e1cd5d6d6 100644
--- a/vendor/github.com/openshift/imagebuilder/evaluator.go
+++ b/vendor/github.com/openshift/imagebuilder/evaluator.go
@@ -122,8 +122,7 @@ func (b *Step) Resolve(ast *parser.Node) error {
envs := b.Env
for ast.Next != nil {
ast = ast.Next
- var str string
- str = ast.Value
+ str := ast.Value
if replaceEnvAllowed[cmd] {
var err error
var words []string