2021-10-15 10:37:50 +02:00
|
|
|
/*
|
|
|
|
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 (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
|
2022-06-22 16:13:08 -04:00
|
|
|
"github.com/compose-spec/compose-go/types"
|
2021-10-15 10:37:50 +02:00
|
|
|
buildx "github.com/docker/buildx/build"
|
|
|
|
"github.com/docker/cli/cli/command/image/build"
|
|
|
|
dockertypes "github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/cli"
|
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
|
"github.com/docker/docker/pkg/idtools"
|
|
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
|
|
"github.com/docker/docker/pkg/progress"
|
|
|
|
"github.com/docker/docker/pkg/streamformatter"
|
|
|
|
"github.com/docker/docker/pkg/urlutil"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2022-06-22 16:13:08 -04:00
|
|
|
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, opts map[string]buildx.Options) (map[string]string, error) {
|
2021-10-15 10:37:50 +02:00
|
|
|
var nameDigests = make(map[string]string)
|
|
|
|
var errs error
|
2022-06-22 16:13:08 -04:00
|
|
|
err := project.WithServices(nil, func(service types.ServiceConfig) error {
|
|
|
|
imageName := getImageName(service, project.Name)
|
|
|
|
o, ok := opts[imageName]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
2021-10-31 00:52:24 +02:00
|
|
|
digest, err := s.doBuildClassicSimpleImage(ctx, o)
|
2021-10-15 10:37:50 +02:00
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err).ErrorOrNil()
|
|
|
|
}
|
2022-06-22 16:13:08 -04:00
|
|
|
nameDigests[imageName] = digest
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-10-15 10:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nameDigests, errs
|
|
|
|
}
|
|
|
|
|
|
|
|
// nolint: gocyclo
|
2021-10-31 00:52:24 +02:00
|
|
|
func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
|
2021-10-15 10:37:50 +02:00
|
|
|
var (
|
|
|
|
buildCtx io.ReadCloser
|
|
|
|
dockerfileCtx io.ReadCloser
|
|
|
|
contextDir string
|
|
|
|
tempDir string
|
|
|
|
relDockerfile string
|
|
|
|
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
dockerfileName := options.Inputs.DockerfilePath
|
|
|
|
specifiedContext := options.Inputs.ContextPath
|
2022-02-23 11:28:56 +01:00
|
|
|
progBuff := s.stdout()
|
|
|
|
buildBuff := s.stdout()
|
2021-10-15 10:37:50 +02:00
|
|
|
if options.ImageIDFile != "" {
|
|
|
|
// Avoid leaving a stale file if we eventually fail
|
|
|
|
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
|
|
|
|
return "", errors.Wrap(err, "removing image ID file")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case isLocalDir(specifiedContext):
|
|
|
|
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName)
|
|
|
|
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
|
|
|
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
|
|
|
|
dockerfileCtx, err = os.Open(dockerfileName)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Errorf("unable to open Dockerfile: %v", err)
|
|
|
|
}
|
|
|
|
defer dockerfileCtx.Close() // nolint:errcheck
|
|
|
|
}
|
|
|
|
case urlutil.IsGitURL(specifiedContext):
|
|
|
|
tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, dockerfileName)
|
|
|
|
case urlutil.IsURL(specifiedContext):
|
|
|
|
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, dockerfileName)
|
|
|
|
default:
|
|
|
|
return "", errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Errorf("unable to prepare context: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tempDir != "" {
|
|
|
|
defer os.RemoveAll(tempDir) // nolint:errcheck
|
|
|
|
contextDir = tempDir
|
|
|
|
}
|
|
|
|
|
|
|
|
// read from a directory into tar archive
|
|
|
|
if buildCtx == nil {
|
|
|
|
excludes, err := build.ReadDockerignore(contextDir)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
|
2021-10-31 02:01:30 +02:00
|
|
|
return "", errors.Wrap(err, "checking context")
|
2021-10-15 10:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// And canonicalize dockerfile name to a platform-independent one
|
|
|
|
relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
|
|
|
|
|
|
|
|
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, false)
|
|
|
|
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
|
|
ExcludePatterns: excludes,
|
2021-10-31 02:01:30 +02:00
|
|
|
ChownOpts: &idtools.Identity{},
|
2021-10-15 10:37:50 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
|
|
|
|
if dockerfileCtx != nil && buildCtx != nil {
|
|
|
|
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buildCtx, err = build.Compress(buildCtx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2021-10-31 00:52:24 +02:00
|
|
|
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
2022-04-08 11:33:54 +02:00
|
|
|
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
2021-10-15 10:37:50 +02:00
|
|
|
|
2022-02-23 11:28:56 +01:00
|
|
|
configFile := s.configFile()
|
2021-10-31 02:01:30 +02:00
|
|
|
creds, err := configFile.GetAllCredentials()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-10-15 10:37:50 +02:00
|
|
|
authConfigs := make(map[string]dockertypes.AuthConfig, len(creds))
|
|
|
|
for k, auth := range creds {
|
|
|
|
authConfigs[k] = dockertypes.AuthConfig(auth)
|
|
|
|
}
|
|
|
|
buildOptions := imageBuildOptions(options)
|
|
|
|
buildOptions.Version = dockertypes.BuilderV1
|
|
|
|
buildOptions.Dockerfile = relDockerfile
|
|
|
|
buildOptions.AuthConfigs = authConfigs
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
2022-02-23 11:28:56 +01:00
|
|
|
response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
|
2021-10-15 10:37:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer response.Body.Close() // nolint:errcheck
|
|
|
|
|
|
|
|
imageID := ""
|
|
|
|
aux := func(msg jsonmessage.JSONMessage) {
|
|
|
|
var result dockertypes.BuildResult
|
|
|
|
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
2022-02-23 11:28:56 +01:00
|
|
|
fmt.Fprintf(s.stderr(), "Failed to parse aux message: %s", err)
|
2021-10-15 10:37:50 +02:00
|
|
|
} else {
|
|
|
|
imageID = result.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-23 11:28:56 +01:00
|
|
|
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
|
2021-10-15 10:37:50 +02:00
|
|
|
if err != nil {
|
|
|
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
|
|
|
// If no error code is set, default to 1
|
|
|
|
if jerr.Code == 0 {
|
|
|
|
jerr.Code = 1
|
|
|
|
}
|
|
|
|
return "", cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
|
|
}
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Windows: show error message about modified file permissions if the
|
|
|
|
// daemon isn't running Windows.
|
|
|
|
if response.OSType != "windows" && runtime.GOOS == "windows" {
|
|
|
|
// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
2022-02-23 11:28:56 +01:00
|
|
|
fmt.Fprintln(s.stdout(), "SECURITY WARNING: You are building a Docker "+
|
2021-10-15 10:37:50 +02:00
|
|
|
"image from Windows against a non-Windows Docker host. All files and "+
|
|
|
|
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
|
|
|
"It is recommended to double check and reset permissions for sensitive "+
|
|
|
|
"files and directories.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if options.ImageIDFile != "" {
|
|
|
|
if imageID == "" {
|
|
|
|
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
|
|
|
|
}
|
2022-07-13 02:53:08 +02:00
|
|
|
if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0o666); err != nil {
|
2021-10-15 10:37:50 +02:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return imageID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isLocalDir(c string) bool {
|
|
|
|
_, err := os.Stat(c)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
|
|
|
|
return dockertypes.ImageBuildOptions{
|
|
|
|
Tags: options.Tags,
|
|
|
|
NoCache: options.NoCache,
|
2021-12-08 03:54:45 +09:00
|
|
|
Remove: true,
|
2021-10-15 10:37:50 +02:00
|
|
|
PullParent: options.Pull,
|
|
|
|
BuildArgs: toMapStringStringPtr(options.BuildArgs),
|
|
|
|
Labels: options.Labels,
|
|
|
|
NetworkMode: options.NetworkMode,
|
|
|
|
ExtraHosts: options.ExtraHosts,
|
|
|
|
Target: options.Target,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toMapStringStringPtr(source map[string]string) map[string]*string {
|
|
|
|
dest := make(map[string]*string)
|
|
|
|
for k, v := range source {
|
2021-11-05 21:07:37 +01:00
|
|
|
v := v
|
2021-10-15 10:37:50 +02:00
|
|
|
dest[k] = &v
|
|
|
|
}
|
|
|
|
return dest
|
|
|
|
}
|