diff --git a/local/compose/build.go b/local/compose/build.go index c4c95f2b..20db41ab 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 00000000..a05fa7c2 --- /dev/null +++ b/local/compose/build_win.go @@ -0,0 +1,110 @@ +/* + 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/local/moby" + + "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 + err := moby.Exec(args) + 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 +} diff --git a/local/moby/exec.go b/local/moby/exec.go new file mode 100644 index 00000000..97ee4377 --- /dev/null +++ b/local/moby/exec.go @@ -0,0 +1,62 @@ +/* + 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 moby + +import ( + "os" + "os/exec" + "os/signal" + + "github.com/docker/compose-cli/cli/mobycli/resolvepath" +) + +// ComDockerCli name of the classic cli binary +const ComDockerCli = "com.docker.cli" + +// Exec delegates to com.docker.cli +func Exec(args []string) error { + // look up the path of the classic cli binary + execBinary, err := resolvepath.LookPath(ComDockerCli) + if err != nil { + return err + } + cmd := exec.Command(execBinary, args...) + 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() + childExit <- true + return err +}