Skip to content

Commit

Permalink
Merge pull request #62 from smarterclayton/shell
Browse files Browse the repository at this point in the history
Add SHELL support
  • Loading branch information
smarterclayton committed Mar 16, 2018
2 parents 604c36b + 1b19da8 commit 6eb335c
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ var evaluateTable = map[string]StepFunc{
command.StopSignal: stopSignal,
command.Arg: arg,
command.Healthcheck: healthcheck,
command.Shell: shell,
}

// builtinAllowedBuildArgs is list of built-in allowed build args
Expand Down
15 changes: 14 additions & 1 deletion builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ func TestBuilder(t *testing.T) {
From: "busybox",
Unrecognized: []Step{
Step{Command: "health", Message: "HEALTH ", Original: "HEALTH NONE", Args: []string{""}, Flags: []string{}, Env: []string{}},
Step{Command: "shell", Message: "SHELL /bin/sh -c", Original: "SHELL [\"/bin/sh\", \"-c\"]", Args: []string{"/bin/sh", "-c"}, Flags: []string{}, Env: []string{}, Attrs: map[string]bool{"json": true}},
Step{Command: "unrecognized", Message: "UNRECOGNIZED ", Original: "UNRECOGNIZED", Args: []string{""}, Env: []string{}},
},
Config: docker.Config{
Expand Down Expand Up @@ -350,6 +349,20 @@ func TestBuilder(t *testing.T) {
{Src: []string{"file4"}, Dest: "/var/www/", Download: true},
},
},
{
Dockerfile: "dockerclient/testdata/Dockerfile.shell",
From: "centos:7",
Config: docker.Config{
Image: "centos:7",
Shell: []string{"/bin/bash", "-xc"},
},
ErrFn: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "multiple FROM statements are not supported")
},
Runs: []Run{
{Shell: true, Args: []string{"env"}},
},
},
}
for i, test := range testCases {
t.Run(fmt.Sprintf("%s %d", test.Dockerfile, i), func(t *testing.T) {
Expand Down
24 changes: 24 additions & 0 deletions dispatchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,26 @@ func arg(b *Builder, args []string, attributes map[string]bool, flagArgs []strin
return nil
}

// SHELL powershell -command
//
// Set the non-default shell to use.
func shell(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
shellSlice := handleJSONArgs(args, attributes)
switch {
case len(shellSlice) == 0:
// SHELL []
return errAtLeastOneArgument("SHELL")
case attributes["json"]:
// SHELL ["powershell", "-command"]
b.RunConfig.Shell = strslice.StrSlice(shellSlice)
// b.RunConfig.Shell = strslice.StrSlice(shellSlice)
default:
// SHELL powershell -command - not JSON
return errNotJSON("SHELL")
}
return nil
}

func errAtLeastOneArgument(command string) error {
return fmt.Errorf("%s requires at least one argument", command)
}
Expand All @@ -544,3 +564,7 @@ func errExactlyOneArgument(command string) error {
func errTooManyArguments(command string) error {
return fmt.Errorf("Bad input to %s, too many arguments", command)
}

func errNotJSON(command string) error {
return fmt.Errorf("%s requires the arguments to be in JSON form", command)
}
21 changes: 17 additions & 4 deletions dockerclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ func (e *ClientExecutor) Prepare(b *imagebuilder.Builder, node *parser.Node, fro

var sharedMount string

defaultShell := b.RunConfig.Shell
if len(defaultShell) == 0 {
defaultShell = []string{"/bin/sh", "-c"}
}

// create a container to execute in, if necessary
mustStart := b.RequiresStart(node)
if e.Container == nil {
Expand Down Expand Up @@ -264,12 +269,12 @@ func (e *ClientExecutor) Prepare(b *imagebuilder.Builder, node *parser.Node, fro
} else {
// TODO; replace me with a better default command
opts.Config.Cmd = []string{"sleep 86400"}
opts.Config.Entrypoint = []string{"/bin/sh", "-c"}
opts.Config.Entrypoint = append([]string{}, defaultShell...)
}
}

if len(opts.Config.Cmd) == 0 {
opts.Config.Entrypoint = []string{"/bin/sh", "-c", "# NOP"}
opts.Config.Entrypoint = append(append([]string{}, defaultShell...), "# NOP")
}

// copy any source content into the temporary mount path
Expand Down Expand Up @@ -581,20 +586,28 @@ func (e *ClientExecutor) Run(run imagebuilder.Run, config docker.Config) error {
args := make([]string, len(run.Args))
copy(args, run.Args)

defaultShell := config.Shell
if len(defaultShell) == 0 {
if runtime.GOOS == "windows" {
defaultShell = []string{"cmd", "/S", "/C"}
} else {
defaultShell = []string{"/bin/sh", "-c"}
}
}
if runtime.GOOS == "windows" {
if len(config.WorkingDir) > 0 {
args[0] = fmt.Sprintf("cd %s && %s", imagebuilder.BashQuote(config.WorkingDir), args[0])
}
// TODO: implement windows ENV
args = append([]string{"cmd", "/S", "/C"}, args...)
args = append(defaultShell, args...)
} else {
if len(config.WorkingDir) > 0 {
args[0] = fmt.Sprintf("cd %s && %s", imagebuilder.BashQuote(config.WorkingDir), args[0])
}
if len(config.Env) > 0 {
args[0] = imagebuilder.ExportEnv(config.Env) + args[0]
}
args = append([]string{"/bin/sh", "-c"}, args...)
args = append(defaultShell, args...)
}

if e.StrictVolumeOwnership && !e.Volumes.Empty() {
Expand Down
40 changes: 40 additions & 0 deletions dockerclient/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ func TestMount(t *testing.T) {
}
}

func TestShell(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "dockerbuild-conformance-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

c, err := docker.NewClientFromEnv()
if err != nil {
t.Fatal(err)
}

e := NewClientExecutor(c)
defer e.Release()

out := &bytes.Buffer{}
e.Out, e.ErrOut = out, out
e.Directory = tmpDir
e.Tag = filepath.Base(tmpDir)
b := imagebuilder.NewBuilder(nil)
node, err := imagebuilder.ParseFile("testdata/Dockerfile.shell")
if err != nil {
t.Fatal(err)
}
if err := e.Prepare(b, node, ""); err != nil {
t.Fatal(err)
}
if err := e.Execute(b, node); err != nil {
t.Fatal(err)
}

if !strings.Contains(out.String(), "+ env\n") {
t.Errorf("Unexpected build output:\n%s", out.String())
}
}

// TestConformance* compares the result of running the direct build against a
// sequential docker build. A dockerfile and git repo is loaded, then each step
// in the file is run sequentially, committing after each step. The generated
Expand Down Expand Up @@ -135,6 +171,10 @@ func TestConformanceInternal(t *testing.T) {
Name: "add",
Dockerfile: "testdata/Dockerfile.add",
},
{
Name: "shell",
Dockerfile: "testdata/Dockerfile.shell",
},
{
Name: "args",
Dockerfile: "testdata/Dockerfile.args",
Expand Down
3 changes: 3 additions & 0 deletions dockerclient/testdata/Dockerfile.shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM centos:7
SHELL ["/bin/bash", "-xc"]
RUN env
1 change: 0 additions & 1 deletion dockerclient/testdata/Dockerfile.unknown
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
FROM busybox
HEALTH NONE
SHELL ["/bin/sh", "-c"]
UNRECOGNIZED
4 changes: 2 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/fsouza/go-dockerclient/container.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion vendor/github.com/fsouza/go-dockerclient/container_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6eb335c

Please sign in to comment.