| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- /*
- 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"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- 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"
- )
- func (s *composeService) doBuildClassic(ctx context.Context, opts map[string]buildx.Options) (map[string]string, error) {
- var nameDigests = make(map[string]string)
- var errs error
- for name, o := range opts {
- digest, err := s.doBuildClassicSimpleImage(ctx, o)
- if err != nil {
- errs = multierror.Append(errs, err).ErrorOrNil()
- }
- nameDigests[name] = digest
- }
- return nameDigests, errs
- }
- // nolint: gocyclo
- func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
- var (
- buildCtx io.ReadCloser
- dockerfileCtx io.ReadCloser
- contextDir string
- tempDir string
- relDockerfile string
- err error
- )
- dockerfileName := options.Inputs.DockerfilePath
- specifiedContext := options.Inputs.ContextPath
- progBuff := s.stdout()
- buildBuff := s.stdout()
- 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 {
- return "", errors.Wrap(err, "checking context")
- }
- // 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,
- ChownOpts: &idtools.Identity{},
- })
- 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
- }
- // if up to this point nothing has set the context then we must have another
- // way for sending it(streaming) and set the context to the Dockerfile
- if dockerfileCtx != nil && buildCtx == nil {
- buildCtx = dockerfileCtx
- }
- progressOutput := streamformatter.NewProgressOutput(progBuff)
- var body io.Reader
- if buildCtx != nil {
- body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
- }
- configFile := s.configFile()
- creds, err := configFile.GetAllCredentials()
- if err != nil {
- return "", err
- }
- 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()
- response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
- 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 {
- fmt.Fprintf(s.stderr(), "Failed to parse aux message: %s", err)
- } else {
- imageID = result.ID
- }
- }
- err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
- 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 {
- fmt.Fprintln(s.stdout(), "SECURITY WARNING: You are building a Docker "+
- "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)
- }
- if err := ioutil.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
- 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,
- Remove: true,
- 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 {
- v := v
- dest[k] = &v
- }
- return dest
- }
|