Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SHELL support #62

Merged
merged 2 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.