convert.go 35 KB

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