builder.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. package buildkit
  2. import (
  3. "context"
  4. "io"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/containerd/containerd/content"
  9. "github.com/containerd/containerd/platforms"
  10. "github.com/docker/docker/api/types"
  11. "github.com/docker/docker/api/types/backend"
  12. "github.com/docker/docker/builder"
  13. "github.com/docker/docker/daemon/images"
  14. "github.com/docker/docker/pkg/streamformatter"
  15. "github.com/docker/docker/pkg/system"
  16. controlapi "github.com/moby/buildkit/api/services/control"
  17. "github.com/moby/buildkit/control"
  18. "github.com/moby/buildkit/identity"
  19. "github.com/moby/buildkit/session"
  20. "github.com/moby/buildkit/util/tracing"
  21. "github.com/pkg/errors"
  22. "golang.org/x/sync/errgroup"
  23. grpcmetadata "google.golang.org/grpc/metadata"
  24. )
  25. // Opt is option struct required for creating the builder
  26. type Opt struct {
  27. SessionManager *session.Manager
  28. Root string
  29. Dist images.DistributionServices
  30. }
  31. // Builder can build using BuildKit backend
  32. type Builder struct {
  33. controller *control.Controller
  34. reqBodyHandler *reqBodyHandler
  35. mu sync.Mutex
  36. jobs map[string]*buildJob
  37. }
  38. // New creates a new builder
  39. func New(opt Opt) (*Builder, error) {
  40. reqHandler := newReqBodyHandler(tracing.DefaultTransport)
  41. c, err := newController(reqHandler, opt)
  42. if err != nil {
  43. return nil, err
  44. }
  45. b := &Builder{
  46. controller: c,
  47. reqBodyHandler: reqHandler,
  48. jobs: map[string]*buildJob{},
  49. }
  50. return b, nil
  51. }
  52. // Cancel cancels a build using ID
  53. func (b *Builder) Cancel(ctx context.Context, id string) error {
  54. b.mu.Lock()
  55. if j, ok := b.jobs[id]; ok && j.cancel != nil {
  56. j.cancel()
  57. }
  58. b.mu.Unlock()
  59. return nil
  60. }
  61. // DiskUsage returns a report about space used by build cache
  62. func (b *Builder) DiskUsage(ctx context.Context) ([]*types.BuildCache, error) {
  63. duResp, err := b.controller.DiskUsage(ctx, &controlapi.DiskUsageRequest{})
  64. if err != nil {
  65. return nil, err
  66. }
  67. var items []*types.BuildCache
  68. for _, r := range duResp.Record {
  69. items = append(items, &types.BuildCache{
  70. ID: r.ID,
  71. Mutable: r.Mutable,
  72. InUse: r.InUse,
  73. Size: r.Size_,
  74. CreatedAt: r.CreatedAt,
  75. LastUsedAt: r.LastUsedAt,
  76. UsageCount: int(r.UsageCount),
  77. Parent: r.Parent,
  78. Description: r.Description,
  79. })
  80. }
  81. return items, nil
  82. }
  83. // Prune clears all reclaimable build cache
  84. func (b *Builder) Prune(ctx context.Context) (int64, error) {
  85. ch := make(chan *controlapi.UsageRecord)
  86. eg, ctx := errgroup.WithContext(ctx)
  87. eg.Go(func() error {
  88. defer close(ch)
  89. return b.controller.Prune(&controlapi.PruneRequest{}, &pruneProxy{
  90. streamProxy: streamProxy{ctx: ctx},
  91. ch: ch,
  92. })
  93. })
  94. var size int64
  95. eg.Go(func() error {
  96. for r := range ch {
  97. size += r.Size_
  98. }
  99. return nil
  100. })
  101. if err := eg.Wait(); err != nil {
  102. return 0, err
  103. }
  104. return size, nil
  105. }
  106. // Build executes a build request
  107. func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder.Result, error) {
  108. var rc = opt.Source
  109. if buildID := opt.Options.BuildID; buildID != "" {
  110. b.mu.Lock()
  111. upload := false
  112. if strings.HasPrefix(buildID, "upload-request:") {
  113. upload = true
  114. buildID = strings.TrimPrefix(buildID, "upload-request:")
  115. }
  116. if _, ok := b.jobs[buildID]; !ok {
  117. b.jobs[buildID] = newBuildJob()
  118. }
  119. j := b.jobs[buildID]
  120. var cancel func()
  121. ctx, cancel = context.WithCancel(ctx)
  122. j.cancel = cancel
  123. b.mu.Unlock()
  124. if upload {
  125. ctx2, cancel := context.WithTimeout(ctx, 5*time.Second)
  126. defer cancel()
  127. err := j.SetUpload(ctx2, rc)
  128. return nil, err
  129. }
  130. if remoteContext := opt.Options.RemoteContext; remoteContext == "upload-request" {
  131. ctx2, cancel := context.WithTimeout(ctx, 5*time.Second)
  132. defer cancel()
  133. var err error
  134. rc, err = j.WaitUpload(ctx2)
  135. if err != nil {
  136. return nil, err
  137. }
  138. opt.Options.RemoteContext = ""
  139. }
  140. defer func() {
  141. delete(b.jobs, buildID)
  142. }()
  143. }
  144. var out builder.Result
  145. id := identity.NewID()
  146. frontendAttrs := map[string]string{}
  147. if opt.Options.Target != "" {
  148. frontendAttrs["target"] = opt.Options.Target
  149. }
  150. if opt.Options.Dockerfile != "" && opt.Options.Dockerfile != "." {
  151. frontendAttrs["filename"] = opt.Options.Dockerfile
  152. }
  153. if opt.Options.RemoteContext != "" {
  154. if opt.Options.RemoteContext != "client-session" {
  155. frontendAttrs["context"] = opt.Options.RemoteContext
  156. }
  157. } else {
  158. url, cancel := b.reqBodyHandler.newRequest(rc)
  159. defer cancel()
  160. frontendAttrs["context"] = url
  161. }
  162. cacheFrom := append([]string{}, opt.Options.CacheFrom...)
  163. frontendAttrs["cache-from"] = strings.Join(cacheFrom, ",")
  164. for k, v := range opt.Options.BuildArgs {
  165. if v == nil {
  166. continue
  167. }
  168. frontendAttrs["build-arg:"+k] = *v
  169. }
  170. for k, v := range opt.Options.Labels {
  171. frontendAttrs["label:"+k] = v
  172. }
  173. if opt.Options.NoCache {
  174. frontendAttrs["no-cache"] = ""
  175. }
  176. if opt.Options.PullParent {
  177. frontendAttrs["image-resolve-mode"] = "pull"
  178. } else {
  179. frontendAttrs["image-resolve-mode"] = "default"
  180. }
  181. if opt.Options.Platform != "" {
  182. // same as in newBuilder in builder/dockerfile.builder.go
  183. // TODO: remove once opt.Options.Platform is of type specs.Platform
  184. sp, err := platforms.Parse(opt.Options.Platform)
  185. if err != nil {
  186. return nil, err
  187. }
  188. if err := system.ValidatePlatform(sp); err != nil {
  189. return nil, err
  190. }
  191. frontendAttrs["platform"] = opt.Options.Platform
  192. }
  193. exporterAttrs := map[string]string{}
  194. if len(opt.Options.Tags) > 0 {
  195. exporterAttrs["name"] = strings.Join(opt.Options.Tags, ",")
  196. }
  197. req := &controlapi.SolveRequest{
  198. Ref: id,
  199. Exporter: "moby",
  200. ExporterAttrs: exporterAttrs,
  201. Frontend: "dockerfile.v0",
  202. FrontendAttrs: frontendAttrs,
  203. Session: opt.Options.SessionID,
  204. }
  205. aux := streamformatter.AuxFormatter{Writer: opt.ProgressWriter.Output}
  206. eg, ctx := errgroup.WithContext(ctx)
  207. eg.Go(func() error {
  208. resp, err := b.controller.Solve(ctx, req)
  209. if err != nil {
  210. return err
  211. }
  212. id, ok := resp.ExporterResponse["containerimage.digest"]
  213. if !ok {
  214. return errors.Errorf("missing image id")
  215. }
  216. out.ImageID = id
  217. return aux.Emit("moby.image.id", types.BuildResult{ID: id})
  218. })
  219. ch := make(chan *controlapi.StatusResponse)
  220. eg.Go(func() error {
  221. defer close(ch)
  222. // streamProxy.ctx is not set to ctx because when request is cancelled,
  223. // only the build request has to be cancelled, not the status request.
  224. stream := &statusProxy{streamProxy: streamProxy{ctx: context.TODO()}, ch: ch}
  225. return b.controller.Status(&controlapi.StatusRequest{Ref: id}, stream)
  226. })
  227. eg.Go(func() error {
  228. for sr := range ch {
  229. dt, err := sr.Marshal()
  230. if err != nil {
  231. return err
  232. }
  233. if err := aux.Emit("moby.buildkit.trace", dt); err != nil {
  234. return err
  235. }
  236. }
  237. return nil
  238. })
  239. if err := eg.Wait(); err != nil {
  240. return nil, err
  241. }
  242. return &out, nil
  243. }
  244. type streamProxy struct {
  245. ctx context.Context
  246. }
  247. func (sp *streamProxy) SetHeader(_ grpcmetadata.MD) error {
  248. return nil
  249. }
  250. func (sp *streamProxy) SendHeader(_ grpcmetadata.MD) error {
  251. return nil
  252. }
  253. func (sp *streamProxy) SetTrailer(_ grpcmetadata.MD) {
  254. }
  255. func (sp *streamProxy) Context() context.Context {
  256. return sp.ctx
  257. }
  258. func (sp *streamProxy) RecvMsg(m interface{}) error {
  259. return io.EOF
  260. }
  261. type statusProxy struct {
  262. streamProxy
  263. ch chan *controlapi.StatusResponse
  264. }
  265. func (sp *statusProxy) Send(resp *controlapi.StatusResponse) error {
  266. return sp.SendMsg(resp)
  267. }
  268. func (sp *statusProxy) SendMsg(m interface{}) error {
  269. if sr, ok := m.(*controlapi.StatusResponse); ok {
  270. sp.ch <- sr
  271. }
  272. return nil
  273. }
  274. type pruneProxy struct {
  275. streamProxy
  276. ch chan *controlapi.UsageRecord
  277. }
  278. func (sp *pruneProxy) Send(resp *controlapi.UsageRecord) error {
  279. return sp.SendMsg(resp)
  280. }
  281. func (sp *pruneProxy) SendMsg(m interface{}) error {
  282. if sr, ok := m.(*controlapi.UsageRecord); ok {
  283. sp.ch <- sr
  284. }
  285. return nil
  286. }
  287. type contentStoreNoLabels struct {
  288. content.Store
  289. }
  290. func (c *contentStoreNoLabels) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
  291. return content.Info{}, nil
  292. }
  293. type wrapRC struct {
  294. io.ReadCloser
  295. once sync.Once
  296. err error
  297. waitCh chan struct{}
  298. }
  299. func (w *wrapRC) Read(b []byte) (int, error) {
  300. n, err := w.ReadCloser.Read(b)
  301. if err != nil {
  302. e := err
  303. if e == io.EOF {
  304. e = nil
  305. }
  306. w.close(e)
  307. }
  308. return n, err
  309. }
  310. func (w *wrapRC) Close() error {
  311. err := w.ReadCloser.Close()
  312. w.close(err)
  313. return err
  314. }
  315. func (w *wrapRC) close(err error) {
  316. w.once.Do(func() {
  317. w.err = err
  318. close(w.waitCh)
  319. })
  320. }
  321. func (w *wrapRC) wait() error {
  322. <-w.waitCh
  323. return w.err
  324. }
  325. type buildJob struct {
  326. cancel func()
  327. waitCh chan func(io.ReadCloser) error
  328. }
  329. func newBuildJob() *buildJob {
  330. return &buildJob{waitCh: make(chan func(io.ReadCloser) error)}
  331. }
  332. func (j *buildJob) WaitUpload(ctx context.Context) (io.ReadCloser, error) {
  333. done := make(chan struct{})
  334. var upload io.ReadCloser
  335. fn := func(rc io.ReadCloser) error {
  336. w := &wrapRC{ReadCloser: rc, waitCh: make(chan struct{})}
  337. upload = w
  338. close(done)
  339. return w.wait()
  340. }
  341. select {
  342. case <-ctx.Done():
  343. return nil, ctx.Err()
  344. case j.waitCh <- fn:
  345. <-done
  346. return upload, nil
  347. }
  348. }
  349. func (j *buildJob) SetUpload(ctx context.Context, rc io.ReadCloser) error {
  350. select {
  351. case <-ctx.Done():
  352. return ctx.Err()
  353. case fn := <-j.waitCh:
  354. return fn(rc)
  355. }
  356. }