convert.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. package dockerfile2llb
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "net/url"
  8. "path"
  9. "path/filepath"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "github.com/containerd/containerd/platforms"
  14. "github.com/docker/distribution/reference"
  15. "github.com/docker/docker/pkg/signal"
  16. "github.com/docker/go-connections/nat"
  17. "github.com/moby/buildkit/client/llb"
  18. "github.com/moby/buildkit/client/llb/imagemetaresolver"
  19. "github.com/moby/buildkit/frontend/dockerfile/instructions"
  20. "github.com/moby/buildkit/frontend/dockerfile/parser"
  21. "github.com/moby/buildkit/frontend/dockerfile/shell"
  22. gw "github.com/moby/buildkit/frontend/gateway/client"
  23. "github.com/moby/buildkit/solver/pb"
  24. "github.com/moby/buildkit/util/apicaps"
  25. "github.com/moby/buildkit/util/system"
  26. specs "github.com/opencontainers/image-spec/specs-go/v1"
  27. "github.com/pkg/errors"
  28. "golang.org/x/sync/errgroup"
  29. )
  30. const (
  31. emptyImageName = "scratch"
  32. localNameContext = "context"
  33. historyComment = "buildkit.dockerfile.v0"
  34. DefaultCopyImage = "docker/dockerfile-copy:v0.1.9@sha256:e8f159d3f00786604b93c675ee2783f8dc194bb565e61ca5788f6a6e9d304061"
  35. )
  36. type ConvertOpt struct {
  37. Target string
  38. MetaResolver llb.ImageMetaResolver
  39. BuildArgs map[string]string
  40. Labels map[string]string
  41. SessionID string
  42. BuildContext *llb.State
  43. Excludes []string
  44. // IgnoreCache contains names of the stages that should not use build cache.
  45. // Empty slice means ignore cache for all stages. Nil doesn't disable cache.
  46. IgnoreCache []string
  47. // CacheIDNamespace scopes the IDs for different cache mounts
  48. CacheIDNamespace string
  49. ImageResolveMode llb.ResolveMode
  50. TargetPlatform *specs.Platform
  51. BuildPlatforms []specs.Platform
  52. PrefixPlatform bool
  53. ExtraHosts []llb.HostIP
  54. ForceNetMode pb.NetMode
  55. OverrideCopyImage string
  56. LLBCaps *apicaps.CapSet
  57. }
  58. func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) {
  59. if len(dt) == 0 {
  60. return nil, nil, errors.Errorf("the Dockerfile cannot be empty")
  61. }
  62. platformOpt := buildPlatformOpt(&opt)
  63. optMetaArgs := getPlatformArgs(platformOpt)
  64. for i, arg := range optMetaArgs {
  65. optMetaArgs[i] = setKVValue(arg, opt.BuildArgs)
  66. }
  67. dockerfile, err := parser.Parse(bytes.NewReader(dt))
  68. if err != nil {
  69. return nil, nil, err
  70. }
  71. proxyEnv := proxyEnvFromBuildArgs(opt.BuildArgs)
  72. stages, metaArgs, err := instructions.Parse(dockerfile.AST)
  73. if err != nil {
  74. return nil, nil, err
  75. }
  76. shlex := shell.NewLex(dockerfile.EscapeToken)
  77. for _, metaArg := range metaArgs {
  78. if metaArg.Value != nil {
  79. *metaArg.Value, _ = shlex.ProcessWordWithMap(*metaArg.Value, metaArgsToMap(optMetaArgs))
  80. }
  81. optMetaArgs = append(optMetaArgs, setKVValue(metaArg.KeyValuePairOptional, opt.BuildArgs))
  82. }
  83. metaResolver := opt.MetaResolver
  84. if metaResolver == nil {
  85. metaResolver = imagemetaresolver.Default()
  86. }
  87. allDispatchStates := newDispatchStates()
  88. // set base state for every image
  89. for i, st := range stages {
  90. name, err := shlex.ProcessWordWithMap(st.BaseName, metaArgsToMap(optMetaArgs))
  91. if err != nil {
  92. return nil, nil, err
  93. }
  94. if name == "" {
  95. return nil, nil, errors.Errorf("base name (%s) should not be blank", st.BaseName)
  96. }
  97. st.BaseName = name
  98. ds := &dispatchState{
  99. stage: st,
  100. deps: make(map[*dispatchState]struct{}),
  101. ctxPaths: make(map[string]struct{}),
  102. stageName: st.Name,
  103. prefixPlatform: opt.PrefixPlatform,
  104. }
  105. if st.Name == "" {
  106. ds.stageName = fmt.Sprintf("stage-%d", i)
  107. }
  108. if v := st.Platform; v != "" {
  109. v, err := shlex.ProcessWordWithMap(v, metaArgsToMap(optMetaArgs))
  110. if err != nil {
  111. return nil, nil, errors.Wrapf(err, "failed to process arguments for platform %s", v)
  112. }
  113. p, err := platforms.Parse(v)
  114. if err != nil {
  115. return nil, nil, errors.Wrapf(err, "failed to parse platform %s", v)
  116. }
  117. ds.platform = &p
  118. }
  119. allDispatchStates.addState(ds)
  120. total := 0
  121. if ds.stage.BaseName != emptyImageName && ds.base == nil {
  122. total = 1
  123. }
  124. for _, cmd := range ds.stage.Commands {
  125. switch cmd.(type) {
  126. case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
  127. total++
  128. }
  129. }
  130. ds.cmdTotal = total
  131. if opt.IgnoreCache != nil {
  132. if len(opt.IgnoreCache) == 0 {
  133. ds.ignoreCache = true
  134. } else if st.Name != "" {
  135. for _, n := range opt.IgnoreCache {
  136. if strings.EqualFold(n, st.Name) {
  137. ds.ignoreCache = true
  138. }
  139. }
  140. }
  141. }
  142. }
  143. if len(allDispatchStates.states) == 1 {
  144. allDispatchStates.states[0].stageName = ""
  145. }
  146. var target *dispatchState
  147. if opt.Target == "" {
  148. target = allDispatchStates.lastTarget()
  149. } else {
  150. var ok bool
  151. target, ok = allDispatchStates.findStateByName(opt.Target)
  152. if !ok {
  153. return nil, nil, errors.Errorf("target stage %s could not be found", opt.Target)
  154. }
  155. }
  156. // fill dependencies to stages so unreachable ones can avoid loading image configs
  157. for _, d := range allDispatchStates.states {
  158. d.commands = make([]command, len(d.stage.Commands))
  159. for i, cmd := range d.stage.Commands {
  160. newCmd, err := toCommand(cmd, allDispatchStates)
  161. if err != nil {
  162. return nil, nil, err
  163. }
  164. d.commands[i] = newCmd
  165. for _, src := range newCmd.sources {
  166. if src != nil {
  167. d.deps[src] = struct{}{}
  168. if src.unregistered {
  169. allDispatchStates.addState(src)
  170. }
  171. }
  172. }
  173. }
  174. }
  175. eg, ctx := errgroup.WithContext(ctx)
  176. for i, d := range allDispatchStates.states {
  177. reachable := isReachable(target, d)
  178. // resolve image config for every stage
  179. if d.base == nil {
  180. if d.stage.BaseName == emptyImageName {
  181. d.state = llb.Scratch()
  182. d.image = emptyImage(platformOpt.targetPlatform)
  183. continue
  184. }
  185. func(i int, d *dispatchState) {
  186. eg.Go(func() error {
  187. ref, err := reference.ParseNormalizedNamed(d.stage.BaseName)
  188. if err != nil {
  189. return errors.Wrapf(err, "failed to parse stage name %q", d.stage.BaseName)
  190. }
  191. platform := d.platform
  192. if platform == nil {
  193. platform = &platformOpt.targetPlatform
  194. }
  195. d.stage.BaseName = reference.TagNameOnly(ref).String()
  196. var isScratch bool
  197. if metaResolver != nil && reachable && !d.unregistered {
  198. prefix := "["
  199. if opt.PrefixPlatform && platform != nil {
  200. prefix += platforms.Format(*platform) + " "
  201. }
  202. prefix += "internal]"
  203. dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName, gw.ResolveImageConfigOpt{
  204. Platform: platform,
  205. ResolveMode: opt.ImageResolveMode.String(),
  206. LogName: fmt.Sprintf("%s load metadata for %s", prefix, d.stage.BaseName),
  207. })
  208. if err == nil { // handle the error while builder is actually running
  209. var img Image
  210. if err := json.Unmarshal(dt, &img); err != nil {
  211. return err
  212. }
  213. img.Created = nil
  214. // if there is no explicit target platform, try to match based on image config
  215. if d.platform == nil && platformOpt.implicitTarget {
  216. p := autoDetectPlatform(img, *platform, platformOpt.buildPlatforms)
  217. platform = &p
  218. }
  219. d.image = img
  220. if dgst != "" {
  221. ref, err = reference.WithDigest(ref, dgst)
  222. if err != nil {
  223. return err
  224. }
  225. }
  226. d.stage.BaseName = ref.String()
  227. if len(img.RootFS.DiffIDs) == 0 {
  228. isScratch = true
  229. // schema1 images can't return diffIDs so double check :(
  230. for _, h := range img.History {
  231. if !h.EmptyLayer {
  232. isScratch = false
  233. break
  234. }
  235. }
  236. }
  237. }
  238. }
  239. if isScratch {
  240. d.state = llb.Scratch()
  241. } else {
  242. d.state = llb.Image(d.stage.BaseName, dfCmd(d.stage.SourceCode), llb.Platform(*platform), opt.ImageResolveMode, llb.WithCustomName(prefixCommand(d, "FROM "+d.stage.BaseName, opt.PrefixPlatform, platform)))
  243. }
  244. d.platform = platform
  245. return nil
  246. })
  247. }(i, d)
  248. }
  249. }
  250. if err := eg.Wait(); err != nil {
  251. return nil, nil, err
  252. }
  253. buildContext := &mutableOutput{}
  254. ctxPaths := map[string]struct{}{}
  255. for _, d := range allDispatchStates.states {
  256. if !isReachable(target, d) {
  257. continue
  258. }
  259. if d.base != nil {
  260. d.state = d.base.state
  261. d.platform = d.base.platform
  262. d.image = clone(d.base.image)
  263. }
  264. // make sure that PATH is always set
  265. if _, ok := shell.BuildEnvs(d.image.Config.Env)["PATH"]; !ok {
  266. d.image.Config.Env = append(d.image.Config.Env, "PATH="+system.DefaultPathEnv)
  267. }
  268. // initialize base metadata from image conf
  269. for _, env := range d.image.Config.Env {
  270. k, v := parseKeyValue(env)
  271. d.state = d.state.AddEnv(k, v)
  272. }
  273. if d.image.Config.WorkingDir != "" {
  274. if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false); err != nil {
  275. return nil, nil, err
  276. }
  277. }
  278. if d.image.Config.User != "" {
  279. if err = dispatchUser(d, &instructions.UserCommand{User: d.image.Config.User}, false); err != nil {
  280. return nil, nil, err
  281. }
  282. }
  283. d.state = d.state.Network(opt.ForceNetMode)
  284. opt := dispatchOpt{
  285. allDispatchStates: allDispatchStates,
  286. metaArgs: optMetaArgs,
  287. buildArgValues: opt.BuildArgs,
  288. shlex: shlex,
  289. sessionID: opt.SessionID,
  290. buildContext: llb.NewState(buildContext),
  291. proxyEnv: proxyEnv,
  292. cacheIDNamespace: opt.CacheIDNamespace,
  293. buildPlatforms: platformOpt.buildPlatforms,
  294. targetPlatform: platformOpt.targetPlatform,
  295. extraHosts: opt.ExtraHosts,
  296. copyImage: opt.OverrideCopyImage,
  297. llbCaps: opt.LLBCaps,
  298. }
  299. if opt.copyImage == "" {
  300. opt.copyImage = DefaultCopyImage
  301. }
  302. if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil {
  303. return nil, nil, err
  304. }
  305. for _, cmd := range d.commands {
  306. if err := dispatch(d, cmd, opt); err != nil {
  307. return nil, nil, err
  308. }
  309. }
  310. for p := range d.ctxPaths {
  311. ctxPaths[p] = struct{}{}
  312. }
  313. }
  314. if len(opt.Labels) != 0 && target.image.Config.Labels == nil {
  315. target.image.Config.Labels = make(map[string]string, len(opt.Labels))
  316. }
  317. for k, v := range opt.Labels {
  318. target.image.Config.Labels[k] = v
  319. }
  320. opts := []llb.LocalOption{
  321. llb.SessionID(opt.SessionID),
  322. llb.ExcludePatterns(opt.Excludes),
  323. llb.SharedKeyHint(localNameContext),
  324. WithInternalName("load build context"),
  325. }
  326. if includePatterns := normalizeContextPaths(ctxPaths); includePatterns != nil {
  327. opts = append(opts, llb.FollowPaths(includePatterns))
  328. }
  329. bc := llb.Local(localNameContext, opts...)
  330. if opt.BuildContext != nil {
  331. bc = *opt.BuildContext
  332. }
  333. buildContext.Output = bc.Output()
  334. defaults := []llb.ConstraintsOpt{
  335. llb.Platform(platformOpt.targetPlatform),
  336. }
  337. if opt.LLBCaps != nil {
  338. defaults = append(defaults, llb.WithCaps(*opt.LLBCaps))
  339. }
  340. st := target.state.SetMarshalDefaults(defaults...)
  341. if !platformOpt.implicitTarget {
  342. target.image.OS = platformOpt.targetPlatform.OS
  343. target.image.Architecture = platformOpt.targetPlatform.Architecture
  344. }
  345. return &st, &target.image, nil
  346. }
  347. func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string {
  348. m := map[string]string{}
  349. for _, arg := range metaArgs {
  350. m[arg.Key] = arg.ValueString()
  351. }
  352. return m
  353. }
  354. func toCommand(ic instructions.Command, allDispatchStates *dispatchStates) (command, error) {
  355. cmd := command{Command: ic}
  356. if c, ok := ic.(*instructions.CopyCommand); ok {
  357. if c.From != "" {
  358. var stn *dispatchState
  359. index, err := strconv.Atoi(c.From)
  360. if err != nil {
  361. stn, ok = allDispatchStates.findStateByName(c.From)
  362. if !ok {
  363. stn = &dispatchState{
  364. stage: instructions.Stage{BaseName: c.From},
  365. deps: make(map[*dispatchState]struct{}),
  366. unregistered: true,
  367. }
  368. }
  369. } else {
  370. stn, err = allDispatchStates.findStateByIndex(index)
  371. if err != nil {
  372. return command{}, err
  373. }
  374. }
  375. cmd.sources = []*dispatchState{stn}
  376. }
  377. }
  378. if ok := detectRunMount(&cmd, allDispatchStates); ok {
  379. return cmd, nil
  380. }
  381. return cmd, nil
  382. }
  383. type dispatchOpt struct {
  384. allDispatchStates *dispatchStates
  385. metaArgs []instructions.KeyValuePairOptional
  386. buildArgValues map[string]string
  387. shlex *shell.Lex
  388. sessionID string
  389. buildContext llb.State
  390. proxyEnv *llb.ProxyEnv
  391. cacheIDNamespace string
  392. targetPlatform specs.Platform
  393. buildPlatforms []specs.Platform
  394. extraHosts []llb.HostIP
  395. copyImage string
  396. llbCaps *apicaps.CapSet
  397. }
  398. func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
  399. if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansion); ok {
  400. err := ex.Expand(func(word string) (string, error) {
  401. return opt.shlex.ProcessWordWithMap(word, toEnvMap(d.buildArgs, d.image.Config.Env))
  402. })
  403. if err != nil {
  404. return err
  405. }
  406. }
  407. var err error
  408. switch c := cmd.Command.(type) {
  409. case *instructions.MaintainerCommand:
  410. err = dispatchMaintainer(d, c)
  411. case *instructions.EnvCommand:
  412. err = dispatchEnv(d, c)
  413. case *instructions.RunCommand:
  414. err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
  415. case *instructions.WorkdirCommand:
  416. err = dispatchWorkdir(d, c, true)
  417. case *instructions.AddCommand:
  418. err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt)
  419. if err == nil {
  420. for _, src := range c.Sources() {
  421. if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
  422. d.ctxPaths[path.Join("/", filepath.ToSlash(src))] = struct{}{}
  423. }
  424. }
  425. }
  426. case *instructions.LabelCommand:
  427. err = dispatchLabel(d, c)
  428. case *instructions.OnbuildCommand:
  429. err = dispatchOnbuild(d, c)
  430. case *instructions.CmdCommand:
  431. err = dispatchCmd(d, c)
  432. case *instructions.EntrypointCommand:
  433. err = dispatchEntrypoint(d, c)
  434. case *instructions.HealthCheckCommand:
  435. err = dispatchHealthcheck(d, c)
  436. case *instructions.ExposeCommand:
  437. err = dispatchExpose(d, c, opt.shlex)
  438. case *instructions.UserCommand:
  439. err = dispatchUser(d, c, true)
  440. case *instructions.VolumeCommand:
  441. err = dispatchVolume(d, c)
  442. case *instructions.StopSignalCommand:
  443. err = dispatchStopSignal(d, c)
  444. case *instructions.ShellCommand:
  445. err = dispatchShell(d, c)
  446. case *instructions.ArgCommand:
  447. err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues)
  448. case *instructions.CopyCommand:
  449. l := opt.buildContext
  450. if len(cmd.sources) != 0 {
  451. l = cmd.sources[0].state
  452. }
  453. err = dispatchCopy(d, c.SourcesAndDest, l, false, c, c.Chown, opt)
  454. if err == nil && len(cmd.sources) == 0 {
  455. for _, src := range c.Sources() {
  456. d.ctxPaths[path.Join("/", filepath.ToSlash(src))] = struct{}{}
  457. }
  458. }
  459. default:
  460. }
  461. return err
  462. }
  463. type dispatchState struct {
  464. state llb.State
  465. image Image
  466. platform *specs.Platform
  467. stage instructions.Stage
  468. base *dispatchState
  469. deps map[*dispatchState]struct{}
  470. buildArgs []instructions.KeyValuePairOptional
  471. commands []command
  472. ctxPaths map[string]struct{}
  473. ignoreCache bool
  474. cmdSet bool
  475. unregistered bool
  476. stageName string
  477. cmdIndex int
  478. cmdTotal int
  479. prefixPlatform bool
  480. }
  481. type dispatchStates struct {
  482. states []*dispatchState
  483. statesByName map[string]*dispatchState
  484. }
  485. func newDispatchStates() *dispatchStates {
  486. return &dispatchStates{statesByName: map[string]*dispatchState{}}
  487. }
  488. func (dss *dispatchStates) addState(ds *dispatchState) {
  489. dss.states = append(dss.states, ds)
  490. if d, ok := dss.statesByName[ds.stage.BaseName]; ok {
  491. ds.base = d
  492. }
  493. if ds.stage.Name != "" {
  494. dss.statesByName[strings.ToLower(ds.stage.Name)] = ds
  495. }
  496. }
  497. func (dss *dispatchStates) findStateByName(name string) (*dispatchState, bool) {
  498. ds, ok := dss.statesByName[strings.ToLower(name)]
  499. return ds, ok
  500. }
  501. func (dss *dispatchStates) findStateByIndex(index int) (*dispatchState, error) {
  502. if index < 0 || index >= len(dss.states) {
  503. return nil, errors.Errorf("invalid stage index %d", index)
  504. }
  505. return dss.states[index], nil
  506. }
  507. func (dss *dispatchStates) lastTarget() *dispatchState {
  508. return dss.states[len(dss.states)-1]
  509. }
  510. type command struct {
  511. instructions.Command
  512. sources []*dispatchState
  513. }
  514. func dispatchOnBuild(d *dispatchState, triggers []string, opt dispatchOpt) error {
  515. for _, trigger := range triggers {
  516. ast, err := parser.Parse(strings.NewReader(trigger))
  517. if err != nil {
  518. return err
  519. }
  520. if len(ast.AST.Children) != 1 {
  521. return errors.New("onbuild trigger should be a single expression")
  522. }
  523. ic, err := instructions.ParseCommand(ast.AST.Children[0])
  524. if err != nil {
  525. return err
  526. }
  527. cmd, err := toCommand(ic, opt.allDispatchStates)
  528. if err != nil {
  529. return err
  530. }
  531. if err := dispatch(d, cmd, opt); err != nil {
  532. return err
  533. }
  534. }
  535. return nil
  536. }
  537. func dispatchEnv(d *dispatchState, c *instructions.EnvCommand) error {
  538. commitMessage := bytes.NewBufferString("ENV")
  539. for _, e := range c.Env {
  540. commitMessage.WriteString(" " + e.String())
  541. d.state = d.state.AddEnv(e.Key, e.Value)
  542. d.image.Config.Env = addEnv(d.image.Config.Env, e.Key, e.Value)
  543. }
  544. return commitToHistory(&d.image, commitMessage.String(), false, nil)
  545. }
  546. func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyEnv, sources []*dispatchState, dopt dispatchOpt) error {
  547. var args []string = c.CmdLine
  548. if c.PrependShell {
  549. args = withShell(d.image, args)
  550. }
  551. env := d.state.Env()
  552. opt := []llb.RunOption{llb.Args(args)}
  553. for _, arg := range d.buildArgs {
  554. env = append(env, fmt.Sprintf("%s=%s", arg.Key, arg.ValueString()))
  555. opt = append(opt, llb.AddEnv(arg.Key, arg.ValueString()))
  556. }
  557. opt = append(opt, dfCmd(c))
  558. if d.ignoreCache {
  559. opt = append(opt, llb.IgnoreCache)
  560. }
  561. if proxy != nil {
  562. opt = append(opt, llb.WithProxy(*proxy))
  563. }
  564. runMounts, err := dispatchRunMounts(d, c, sources, dopt)
  565. if err != nil {
  566. return err
  567. }
  568. opt = append(opt, runMounts...)
  569. shlex := *dopt.shlex
  570. shlex.RawQuotes = true
  571. shlex.SkipUnsetEnv = true
  572. opt = append(opt, llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(&shlex, c.String(), env)), d.prefixPlatform, d.state.GetPlatform())))
  573. for _, h := range dopt.extraHosts {
  574. opt = append(opt, llb.AddExtraHost(h.Host, h.IP))
  575. }
  576. d.state = d.state.Run(opt...).Root()
  577. return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state)
  578. }
  579. func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bool) error {
  580. d.state = d.state.Dir(c.Path)
  581. wd := c.Path
  582. if !path.IsAbs(c.Path) {
  583. wd = path.Join("/", d.image.Config.WorkingDir, wd)
  584. }
  585. d.image.Config.WorkingDir = wd
  586. if commit {
  587. return commitToHistory(&d.image, "WORKDIR "+wd, false, nil)
  588. }
  589. return nil
  590. }
  591. func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
  592. // TODO: this should use CopyOp instead. Current implementation is inefficient
  593. img := llb.Image(opt.copyImage, llb.MarkImageInternal, llb.Platform(opt.buildPlatforms[0]), WithInternalName("helper image for file operations"))
  594. dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
  595. if c.Dest() == "." || c.Dest() == "" || c.Dest()[len(c.Dest())-1] == filepath.Separator {
  596. dest += string(filepath.Separator)
  597. }
  598. args := []string{"copy"}
  599. unpack := isAddCommand
  600. mounts := make([]llb.RunOption, 0, len(c.Sources()))
  601. if chown != "" {
  602. args = append(args, fmt.Sprintf("--chown=%s", chown))
  603. _, _, err := parseUser(chown)
  604. if err != nil {
  605. mounts = append(mounts, llb.AddMount("/etc/passwd", d.state, llb.SourcePath("/etc/passwd"), llb.Readonly))
  606. mounts = append(mounts, llb.AddMount("/etc/group", d.state, llb.SourcePath("/etc/group"), llb.Readonly))
  607. }
  608. }
  609. commitMessage := bytes.NewBufferString("")
  610. if isAddCommand {
  611. commitMessage.WriteString("ADD")
  612. } else {
  613. commitMessage.WriteString("COPY")
  614. }
  615. for i, src := range c.Sources() {
  616. commitMessage.WriteString(" " + src)
  617. if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
  618. if !isAddCommand {
  619. return errors.New("source can't be a URL for COPY")
  620. }
  621. // Resources from remote URLs are not decompressed.
  622. // https://docs.docker.com/engine/reference/builder/#add
  623. //
  624. // Note: mixing up remote archives and local archives in a single ADD instruction
  625. // would result in undefined behavior: https://github.com/moby/buildkit/pull/387#discussion_r189494717
  626. unpack = false
  627. u, err := url.Parse(src)
  628. f := "__unnamed__"
  629. if err == nil {
  630. if base := path.Base(u.Path); base != "." && base != "/" {
  631. f = base
  632. }
  633. }
  634. target := path.Join(fmt.Sprintf("/src-%d", i), f)
  635. args = append(args, target)
  636. mounts = append(mounts, llb.AddMount(path.Dir(target), llb.HTTP(src, llb.Filename(f), dfCmd(c)), llb.Readonly))
  637. } else {
  638. d, f := splitWildcards(src)
  639. targetCmd := fmt.Sprintf("/src-%d", i)
  640. targetMount := targetCmd
  641. if f == "" {
  642. f = path.Base(src)
  643. targetMount = path.Join(targetMount, f)
  644. }
  645. targetCmd = path.Join(targetCmd, f)
  646. args = append(args, targetCmd)
  647. mounts = append(mounts, llb.AddMount(targetMount, sourceState, llb.SourcePath(d), llb.Readonly))
  648. }
  649. }
  650. commitMessage.WriteString(" " + c.Dest())
  651. args = append(args, dest)
  652. if unpack {
  653. args = append(args[:1], append([]string{"--unpack"}, args[1:]...)...)
  654. }
  655. platform := opt.targetPlatform
  656. if d.platform != nil {
  657. platform = *d.platform
  658. }
  659. runOpt := []llb.RunOption{llb.Args(args), llb.Dir("/dest"), llb.ReadonlyRootFS(), dfCmd(cmdToPrint), llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, cmdToPrint.String(), d.state.Env())), d.prefixPlatform, &platform))}
  660. if d.ignoreCache {
  661. runOpt = append(runOpt, llb.IgnoreCache)
  662. }
  663. if opt.llbCaps != nil {
  664. if err := opt.llbCaps.Supports(pb.CapExecMetaNetwork); err == nil {
  665. runOpt = append(runOpt, llb.Network(llb.NetModeNone))
  666. }
  667. }
  668. run := img.Run(append(runOpt, mounts...)...)
  669. d.state = run.AddMount("/dest", d.state).Platform(platform)
  670. return commitToHistory(&d.image, commitMessage.String(), true, &d.state)
  671. }
  672. func dispatchMaintainer(d *dispatchState, c *instructions.MaintainerCommand) error {
  673. d.image.Author = c.Maintainer
  674. return commitToHistory(&d.image, fmt.Sprintf("MAINTAINER %v", c.Maintainer), false, nil)
  675. }
  676. func dispatchLabel(d *dispatchState, c *instructions.LabelCommand) error {
  677. commitMessage := bytes.NewBufferString("LABEL")
  678. if d.image.Config.Labels == nil {
  679. d.image.Config.Labels = make(map[string]string, len(c.Labels))
  680. }
  681. for _, v := range c.Labels {
  682. d.image.Config.Labels[v.Key] = v.Value
  683. commitMessage.WriteString(" " + v.String())
  684. }
  685. return commitToHistory(&d.image, commitMessage.String(), false, nil)
  686. }
  687. func dispatchOnbuild(d *dispatchState, c *instructions.OnbuildCommand) error {
  688. d.image.Config.OnBuild = append(d.image.Config.OnBuild, c.Expression)
  689. return nil
  690. }
  691. func dispatchCmd(d *dispatchState, c *instructions.CmdCommand) error {
  692. var args []string = c.CmdLine
  693. if c.PrependShell {
  694. args = withShell(d.image, args)
  695. }
  696. d.image.Config.Cmd = args
  697. d.image.Config.ArgsEscaped = true
  698. d.cmdSet = true
  699. return commitToHistory(&d.image, fmt.Sprintf("CMD %q", args), false, nil)
  700. }
  701. func dispatchEntrypoint(d *dispatchState, c *instructions.EntrypointCommand) error {
  702. var args []string = c.CmdLine
  703. if c.PrependShell {
  704. args = withShell(d.image, args)
  705. }
  706. d.image.Config.Entrypoint = args
  707. if !d.cmdSet {
  708. d.image.Config.Cmd = nil
  709. }
  710. return commitToHistory(&d.image, fmt.Sprintf("ENTRYPOINT %q", args), false, nil)
  711. }
  712. func dispatchHealthcheck(d *dispatchState, c *instructions.HealthCheckCommand) error {
  713. d.image.Config.Healthcheck = &HealthConfig{
  714. Test: c.Health.Test,
  715. Interval: c.Health.Interval,
  716. Timeout: c.Health.Timeout,
  717. StartPeriod: c.Health.StartPeriod,
  718. Retries: c.Health.Retries,
  719. }
  720. return commitToHistory(&d.image, fmt.Sprintf("HEALTHCHECK %q", d.image.Config.Healthcheck), false, nil)
  721. }
  722. func dispatchExpose(d *dispatchState, c *instructions.ExposeCommand, shlex *shell.Lex) error {
  723. ports := []string{}
  724. for _, p := range c.Ports {
  725. ps, err := shlex.ProcessWordsWithMap(p, toEnvMap(d.buildArgs, d.image.Config.Env))
  726. if err != nil {
  727. return err
  728. }
  729. ports = append(ports, ps...)
  730. }
  731. c.Ports = ports
  732. ps, _, err := nat.ParsePortSpecs(c.Ports)
  733. if err != nil {
  734. return err
  735. }
  736. if d.image.Config.ExposedPorts == nil {
  737. d.image.Config.ExposedPorts = make(map[string]struct{})
  738. }
  739. for p := range ps {
  740. d.image.Config.ExposedPorts[string(p)] = struct{}{}
  741. }
  742. return commitToHistory(&d.image, fmt.Sprintf("EXPOSE %v", ps), false, nil)
  743. }
  744. func dispatchUser(d *dispatchState, c *instructions.UserCommand, commit bool) error {
  745. d.state = d.state.User(c.User)
  746. d.image.Config.User = c.User
  747. if commit {
  748. return commitToHistory(&d.image, fmt.Sprintf("USER %v", c.User), false, nil)
  749. }
  750. return nil
  751. }
  752. func dispatchVolume(d *dispatchState, c *instructions.VolumeCommand) error {
  753. if d.image.Config.Volumes == nil {
  754. d.image.Config.Volumes = map[string]struct{}{}
  755. }
  756. for _, v := range c.Volumes {
  757. if v == "" {
  758. return errors.New("VOLUME specified can not be an empty string")
  759. }
  760. d.image.Config.Volumes[v] = struct{}{}
  761. }
  762. return commitToHistory(&d.image, fmt.Sprintf("VOLUME %v", c.Volumes), false, nil)
  763. }
  764. func dispatchStopSignal(d *dispatchState, c *instructions.StopSignalCommand) error {
  765. if _, err := signal.ParseSignal(c.Signal); err != nil {
  766. return err
  767. }
  768. d.image.Config.StopSignal = c.Signal
  769. return commitToHistory(&d.image, fmt.Sprintf("STOPSIGNAL %v", c.Signal), false, nil)
  770. }
  771. func dispatchShell(d *dispatchState, c *instructions.ShellCommand) error {
  772. d.image.Config.Shell = c.Shell
  773. return commitToHistory(&d.image, fmt.Sprintf("SHELL %v", c.Shell), false, nil)
  774. }
  775. func dispatchArg(d *dispatchState, c *instructions.ArgCommand, metaArgs []instructions.KeyValuePairOptional, buildArgValues map[string]string) error {
  776. commitStr := "ARG " + c.Key
  777. buildArg := setKVValue(c.KeyValuePairOptional, buildArgValues)
  778. if c.Value != nil {
  779. commitStr += "=" + *c.Value
  780. }
  781. if buildArg.Value == nil {
  782. for _, ma := range metaArgs {
  783. if ma.Key == buildArg.Key {
  784. buildArg.Value = ma.Value
  785. }
  786. }
  787. }
  788. d.buildArgs = append(d.buildArgs, buildArg)
  789. return commitToHistory(&d.image, commitStr, false, nil)
  790. }
  791. func pathRelativeToWorkingDir(s llb.State, p string) string {
  792. if path.IsAbs(p) {
  793. return p
  794. }
  795. return path.Join(s.GetDir(), p)
  796. }
  797. func splitWildcards(name string) (string, string) {
  798. i := 0
  799. for ; i < len(name); i++ {
  800. ch := name[i]
  801. if ch == '\\' {
  802. i++
  803. } else if ch == '*' || ch == '?' || ch == '[' {
  804. break
  805. }
  806. }
  807. if i == len(name) {
  808. return name, ""
  809. }
  810. base := path.Base(name[:i])
  811. if name[:i] == "" || strings.HasSuffix(name[:i], string(filepath.Separator)) {
  812. base = ""
  813. }
  814. return path.Dir(name[:i]), base + name[i:]
  815. }
  816. func addEnv(env []string, k, v string) []string {
  817. gotOne := false
  818. for i, envVar := range env {
  819. key, _ := parseKeyValue(envVar)
  820. if shell.EqualEnvKeys(key, k) {
  821. env[i] = k + "=" + v
  822. gotOne = true
  823. break
  824. }
  825. }
  826. if !gotOne {
  827. env = append(env, k+"="+v)
  828. }
  829. return env
  830. }
  831. func parseKeyValue(env string) (string, string) {
  832. parts := strings.SplitN(env, "=", 2)
  833. v := ""
  834. if len(parts) > 1 {
  835. v = parts[1]
  836. }
  837. return parts[0], v
  838. }
  839. func setKVValue(kvpo instructions.KeyValuePairOptional, values map[string]string) instructions.KeyValuePairOptional {
  840. if v, ok := values[kvpo.Key]; ok {
  841. kvpo.Value = &v
  842. }
  843. return kvpo
  844. }
  845. func toEnvMap(args []instructions.KeyValuePairOptional, env []string) map[string]string {
  846. m := shell.BuildEnvs(env)
  847. for _, arg := range args {
  848. // If key already exists, keep previous value.
  849. if _, ok := m[arg.Key]; ok {
  850. continue
  851. }
  852. m[arg.Key] = arg.ValueString()
  853. }
  854. return m
  855. }
  856. func dfCmd(cmd interface{}) llb.ConstraintsOpt {
  857. // TODO: add fmt.Stringer to instructions.Command to remove interface{}
  858. var cmdStr string
  859. if cmd, ok := cmd.(fmt.Stringer); ok {
  860. cmdStr = cmd.String()
  861. }
  862. if cmd, ok := cmd.(string); ok {
  863. cmdStr = cmd
  864. }
  865. return llb.WithDescription(map[string]string{
  866. "com.docker.dockerfile.v1.command": cmdStr,
  867. })
  868. }
  869. func runCommandString(args []string, buildArgs []instructions.KeyValuePairOptional) string {
  870. var tmpBuildEnv []string
  871. for _, arg := range buildArgs {
  872. tmpBuildEnv = append(tmpBuildEnv, arg.Key+"="+arg.ValueString())
  873. }
  874. if len(tmpBuildEnv) > 0 {
  875. tmpBuildEnv = append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...)
  876. }
  877. return strings.Join(append(tmpBuildEnv, args...), " ")
  878. }
  879. func commitToHistory(img *Image, msg string, withLayer bool, st *llb.State) error {
  880. if st != nil {
  881. msg += " # buildkit"
  882. }
  883. img.History = append(img.History, specs.History{
  884. CreatedBy: msg,
  885. Comment: historyComment,
  886. EmptyLayer: !withLayer,
  887. })
  888. return nil
  889. }
  890. func isReachable(from, to *dispatchState) (ret bool) {
  891. if from == nil {
  892. return false
  893. }
  894. if from == to || isReachable(from.base, to) {
  895. return true
  896. }
  897. for d := range from.deps {
  898. if isReachable(d, to) {
  899. return true
  900. }
  901. }
  902. return false
  903. }
  904. func parseUser(str string) (uid uint32, gid uint32, err error) {
  905. if str == "" {
  906. return 0, 0, nil
  907. }
  908. parts := strings.SplitN(str, ":", 2)
  909. for i, v := range parts {
  910. switch i {
  911. case 0:
  912. uid, err = parseUID(v)
  913. if err != nil {
  914. return 0, 0, err
  915. }
  916. if len(parts) == 1 {
  917. gid = uid
  918. }
  919. case 1:
  920. gid, err = parseUID(v)
  921. if err != nil {
  922. return 0, 0, err
  923. }
  924. }
  925. }
  926. return
  927. }
  928. func parseUID(str string) (uint32, error) {
  929. if str == "root" {
  930. return 0, nil
  931. }
  932. uid, err := strconv.ParseUint(str, 10, 32)
  933. if err != nil {
  934. return 0, err
  935. }
  936. return uint32(uid), nil
  937. }
  938. func normalizeContextPaths(paths map[string]struct{}) []string {
  939. pathSlice := make([]string, 0, len(paths))
  940. for p := range paths {
  941. if p == "/" {
  942. return nil
  943. }
  944. pathSlice = append(pathSlice, p)
  945. }
  946. toDelete := map[string]struct{}{}
  947. for i := range pathSlice {
  948. for j := range pathSlice {
  949. if i == j {
  950. continue
  951. }
  952. if strings.HasPrefix(pathSlice[j], pathSlice[i]+"/") {
  953. delete(paths, pathSlice[j])
  954. }
  955. }
  956. }
  957. toSort := make([]string, 0, len(paths))
  958. for p := range paths {
  959. if _, ok := toDelete[p]; !ok {
  960. toSort = append(toSort, path.Join(".", p))
  961. }
  962. }
  963. sort.Slice(toSort, func(i, j int) bool {
  964. return toSort[i] < toSort[j]
  965. })
  966. return toSort
  967. }
  968. func proxyEnvFromBuildArgs(args map[string]string) *llb.ProxyEnv {
  969. pe := &llb.ProxyEnv{}
  970. isNil := true
  971. for k, v := range args {
  972. if strings.EqualFold(k, "http_proxy") {
  973. pe.HttpProxy = v
  974. isNil = false
  975. }
  976. if strings.EqualFold(k, "https_proxy") {
  977. pe.HttpsProxy = v
  978. isNil = false
  979. }
  980. if strings.EqualFold(k, "ftp_proxy") {
  981. pe.FtpProxy = v
  982. isNil = false
  983. }
  984. if strings.EqualFold(k, "no_proxy") {
  985. pe.NoProxy = v
  986. isNil = false
  987. }
  988. }
  989. if isNil {
  990. return nil
  991. }
  992. return pe
  993. }
  994. type mutableOutput struct {
  995. llb.Output
  996. }
  997. func withShell(img Image, args []string) []string {
  998. var shell []string
  999. if len(img.Config.Shell) > 0 {
  1000. shell = append([]string{}, img.Config.Shell...)
  1001. } else {
  1002. shell = defaultShell()
  1003. }
  1004. return append(shell, strings.Join(args, " "))
  1005. }
  1006. func autoDetectPlatform(img Image, target specs.Platform, supported []specs.Platform) specs.Platform {
  1007. os := img.OS
  1008. arch := img.Architecture
  1009. if target.OS == os && target.Architecture == arch {
  1010. return target
  1011. }
  1012. for _, p := range supported {
  1013. if p.OS == os && p.Architecture == arch {
  1014. return p
  1015. }
  1016. }
  1017. return target
  1018. }
  1019. func WithInternalName(name string, a ...interface{}) llb.ConstraintsOpt {
  1020. return llb.WithCustomName("[internal] "+name, a...)
  1021. }
  1022. func uppercaseCmd(str string) string {
  1023. p := strings.SplitN(str, " ", 2)
  1024. p[0] = strings.ToUpper(p[0])
  1025. return strings.Join(p, " ")
  1026. }
  1027. func processCmdEnv(shlex *shell.Lex, cmd string, env []string) string {
  1028. w, err := shlex.ProcessWord(cmd, env)
  1029. if err != nil {
  1030. return cmd
  1031. }
  1032. return w
  1033. }
  1034. func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform *specs.Platform) string {
  1035. if ds.cmdTotal == 0 {
  1036. return str
  1037. }
  1038. out := "["
  1039. if prefixPlatform && platform != nil {
  1040. out += platforms.Format(*platform) + " "
  1041. }
  1042. if ds.stageName != "" {
  1043. out += ds.stageName + " "
  1044. }
  1045. ds.cmdIndex++
  1046. out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
  1047. return out + str
  1048. }