From d26266659d8649b36b91e8f8f78f8073007554ac Mon Sep 17 00:00:00 2001
From: baude <bbaude@redhat.com>
Date: Sat, 10 Feb 2018 11:57:05 -0600
Subject: [PATCH] Honor ENTRYPOINT in image

When an image has an ENTRYPOINT defined, we should be honoring it. The
problem is described in issue #321.

Also, added buildah binary to test runtimes for testing entrypoint and
will also allow us to test podman build as well.

Signed-off-by: baude <bbaude@redhat.com>

Closes: #322
Approved by: rhatdan
---
 Dockerfile                      |   9 +++
 Dockerfile.CentOS               |   9 +++
 Dockerfile.Fedora               |   9 +++
 cmd/podman/common.go            |   2 +-
 cmd/podman/create.go            |  29 +++++----
 cmd/podman/inspect.go           |   3 +-
 test/e2e/libpod_suite_test.go   |  11 ++++
 test/e2e/run_entrypoint_test.go | 100 ++++++++++++++++++++++++++++++++
 8 files changed, 160 insertions(+), 12 deletions(-)
 create mode 100644 test/e2e/run_entrypoint_test.go

diff --git a/Dockerfile b/Dockerfile
index 941a7e0d3b..6a29269b36 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y \
     e2fslibs-dev \
     gawk \
     gettext \
+    go-md2man \
     iptables \
     pkg-config \
     libaio-dev \
@@ -96,6 +97,14 @@ RUN set -x \
        && cp "$GOPATH"/bin/crictl /usr/bin/ \
        && rm -rf "$GOPATH"
 
+# Install buildah
+RUN set -x \
+       && export GOPATH=/go \
+       && git clone https://github.com/projectatomic/buildah "$GOPATH/src/github.com/projectatomic/buildah" \
+       && cd "$GOPATH/src/github.com/projectatomic/buildah" \
+       && make \
+       && make install
+
 # Install ginkgo
 RUN set -x \
        && export GOPATH=/go \
diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS
index 217837ce05..cecb354ba2 100644
--- a/Dockerfile.CentOS
+++ b/Dockerfile.CentOS
@@ -9,6 +9,7 @@ RUN yum -y install btrfs-progs-devel \
               glib2-devel \
               gnupg \
               golang \
+              golang-github-cpuguy83-go-md2man \
               gpgme-devel \
               libassuan-devel \
               libseccomp-devel \
@@ -34,6 +35,14 @@ RUN set -x \
        && cp bin/* /usr/libexec/cni \
        && rm -rf "$GOPATH"
 
+# Install buildah
+RUN set -x \
+       && export GOPATH=/go \
+       && git clone https://github.com/projectatomic/buildah "$GOPATH/src/github.com/projectatomic/buildah" \
+       && cd "$GOPATH/src/github.com/projectatomic/buildah" \
+       && make \
+       && make install
+
 # Install ginkgo
 RUN set -x \
        && export GOPATH=/go \
diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora
index f15734f05e..aa10a054f6 100644
--- a/Dockerfile.Fedora
+++ b/Dockerfile.Fedora
@@ -10,6 +10,7 @@ RUN dnf -y install btrfs-progs-devel \
               glib2-devel \
               gnupg \
               golang \
+              golang-github-cpuguy83-go-md2man \
               gpgme-devel \
               libassuan-devel \
               libseccomp-devel \
@@ -36,6 +37,14 @@ RUN set -x \
        && cp bin/* /usr/libexec/cni \
        && rm -rf "$GOPATH"
 
+# Install buildah
+RUN set -x \
+       && export GOPATH=/go \
+       && git clone https://github.com/projectatomic/buildah "$GOPATH/src/github.com/projectatomic/buildah" \
+       && cd "$GOPATH/src/github.com/projectatomic/buildah" \
+       && make \
+       && make install
+
 # Install ginkgo
 RUN set -x \
        && export GOPATH=/go \
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index e0aaf52c08..657535e63e 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -173,7 +173,7 @@ var createFlags = []cli.Flag{
 		Name:  "dns-search",
 		Usage: "Set custom DNS search domains",
 	},
-	cli.StringFlag{
+	cli.StringSliceFlag{
 		Name:  "entrypoint",
 		Usage: "Overwrite the default ENTRYPOINT of the image",
 	},
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 340c036cc1..f847b2d227 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -81,7 +81,7 @@ type createConfig struct {
 	DNSOpt             []string          //dns-opt
 	DNSSearch          []string          //dns-search
 	DNSServers         []string          //dns
-	Entrypoint         string            //entrypoint
+	Entrypoint         []string          //entrypoint
 	Env                map[string]string //env
 	ExposedPorts       map[nat.Port]struct{}
 	GroupAdd           []uint32 // group-add
@@ -419,14 +419,14 @@ func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, s
 // 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, imageName string, data *inspect.ImageData) (*createConfig, error) {
-	var command []string
+	var inputCommand, command []string
 	var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
 	var blkioWeight uint16
 
 	imageID := data.ID
 
 	if len(c.Args()) > 1 {
-		command = c.Args()[1:]
+		inputCommand = c.Args()[1:]
 	}
 
 	sysctl, err := validateSysctl(c.StringSlice("sysctl"))
@@ -567,15 +567,24 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
 		workDir = data.Config.WorkingDir
 	}
 
-	// COMMAND
-	if len(command) == 0 {
-		command = data.Config.Cmd
+	// ENTRYPOINT
+	// User input entrypoint takes priority over image entrypoint
+	entrypoint := c.StringSlice("entrypoint")
+	if len(entrypoint) == 0 {
+		entrypoint = data.Config.Entrypoint
 	}
 
-	// ENTRYPOINT
-	entrypoint := c.String("entrypoint")
-	if entrypoint == "" {
-		entrypoint = strings.Join(data.Config.Entrypoint, " ")
+	// Build the command
+	// If we have an entry point, it goes first
+	if len(entrypoint) > 0 {
+		command = entrypoint
+	}
+	if len(inputCommand) > 0 {
+		// User command overrides data CMD
+		command = append(command, inputCommand...)
+	} else if len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") {
+		// If not user command, add CMD
+		command = append(command, data.Config.Cmd...)
 	}
 
 	// EXPOSED PORTS
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index ba7b17ed71..84107f3dba 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"encoding/json"
+	"strings"
 
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/pkg/errors"
@@ -223,7 +224,7 @@ func getCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI
 			OpenStdin:   config.Stdin,
 			StopSignal:  config.StopSignal,
 			Cmd:         config.Spec.Process.Args,
-			Entrypoint:  createArtifact.Entrypoint,
+			Entrypoint:  strings.Join(createArtifact.Entrypoint, " "),
 		},
 	}
 	return data, nil
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index b8e650e308..afe91134e1 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -449,3 +449,14 @@ func (p *PodmanTest) GetContainerStatus() string {
 	session.WaitWithDefaultTimeout()
 	return session.OutputToString()
 }
+
+// BuildImage uses podman build and buildah to build an image
+// called imageName based on a string dockerfile
+func (p *PodmanTest) BuildImage(dockerfile, imageName string) {
+	dockerfilePath := filepath.Join(p.TempDir, "Dockerfile")
+	err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
+	Expect(err).To(BeNil())
+	session := p.Podman([]string{"build", "-t", imageName, "--file", dockerfilePath, p.TempDir})
+	session.Wait(120)
+	Expect(session.ExitCode()).To(Equal(0))
+}
diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go
new file mode 100644
index 0000000000..2ae2829679
--- /dev/null
+++ b/test/e2e/run_entrypoint_test.go
@@ -0,0 +1,100 @@
+package integration
+
+import (
+	"os"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman run entrypoint", func() {
+	var (
+		tempdir    string
+		err        error
+		podmanTest PodmanTest
+	)
+
+	BeforeEach(func() {
+		tempdir, err = CreateTempDirInTempDir()
+		if err != nil {
+			os.Exit(1)
+		}
+		podmanTest = PodmanCreate(tempdir)
+		podmanTest.RestoreArtifact(ALPINE)
+	})
+
+	AfterEach(func() {
+		podmanTest.Cleanup()
+
+	})
+
+	It("podman run entrypoint", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "foobar.com/entrypoint:latest"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(len(session.OutputToStringArray())).To(Equal(3))
+	})
+
+	It("podman run entrypoint with cmd", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+CMD [ "-v"]
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "foobar.com/entrypoint:latest"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(len(session.OutputToStringArray())).To(Equal(5))
+	})
+
+	It("podman run entrypoint with user cmd overrides image cmd", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+CMD [ "-v"]
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "foobar.com/entrypoint:latest", "-i"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(len(session.OutputToStringArray())).To(Equal(6))
+	})
+
+	It("podman run entrypoint with user cmd no image cmd", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "foobar.com/entrypoint:latest", "-i"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(len(session.OutputToStringArray())).To(Equal(6))
+	})
+
+	It("podman run user entrypoint overrides image entrypoint and image cmd", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+CMD ["-i"]
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue())
+	})
+
+	It("podman run user entrypoint with command overrides image entrypoint and image cmd", func() {
+		dockerfile := `FROM docker.io/library/alpine:latest
+CMD ["-i"]
+ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
+`
+		podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest")
+		session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest", "-r"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(session.LineInOuputStartsWith("Linux")).To(BeFalse())
+	})
+})