Skip to content

Commit

Permalink
Configure "podman build" to produce images with Docker manifests.
Browse files Browse the repository at this point in the history
Podman's default is to produce OCI manifests and metadata, while
Docker (unsurprisingly) produces the Docker filetypes. This can lead
to behavior differences down the line, especially once container
registries are added to the mix.
  • Loading branch information
benluddy committed May 26, 2020
1 parent 18b8ef3 commit 5eb38ea
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 8 deletions.
10 changes: 10 additions & 0 deletions pkg/containertools/containertool.go
Expand Up @@ -20,6 +20,16 @@ func (t ContainerTool) String() (s string) {
return
}

func (t ContainerTool) CommandFactory() CommandFactory {
switch t {
case PodmanTool:
return &PodmanCommandFactory{}
case DockerTool:
return &DockerCommandFactory{}
}
return &StubCommandFactory{}
}

func NewContainerTool(s string, defaultTool ContainerTool) (t ContainerTool) {
switch s {
case "podman":
Expand Down
9 changes: 9 additions & 0 deletions pkg/containertools/factory.go
@@ -0,0 +1,9 @@
package containertools

import (
"os/exec"
)

type CommandFactory interface {
BuildCommand(o BuildOptions) (*exec.Cmd, error)
}
31 changes: 31 additions & 0 deletions pkg/containertools/factory_docker.go
@@ -0,0 +1,31 @@
package containertools

import (
"fmt"
"os/exec"
)

type DockerCommandFactory struct{}

func (d *DockerCommandFactory) BuildCommand(o BuildOptions) (*exec.Cmd, error) {
args := []string{"build"}

if o.format != "" && o.format != "docker" {
return nil, fmt.Errorf(`format %q invalid for "docker build"`, o.format)
}

if o.dockerfile != "" {
args = append(args, "-f", o.dockerfile)
}

for _, tag := range o.tags {
args = append(args, "-t", tag)
}

if o.context == "" {
return nil, fmt.Errorf("context not provided")
}
args = append(args, o.context)

return exec.Command("docker", args...), nil
}
33 changes: 33 additions & 0 deletions pkg/containertools/factory_podman.go
@@ -0,0 +1,33 @@
package containertools

import (
"fmt"
"os/exec"
)

type PodmanCommandFactory struct{}

func (p *PodmanCommandFactory) BuildCommand(o BuildOptions) (*exec.Cmd, error) {
args := []string{"build"}

if o.format != "" {
args = append(args, "--format", o.format)
} else {
args = append(args, "--format", "docker")
}

if o.dockerfile != "" {
args = append(args, "-f", o.dockerfile)
}

for _, tag := range o.tags {
args = append(args, "-t", tag)
}

if o.context == "" {
return nil, fmt.Errorf("context not provided")
}
args = append(args, o.context)

return exec.Command("podman", args...), nil
}
14 changes: 14 additions & 0 deletions pkg/containertools/factory_stub.go
@@ -0,0 +1,14 @@
package containertools

import (
"fmt"
"os/exec"
)

type StubCommandFactory struct {
name string
}

func (s *StubCommandFactory) BuildCommand(o BuildOptions) (*exec.Cmd, error) {
return nil, fmt.Errorf(`"build" is not supported by tool %q`, s.name)
}
152 changes: 152 additions & 0 deletions pkg/containertools/factory_test.go
@@ -0,0 +1,152 @@
package containertools

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBuildCommand(t *testing.T) {
for _, tt := range []struct {
Name string
Factory CommandFactory
Options BuildOptions
Args []string
}{
{
Name: "docker defaults",
Factory: &DockerCommandFactory{},
Options: DefaultBuildOptions(),
Args: []string{
"docker", "build", ".",
},
},
{
Name: "docker unsupported format",
Factory: &DockerCommandFactory{},
Options: BuildOptions{
context: ".",
format: "oci",
},
},
{
Name: "docker context",
Factory: &DockerCommandFactory{},
Options: BuildOptions{
context: "foo",
},
Args: []string{
"docker", "build", "foo",
},
},
{
Name: "docker dockerfile",
Factory: &DockerCommandFactory{},
Options: BuildOptions{
context: ".",
dockerfile: "foo",
},
Args: []string{
"docker", "build", "-f", "foo", ".",
},
},
{
Name: "docker single tag",
Factory: &DockerCommandFactory{},
Options: BuildOptions{
context: ".",
tags: []string{"foo"},
},
Args: []string{
"docker", "build", "-t", "foo", ".",
},
},
{
Name: "docker multiple tags",
Factory: &DockerCommandFactory{},
Options: BuildOptions{
context: ".",
tags: []string{"foo", "bar"},
},
Args: []string{
"docker", "build", "-t", "foo", "-t", "bar", ".",
},
},
{
Name: "podman defaults",
Factory: &PodmanCommandFactory{},
Options: DefaultBuildOptions(),
Args: []string{
"podman", "build", "--format", "docker", ".",
},
},
{
Name: "podman oci format",
Factory: &PodmanCommandFactory{},
Options: BuildOptions{
context: ".",
format: "oci",
},
Args: []string{
"podman", "build", "--format", "oci", ".",
},
},
{
Name: "podman context",
Factory: &PodmanCommandFactory{},
Options: BuildOptions{
context: "foo",
},
Args: []string{
"podman", "build", "--format", "docker", "foo",
},
},
{
Name: "podman dockerfile",
Factory: &PodmanCommandFactory{},
Options: BuildOptions{
context: ".",
dockerfile: "foo",
},
Args: []string{
"podman", "build", "--format", "docker", "-f", "foo", ".",
},
},
{
Name: "podman single tag",
Factory: &PodmanCommandFactory{},
Options: BuildOptions{
context: ".",
tags: []string{"foo"},
},
Args: []string{
"podman", "build", "--format", "docker", "-t", "foo", ".",
},
},
{
Name: "podman multiple tags",
Factory: &PodmanCommandFactory{},
Options: BuildOptions{
context: ".",
tags: []string{"foo", "bar"},
},
Args: []string{
"podman", "build", "--format", "docker", "-t", "foo", "-t", "bar", ".",
},
},
} {
t.Run(tt.Name, func(t *testing.T) {
require := require.New(t)

cmd, err := tt.Factory.BuildCommand(tt.Options)
if tt.Args == nil {
require.Nil(cmd)
require.Error(err)
} else {
require.NotNil(cmd)
require.NoError(err)
require.Equal(tt.Args, cmd.Args)
}
})
}
}
35 changes: 35 additions & 0 deletions pkg/containertools/option_build.go
@@ -0,0 +1,35 @@
package containertools

type BuildOptions struct {
format string
tags []string
dockerfile string
context string
}

func (o *BuildOptions) SetFormatDocker() {
o.format = "docker"
}

func (o *BuildOptions) SetFormatOCI() {
o.format = "oci"
}

func (o *BuildOptions) AddTag(tag string) {
o.tags = append(o.tags, tag)
}

func (o *BuildOptions) SetDockerfile(dockerfile string) {
o.dockerfile = dockerfile
}

func (o *BuildOptions) SetContext(context string) {
o.context = context
}

func DefaultBuildOptions() BuildOptions {
var o BuildOptions
o.SetFormatDocker()
o.SetContext(".")
return o
}
17 changes: 9 additions & 8 deletions pkg/containertools/command.go → pkg/containertools/runner.go
Expand Up @@ -28,7 +28,7 @@ type ContainerCommandRunner struct {
// CommandRunner to run commands with that cli tool
func NewCommandRunner(containerTool ContainerTool, logger *logrus.Entry) CommandRunner {
r := ContainerCommandRunner{
logger: logger,
logger: logger,
containerTool: containerTool,
}
return &r
Expand Down Expand Up @@ -59,15 +59,16 @@ func (r *ContainerCommandRunner) Pull(image string) error {

// Build takes a dockerfile and a tag and builds a container image
func (r *ContainerCommandRunner) Build(dockerfile, tag string) error {
args := []string{"build", "-f", dockerfile}

o := DefaultBuildOptions()
if tag != "" {
args = append(args, "-t", tag)
o.AddTag(tag)
}
o.SetDockerfile(dockerfile)
o.SetContext(".")
command, err := r.containerTool.CommandFactory().BuildCommand(o)
if err != nil {
return fmt.Errorf("unable to perform build: %v", err)
}

args = append(args, ".")

command := exec.Command(r.containerTool.String(), args...)

r.logger.Infof("running %s build", r.containerTool)
r.logger.Infof("%s", command.Args)
Expand Down

0 comments on commit 5eb38ea

Please sign in to comment.