builder.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package docker
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "time"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/pkg/archive"
  13. "github.com/docker/docker/pkg/fileutils"
  14. "github.com/moby/buildkit/frontend/dockerfile/dockerignore"
  15. "github.com/moby/moby/pkg/jsonmessage"
  16. "github.com/moby/moby/pkg/stringid"
  17. "github.com/moby/term"
  18. "github.com/pkg/errors"
  19. )
  20. type BuildOpts struct {
  21. ImageRepo string
  22. Tag string
  23. CurrentTag string
  24. BuildContext string
  25. DockerfilePath string
  26. IsDockerfileInCtx bool
  27. UseCache bool
  28. Env map[string]string
  29. LogFile *os.File
  30. }
  31. // BuildLocal
  32. func (a *Agent) BuildLocal(ctx context.Context, opts *BuildOpts) (err error) {
  33. dockerfilePath := opts.DockerfilePath
  34. // attempt to read dockerignore file and paths
  35. dockerIgnoreBytes, _ := ioutil.ReadFile(".dockerignore")
  36. var excludes []string
  37. if len(dockerIgnoreBytes) != 0 {
  38. excludes, err = dockerignore.ReadAll(bytes.NewBuffer(dockerIgnoreBytes))
  39. if err != nil {
  40. return err
  41. }
  42. }
  43. excludes = trimBuildFilesFromExcludes(excludes, dockerfilePath)
  44. tar, err := archive.TarWithOptions(opts.BuildContext, &archive.TarOptions{
  45. ExcludePatterns: excludes,
  46. })
  47. if err != nil {
  48. return err
  49. }
  50. var writer io.Writer = os.Stderr
  51. if opts.LogFile != nil {
  52. writer = io.MultiWriter(os.Stderr, opts.LogFile)
  53. }
  54. if !opts.IsDockerfileInCtx {
  55. dockerfileCtx, err := os.Open(dockerfilePath)
  56. if err != nil {
  57. return errors.Errorf("unable to open Dockerfile: %v", err)
  58. }
  59. defer dockerfileCtx.Close()
  60. // add the dockerfile to the build context
  61. tar, dockerfilePath, err = AddDockerfileToBuildContext(dockerfileCtx, tar)
  62. if err != nil {
  63. return err
  64. }
  65. }
  66. buildArgs := make(map[string]*string)
  67. for key, val := range opts.Env {
  68. valCopy := val
  69. buildArgs[key] = &valCopy
  70. }
  71. // attach BUILDKIT_INLINE_CACHE=1 by default, to take advantage of caching
  72. inlineCacheVal := "1"
  73. buildArgs["BUILDKIT_INLINE_CACHE"] = &inlineCacheVal
  74. out, err := a.ImageBuild(ctx, tar, types.ImageBuildOptions{
  75. Dockerfile: dockerfilePath,
  76. BuildArgs: buildArgs,
  77. Tags: []string{
  78. fmt.Sprintf("%s:%s", opts.ImageRepo, opts.Tag),
  79. },
  80. CacheFrom: []string{
  81. fmt.Sprintf("%s:%s", opts.ImageRepo, opts.CurrentTag),
  82. },
  83. Remove: true,
  84. Platform: "linux/amd64",
  85. })
  86. if err != nil {
  87. return err
  88. }
  89. defer out.Body.Close()
  90. termFd, isTerm := term.GetFdInfo(os.Stderr)
  91. return jsonmessage.DisplayJSONMessagesStream(out.Body, writer, termFd, isTerm, nil)
  92. }
  93. func trimBuildFilesFromExcludes(excludes []string, dockerfile string) []string {
  94. if keep, _ := fileutils.Matches(".dockerignore", excludes); keep {
  95. excludes = append(excludes, "!.dockerignore")
  96. }
  97. if keep, _ := fileutils.Matches(dockerfile, excludes); keep {
  98. excludes = append(excludes, "!"+dockerfile)
  99. }
  100. return excludes
  101. }
  102. // AddDockerfileToBuildContext from a ReadCloser, returns a new archive and
  103. // the relative path to the dockerfile in the context.
  104. func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) {
  105. file, err := ioutil.ReadAll(dockerfileCtx)
  106. dockerfileCtx.Close()
  107. if err != nil {
  108. return nil, "", err
  109. }
  110. now := time.Now()
  111. hdrTmpl := &tar.Header{
  112. Mode: 0o600,
  113. Uid: 0,
  114. Gid: 0,
  115. ModTime: now,
  116. Typeflag: tar.TypeReg,
  117. AccessTime: now,
  118. ChangeTime: now,
  119. }
  120. randomName := ".dockerfile." + stringid.GenerateRandomID()[:20]
  121. buildCtx = archive.ReplaceFileTarWrapper(buildCtx, map[string]archive.TarModifierFunc{
  122. // Add the dockerfile with a random filename
  123. randomName: func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  124. return hdrTmpl, file, nil
  125. },
  126. // Update .dockerignore to include the random filename
  127. ".dockerignore": func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  128. if h == nil {
  129. h = hdrTmpl
  130. }
  131. b := &bytes.Buffer{}
  132. if content != nil {
  133. if _, err := b.ReadFrom(content); err != nil {
  134. return nil, nil, err
  135. }
  136. } else {
  137. b.WriteString(".dockerignore")
  138. }
  139. b.WriteString("\n" + randomName + "\n")
  140. return h, b.Bytes(), nil
  141. },
  142. })
  143. return buildCtx, randomName, nil
  144. }