diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index dbafd79fd..6a5e09dd9 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -62,35 +62,8 @@ func mustDelegateToMoby(ctxType string) bool { // Exec delegates to com.docker.cli if on moby context func Exec(root *cobra.Command) { - execBinary, err := resolvepath.LookPath(ComDockerCli) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - cmd := exec.Command(execBinary, os.Args[1:]...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - signals := make(chan os.Signal, 1) childExit := make(chan bool) - signal.Notify(signals) // catch all signals - go func() { - for { - select { - case sig := <-signals: - if cmd.Process == nil { - continue // can happen if receiving signal before the process is actually started - } - // nolint errcheck - cmd.Process.Signal(sig) - case <-childExit: - return - } - } - }() - - err = cmd.Run() + err := RunDocker(childExit, os.Args[1:]...) childExit <- true if err != nil { metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus) @@ -110,6 +83,38 @@ func Exec(root *cobra.Command) { os.Exit(0) } +// RunDocker runs a docker command, and forward signals to the shellout command (stops listening to signals when an event is sent to childExit) +func RunDocker(childExit chan bool, args ...string) error { + execBinary, err := resolvepath.LookPath(ComDockerCli) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + cmd := exec.Command(execBinary, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + signals := make(chan os.Signal, 1) + signal.Notify(signals) // catch all signals + go func() { + for { + select { + case sig := <-signals: + if cmd.Process == nil { + continue // can happen if receiving signal before the process is actually started + } + // nolint errcheck + cmd.Process.Signal(sig) + case <-childExit: + return + } + } + }() + + return cmd.Run() +} + // IsDefaultContextCommand checks if the command exists in the classic cli (issues a shellout --help) func IsDefaultContextCommand(dockerCommand string) bool { cmd := exec.Command(ComDockerCli, dockerCommand, "--help") diff --git a/local/compose/build.go b/local/compose/build.go index c4c95f2bd..20db41ab6 100644 --- a/local/compose/build.go +++ b/local/compose/build.go @@ -41,6 +41,18 @@ import ( func (s *composeService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error { opts := map[string]build.Options{} imagesToBuild := []string{} + + // retrieve OS type + info, err := s.apiClient.Info(ctx) + if err != nil { + return err + } + if info.OSType == "windows" { + // no support yet for Windows container builds in Buildkit + // https://docs.docker.com/develop/develop-images/build_enhancements/#limitations + return s.windowsBuild(project, options) + } + for _, service := range project.Services { if service.Build != nil { imageName := getImageName(service, project.Name) @@ -66,7 +78,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti } } - err := s.build(ctx, project, opts, options.Progress) + err = s.build(ctx, project, opts, options.Progress) if err == nil { if len(imagesToBuild) > 0 { utils.DisplayScanSuggestMsg() diff --git a/local/compose/build_win.go b/local/compose/build_win.go new file mode 100644 index 000000000..431eeed01 --- /dev/null +++ b/local/compose/build_win.go @@ -0,0 +1,113 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/cli/mobycli" + + "github.com/compose-spec/compose-go/types" +) + +func (s *composeService) windowsBuild(project *types.Project, options compose.BuildOptions) error { + projectDir := project.WorkingDir + for _, service := range project.Services { + if service.Build != nil { + imageName := getImageName(service, project.Name) + dockerfile := service.Build.Dockerfile + if dockerfile != "" { + if stat, err := os.Stat(projectDir); err == nil && stat.IsDir() { + + dockerfile = filepath.Join(projectDir, dockerfile) + } + } + // build args + cmd := &commandBuilder{ + Path: filepath.Join(projectDir, service.Build.Context), + } + cmd.addParams("--build-arg", options.Args) + cmd.addFlag("--pull", options.Pull) + cmd.addArg("--progress", options.Progress) + + cmd.addList("--cache-from", service.Build.CacheFrom) + cmd.addArg("--file", dockerfile) + cmd.addParams("--label", service.Build.Labels) + cmd.addArg("--network", service.Build.Network) + cmd.addArg("--target", service.Build.Target) + cmd.addArg("--platform", service.Platform) + cmd.addArg("--isolation", service.Build.Isolation) + cmd.addList("--add-host", service.Build.ExtraHosts) + + cmd.addArg("--tag", imageName) + + args := cmd.getArguments() + // shell out to moby cli + childExit := make(chan bool) + err := mobycli.RunDocker(childExit, args...) + childExit <- true + + if err != nil { + return err + } + } + } + return nil +} + +type commandBuilder struct { + Args []string + Path string +} + +func (c *commandBuilder) addArg(name, value string) { + if value != "" { + c.Args = append(c.Args, name, value) + } +} + +func (c *commandBuilder) addFlag(name string, flag bool) { + if flag { + c.Args = append(c.Args, name) + } +} + +func (c *commandBuilder) addParams(name string, params map[string]string) { + if len(params) > 0 { + for k, v := range params { + c.Args = append(c.Args, name, fmt.Sprintf("%s=%s", k, v)) + } + } +} + +func (c *commandBuilder) addList(name string, values []string) { + if len(values) > 0 { + for _, v := range values { + c.Args = append(c.Args, name, v) + } + } +} + +func (c *commandBuilder) getArguments() []string { + cmd := []string{"build"} + cmd = append(cmd, c.Args...) + cmd = append(cmd, c.Path) + return cmd +}