builder.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package buildkit
  2. import (
  3. "context"
  4. "encoding/json"
  5. "io"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/containerd/containerd/content"
  10. "github.com/containerd/containerd/platforms"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/api/types/backend"
  13. "github.com/docker/docker/builder"
  14. "github.com/docker/docker/daemon/images"
  15. "github.com/docker/docker/pkg/jsonmessage"
  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.Platform != nil {
  177. frontendAttrs["platform"] = platforms.Format(*opt.Options.Platform)
  178. }
  179. exporterAttrs := map[string]string{}
  180. if len(opt.Options.Tags) > 0 {
  181. exporterAttrs["name"] = strings.Join(opt.Options.Tags, ",")
  182. }
  183. req := &controlapi.SolveRequest{
  184. Ref: id,
  185. Exporter: "moby",
  186. ExporterAttrs: exporterAttrs,
  187. Frontend: "dockerfile.v0",
  188. FrontendAttrs: frontendAttrs,
  189. Session: opt.Options.SessionID,
  190. }
  191. eg, ctx := errgroup.WithContext(ctx)
  192. eg.Go(func() error {
  193. resp, err := b.controller.Solve(ctx, req)
  194. if err != nil {
  195. return err
  196. }
  197. id, ok := resp.ExporterResponse["containerimage.digest"]
  198. if !ok {
  199. return errors.Errorf("missing image id")
  200. }
  201. out.ImageID = id
  202. return nil
  203. })
  204. ch := make(chan *controlapi.StatusResponse)
  205. eg.Go(func() error {
  206. defer close(ch)
  207. return b.controller.Status(&controlapi.StatusRequest{
  208. Ref: id,
  209. }, &statusProxy{streamProxy: streamProxy{ctx: ctx}, ch: ch})
  210. })
  211. eg.Go(func() error {
  212. for sr := range ch {
  213. dt, err := sr.Marshal()
  214. if err != nil {
  215. return err
  216. }
  217. auxJSONBytes, err := json.Marshal(dt)
  218. if err != nil {
  219. return err
  220. }
  221. auxJSON := new(json.RawMessage)
  222. *auxJSON = auxJSONBytes
  223. msgJSON, err := json.Marshal(&jsonmessage.JSONMessage{ID: "moby.buildkit.trace", Aux: auxJSON})
  224. if err != nil {
  225. return err
  226. }
  227. msgJSON = append(msgJSON, []byte("\r\n")...)
  228. n, err := opt.ProgressWriter.Output.Write(msgJSON)
  229. if err != nil {
  230. return err
  231. }
  232. if n != len(msgJSON) {
  233. return io.ErrShortWrite
  234. }
  235. }
  236. return nil
  237. })
  238. if err := eg.Wait(); err != nil {
  239. return nil, err
  240. }
  241. return &out, nil
  242. }
  243. type streamProxy struct {
  244. ctx context.Context
  245. }
  246. func (sp *streamProxy) SetHeader(_ grpcmetadata.MD) error {
  247. return nil
  248. }
  249. func (sp *streamProxy) SendHeader(_ grpcmetadata.MD) error {
  250. return nil
  251. }
  252. func (sp *streamProxy) SetTrailer(_ grpcmetadata.MD) {
  253. }
  254. func (sp *streamProxy) Context() context.Context {
  255. return sp.ctx
  256. }
  257. func (sp *streamProxy) RecvMsg(m interface{}) error {
  258. return io.EOF
  259. }
  260. type statusProxy struct {
  261. streamProxy
  262. ch chan *controlapi.StatusResponse
  263. }
  264. func (sp *statusProxy) Send(resp *controlapi.StatusResponse) error {
  265. return sp.SendMsg(resp)
  266. }
  267. func (sp *statusProxy) SendMsg(m interface{}) error {
  268. if sr, ok := m.(*controlapi.StatusResponse); ok {
  269. sp.ch <- sr
  270. }
  271. return nil
  272. }
  273. type pruneProxy struct {
  274. streamProxy
  275. ch chan *controlapi.UsageRecord
  276. }
  277. func (sp *pruneProxy) Send(resp *controlapi.UsageRecord) error {
  278. return sp.SendMsg(resp)
  279. }
  280. func (sp *pruneProxy) SendMsg(m interface{}) error {
  281. if sr, ok := m.(*controlapi.UsageRecord); ok {
  282. sp.ch <- sr
  283. }
  284. return nil
  285. }
  286. type contentStoreNoLabels struct {
  287. content.Store
  288. }
  289. func (c *contentStoreNoLabels) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
  290. return content.Info{}, nil
  291. }
  292. type wrapRC struct {
  293. io.ReadCloser
  294. once sync.Once
  295. err error
  296. waitCh chan struct{}
  297. }
  298. func (w *wrapRC) Read(b []byte) (int, error) {
  299. n, err := w.ReadCloser.Read(b)
  300. if err != nil {
  301. e := err
  302. if e == io.EOF {
  303. e = nil
  304. }
  305. w.close(e)
  306. }
  307. return n, err
  308. }
  309. func (w *wrapRC) Close() error {
  310. err := w.ReadCloser.Close()
  311. w.close(err)
  312. return err
  313. }
  314. func (w *wrapRC) close(err error) {
  315. w.once.Do(func() {
  316. w.err = err
  317. close(w.waitCh)
  318. })
  319. }
  320. func (w *wrapRC) wait() error {
  321. <-w.waitCh
  322. return w.err
  323. }
  324. type buildJob struct {
  325. cancel func()
  326. waitCh chan func(io.ReadCloser) error
  327. }
  328. func newBuildJob() *buildJob {
  329. return &buildJob{waitCh: make(chan func(io.ReadCloser) error)}
  330. }
  331. func (j *buildJob) WaitUpload(ctx context.Context) (io.ReadCloser, error) {
  332. done := make(chan struct{})
  333. var upload io.ReadCloser
  334. fn := func(rc io.ReadCloser) error {
  335. w := &wrapRC{ReadCloser: rc, waitCh: make(chan struct{})}
  336. upload = w
  337. close(done)
  338. return w.wait()
  339. }
  340. select {
  341. case <-ctx.Done():
  342. return nil, ctx.Err()
  343. case j.waitCh <- fn:
  344. <-done
  345. return upload, nil
  346. }
  347. }
  348. func (j *buildJob) SetUpload(ctx context.Context, rc io.ReadCloser) error {
  349. select {
  350. case <-ctx.Done():
  351. return ctx.Err()
  352. case fn := <-j.waitCh:
  353. return fn(rc)
  354. }
  355. }