dispatchers.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. package dockerfile
  2. // This file contains the dispatchers for each command. Note that
  3. // `nullDispatch` is not actually a command, but support for commands we parse
  4. // but do nothing with.
  5. //
  6. // See evaluator.go for a higher level discussion of the whole evaluator
  7. // package.
  8. import (
  9. "bytes"
  10. "fmt"
  11. "regexp"
  12. "runtime"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/docker/docker/api"
  18. "github.com/docker/docker/api/types/container"
  19. "github.com/docker/docker/api/types/strslice"
  20. "github.com/docker/docker/builder"
  21. "github.com/docker/docker/builder/dockerfile/parser"
  22. "github.com/docker/docker/image"
  23. "github.com/docker/docker/pkg/jsonmessage"
  24. "github.com/docker/docker/pkg/signal"
  25. "github.com/docker/docker/pkg/system"
  26. "github.com/docker/go-connections/nat"
  27. "github.com/pkg/errors"
  28. "github.com/sirupsen/logrus"
  29. )
  30. // ENV foo bar
  31. //
  32. // Sets the environment variable foo to bar, also makes interpolation
  33. // in the dockerfile available from the next statement on via ${foo}.
  34. //
  35. func env(req dispatchRequest) error {
  36. if len(req.args) == 0 {
  37. return errAtLeastOneArgument("ENV")
  38. }
  39. if len(req.args)%2 != 0 {
  40. // should never get here, but just in case
  41. return errTooManyArguments("ENV")
  42. }
  43. if err := req.flags.Parse(); err != nil {
  44. return err
  45. }
  46. runConfig := req.state.runConfig
  47. commitMessage := bytes.NewBufferString("ENV")
  48. for j := 0; j < len(req.args); j += 2 {
  49. if len(req.args[j]) == 0 {
  50. return errBlankCommandNames("ENV")
  51. }
  52. name := req.args[j]
  53. value := req.args[j+1]
  54. newVar := name + "=" + value
  55. commitMessage.WriteString(" " + newVar)
  56. gotOne := false
  57. for i, envVar := range runConfig.Env {
  58. envParts := strings.SplitN(envVar, "=", 2)
  59. compareFrom := envParts[0]
  60. if equalEnvKeys(compareFrom, name) {
  61. runConfig.Env[i] = newVar
  62. gotOne = true
  63. break
  64. }
  65. }
  66. if !gotOne {
  67. runConfig.Env = append(runConfig.Env, newVar)
  68. }
  69. }
  70. return req.builder.commit(req.state, commitMessage.String())
  71. }
  72. // MAINTAINER some text <maybe@an.email.address>
  73. //
  74. // Sets the maintainer metadata.
  75. func maintainer(req dispatchRequest) error {
  76. if len(req.args) != 1 {
  77. return errExactlyOneArgument("MAINTAINER")
  78. }
  79. if err := req.flags.Parse(); err != nil {
  80. return err
  81. }
  82. maintainer := req.args[0]
  83. req.state.maintainer = maintainer
  84. return req.builder.commit(req.state, "MAINTAINER "+maintainer)
  85. }
  86. // LABEL some json data describing the image
  87. //
  88. // Sets the Label variable foo to bar,
  89. //
  90. func label(req dispatchRequest) error {
  91. if len(req.args) == 0 {
  92. return errAtLeastOneArgument("LABEL")
  93. }
  94. if len(req.args)%2 != 0 {
  95. // should never get here, but just in case
  96. return errTooManyArguments("LABEL")
  97. }
  98. if err := req.flags.Parse(); err != nil {
  99. return err
  100. }
  101. commitStr := "LABEL"
  102. runConfig := req.state.runConfig
  103. if runConfig.Labels == nil {
  104. runConfig.Labels = map[string]string{}
  105. }
  106. for j := 0; j < len(req.args); j++ {
  107. name := req.args[j]
  108. if name == "" {
  109. return errBlankCommandNames("LABEL")
  110. }
  111. value := req.args[j+1]
  112. commitStr += " " + name + "=" + value
  113. runConfig.Labels[name] = value
  114. j++
  115. }
  116. return req.builder.commit(req.state, commitStr)
  117. }
  118. // ADD foo /path
  119. //
  120. // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
  121. // exist here. If you do not wish to have this automatic handling, use COPY.
  122. //
  123. func add(req dispatchRequest) error {
  124. if len(req.args) < 2 {
  125. return errAtLeastTwoArguments("ADD")
  126. }
  127. flChown := req.flags.AddString("chown", "")
  128. if err := req.flags.Parse(); err != nil {
  129. return err
  130. }
  131. downloader := newRemoteSourceDownloader(req.builder.Output, req.builder.Stdout)
  132. copier := copierFromDispatchRequest(req, downloader, nil)
  133. defer copier.Cleanup()
  134. copyInstruction, err := copier.createCopyInstruction(req.args, "ADD")
  135. if err != nil {
  136. return err
  137. }
  138. copyInstruction.chownStr = flChown.Value
  139. copyInstruction.allowLocalDecompression = true
  140. return req.builder.performCopy(req.state, copyInstruction)
  141. }
  142. // COPY foo /path
  143. //
  144. // Same as 'ADD' but without the tar and remote url handling.
  145. //
  146. func dispatchCopy(req dispatchRequest) error {
  147. if len(req.args) < 2 {
  148. return errAtLeastTwoArguments("COPY")
  149. }
  150. flFrom := req.flags.AddString("from", "")
  151. flChown := req.flags.AddString("chown", "")
  152. if err := req.flags.Parse(); err != nil {
  153. return err
  154. }
  155. im, err := req.builder.getImageMount(flFrom)
  156. if err != nil {
  157. return errors.Wrapf(err, "invalid from flag value %s", flFrom.Value)
  158. }
  159. copier := copierFromDispatchRequest(req, errOnSourceDownload, im)
  160. defer copier.Cleanup()
  161. copyInstruction, err := copier.createCopyInstruction(req.args, "COPY")
  162. if err != nil {
  163. return err
  164. }
  165. copyInstruction.chownStr = flChown.Value
  166. return req.builder.performCopy(req.state, copyInstruction)
  167. }
  168. func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) {
  169. if !fromFlag.IsUsed() {
  170. // TODO: this could return the source in the default case as well?
  171. return nil, nil
  172. }
  173. var localOnly bool
  174. imageRefOrID := fromFlag.Value
  175. stage, err := b.buildStages.get(fromFlag.Value)
  176. if err != nil {
  177. return nil, err
  178. }
  179. if stage != nil {
  180. imageRefOrID = stage.ImageID()
  181. localOnly = true
  182. }
  183. return b.imageSources.Get(imageRefOrID, localOnly)
  184. }
  185. // FROM imagename[:tag | @digest] [AS build-stage-name]
  186. //
  187. func from(req dispatchRequest) error {
  188. stageName, err := parseBuildStageName(req.args)
  189. if err != nil {
  190. return err
  191. }
  192. if err := req.flags.Parse(); err != nil {
  193. return err
  194. }
  195. req.builder.imageProber.Reset()
  196. image, err := req.builder.getFromImage(req.shlex, req.args[0])
  197. if err != nil {
  198. return err
  199. }
  200. if err := req.builder.buildStages.add(stageName, image); err != nil {
  201. return err
  202. }
  203. req.state.beginStage(stageName, image)
  204. req.builder.buildArgs.ResetAllowed()
  205. if image.ImageID() == "" {
  206. // Typically this means they used "FROM scratch"
  207. return nil
  208. }
  209. return processOnBuild(req)
  210. }
  211. func parseBuildStageName(args []string) (string, error) {
  212. stageName := ""
  213. switch {
  214. case len(args) == 3 && strings.EqualFold(args[1], "as"):
  215. stageName = strings.ToLower(args[2])
  216. if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", stageName); !ok {
  217. return "", errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", stageName)
  218. }
  219. case len(args) != 1:
  220. return "", errors.New("FROM requires either one or three arguments")
  221. }
  222. return stageName, nil
  223. }
  224. // scratchImage is used as a token for the empty base image.
  225. var scratchImage builder.Image = &image.Image{}
  226. func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) {
  227. substitutionArgs := []string{}
  228. for key, value := range b.buildArgs.GetAllMeta() {
  229. substitutionArgs = append(substitutionArgs, key+"="+value)
  230. }
  231. name, err := shlex.ProcessWord(name, substitutionArgs)
  232. if err != nil {
  233. return nil, err
  234. }
  235. var localOnly bool
  236. if stage, ok := b.buildStages.getByName(name); ok {
  237. name = stage.ImageID()
  238. localOnly = true
  239. }
  240. // Windows cannot support a container with no base image unless it is LCOW.
  241. if name == api.NoBaseImageSpecifier {
  242. if runtime.GOOS == "windows" {
  243. if b.platform == "windows" || (b.platform != "windows" && !system.LCOWSupported()) {
  244. return nil, errors.New("Windows does not support FROM scratch")
  245. }
  246. }
  247. return scratchImage, nil
  248. }
  249. imageMount, err := b.imageSources.Get(name, localOnly)
  250. if err != nil {
  251. return nil, err
  252. }
  253. return imageMount.Image(), nil
  254. }
  255. func processOnBuild(req dispatchRequest) error {
  256. dispatchState := req.state
  257. // Process ONBUILD triggers if they exist
  258. if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
  259. word := "trigger"
  260. if nTriggers > 1 {
  261. word = "triggers"
  262. }
  263. fmt.Fprintf(req.builder.Stderr, "# Executing %d build %s...\n", nTriggers, word)
  264. }
  265. // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
  266. onBuildTriggers := dispatchState.runConfig.OnBuild
  267. dispatchState.runConfig.OnBuild = []string{}
  268. // Reset stdin settings as all build actions run without stdin
  269. dispatchState.runConfig.OpenStdin = false
  270. dispatchState.runConfig.StdinOnce = false
  271. // parse the ONBUILD triggers by invoking the parser
  272. for _, step := range onBuildTriggers {
  273. dockerfile, err := parser.Parse(strings.NewReader(step))
  274. if err != nil {
  275. return err
  276. }
  277. for _, n := range dockerfile.AST.Children {
  278. if err := checkDispatch(n); err != nil {
  279. return err
  280. }
  281. upperCasedCmd := strings.ToUpper(n.Value)
  282. switch upperCasedCmd {
  283. case "ONBUILD":
  284. return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
  285. case "MAINTAINER", "FROM":
  286. return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
  287. }
  288. }
  289. if _, err := dispatchFromDockerfile(req.builder, dockerfile, dispatchState, req.source); err != nil {
  290. return err
  291. }
  292. }
  293. return nil
  294. }
  295. // ONBUILD RUN echo yo
  296. //
  297. // ONBUILD triggers run when the image is used in a FROM statement.
  298. //
  299. // ONBUILD handling has a lot of special-case functionality, the heading in
  300. // evaluator.go and comments around dispatch() in the same file explain the
  301. // special cases. search for 'OnBuild' in internals.go for additional special
  302. // cases.
  303. //
  304. func onbuild(req dispatchRequest) error {
  305. if len(req.args) == 0 {
  306. return errAtLeastOneArgument("ONBUILD")
  307. }
  308. if err := req.flags.Parse(); err != nil {
  309. return err
  310. }
  311. triggerInstruction := strings.ToUpper(strings.TrimSpace(req.args[0]))
  312. switch triggerInstruction {
  313. case "ONBUILD":
  314. return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
  315. case "MAINTAINER", "FROM":
  316. return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
  317. }
  318. runConfig := req.state.runConfig
  319. original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
  320. runConfig.OnBuild = append(runConfig.OnBuild, original)
  321. return req.builder.commit(req.state, "ONBUILD "+original)
  322. }
  323. // WORKDIR /tmp
  324. //
  325. // Set the working directory for future RUN/CMD/etc statements.
  326. //
  327. func workdir(req dispatchRequest) error {
  328. if len(req.args) != 1 {
  329. return errExactlyOneArgument("WORKDIR")
  330. }
  331. err := req.flags.Parse()
  332. if err != nil {
  333. return err
  334. }
  335. runConfig := req.state.runConfig
  336. // This is from the Dockerfile and will not necessarily be in platform
  337. // specific semantics, hence ensure it is converted.
  338. runConfig.WorkingDir, err = normalizeWorkdir(req.builder.platform, runConfig.WorkingDir, req.args[0])
  339. if err != nil {
  340. return err
  341. }
  342. // For performance reasons, we explicitly do a create/mkdir now
  343. // This avoids having an unnecessary expensive mount/unmount calls
  344. // (on Windows in particular) during each container create.
  345. // Prior to 1.13, the mkdir was deferred and not executed at this step.
  346. if req.builder.disableCommit {
  347. // Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo".
  348. // We've already updated the runConfig and that's enough.
  349. return nil
  350. }
  351. comment := "WORKDIR " + runConfig.WorkingDir
  352. runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, req.builder.platform))
  353. containerID, err := req.builder.probeAndCreate(req.state, runConfigWithCommentCmd)
  354. if err != nil || containerID == "" {
  355. return err
  356. }
  357. if err := req.builder.docker.ContainerCreateWorkdir(containerID); err != nil {
  358. return err
  359. }
  360. return req.builder.commitContainer(req.state, containerID, runConfigWithCommentCmd)
  361. }
  362. // RUN some command yo
  363. //
  364. // run a command and commit the image. Args are automatically prepended with
  365. // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
  366. // Windows, in the event there is only one argument The difference in processing:
  367. //
  368. // RUN echo hi # sh -c echo hi (Linux and LCOW)
  369. // RUN echo hi # cmd /S /C echo hi (Windows)
  370. // RUN [ "echo", "hi" ] # echo hi
  371. //
  372. func run(req dispatchRequest) error {
  373. if !req.state.hasFromImage() {
  374. return errors.New("Please provide a source image with `from` prior to run")
  375. }
  376. if err := req.flags.Parse(); err != nil {
  377. return err
  378. }
  379. stateRunConfig := req.state.runConfig
  380. args := handleJSONArgs(req.args, req.attributes)
  381. if !req.attributes["json"] {
  382. args = append(getShell(stateRunConfig, req.builder.platform), args...)
  383. }
  384. cmdFromArgs := strslice.StrSlice(args)
  385. buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env)
  386. saveCmd := cmdFromArgs
  387. if len(buildArgs) > 0 {
  388. saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs)
  389. }
  390. runConfigForCacheProbe := copyRunConfig(stateRunConfig,
  391. withCmd(saveCmd),
  392. withEntrypointOverride(saveCmd, nil))
  393. hit, err := req.builder.probeCache(req.state, runConfigForCacheProbe)
  394. if err != nil || hit {
  395. return err
  396. }
  397. runConfig := copyRunConfig(stateRunConfig,
  398. withCmd(cmdFromArgs),
  399. withEnv(append(stateRunConfig.Env, buildArgs...)),
  400. withEntrypointOverride(saveCmd, strslice.StrSlice{""}))
  401. // set config as already being escaped, this prevents double escaping on windows
  402. runConfig.ArgsEscaped = true
  403. logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
  404. cID, err := req.builder.create(runConfig)
  405. if err != nil {
  406. return err
  407. }
  408. if err := req.builder.containerManager.Run(req.builder.clientCtx, cID, req.builder.Stdout, req.builder.Stderr); err != nil {
  409. if err, ok := err.(*statusCodeError); ok {
  410. // TODO: change error type, because jsonmessage.JSONError assumes HTTP
  411. return &jsonmessage.JSONError{
  412. Message: fmt.Sprintf(
  413. "The command '%s' returned a non-zero code: %d",
  414. strings.Join(runConfig.Cmd, " "), err.StatusCode()),
  415. Code: err.StatusCode(),
  416. }
  417. }
  418. return err
  419. }
  420. return req.builder.commitContainer(req.state, cID, runConfigForCacheProbe)
  421. }
  422. // Derive the command to use for probeCache() and to commit in this container.
  423. // Note that we only do this if there are any build-time env vars. Also, we
  424. // use the special argument "|#" at the start of the args array. This will
  425. // avoid conflicts with any RUN command since commands can not
  426. // start with | (vertical bar). The "#" (number of build envs) is there to
  427. // help ensure proper cache matches. We don't want a RUN command
  428. // that starts with "foo=abc" to be considered part of a build-time env var.
  429. //
  430. // remove any unreferenced built-in args from the environment variables.
  431. // These args are transparent so resulting image should be the same regardless
  432. // of the value.
  433. func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
  434. var tmpBuildEnv []string
  435. for _, env := range buildArgVars {
  436. key := strings.SplitN(env, "=", 2)[0]
  437. if buildArgs.IsReferencedOrNotBuiltin(key) {
  438. tmpBuildEnv = append(tmpBuildEnv, env)
  439. }
  440. }
  441. sort.Strings(tmpBuildEnv)
  442. tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...)
  443. return strslice.StrSlice(append(tmpEnv, cmd...))
  444. }
  445. // CMD foo
  446. //
  447. // Set the default command to run in the container (which may be empty).
  448. // Argument handling is the same as RUN.
  449. //
  450. func cmd(req dispatchRequest) error {
  451. if err := req.flags.Parse(); err != nil {
  452. return err
  453. }
  454. runConfig := req.state.runConfig
  455. cmdSlice := handleJSONArgs(req.args, req.attributes)
  456. if !req.attributes["json"] {
  457. cmdSlice = append(getShell(runConfig, req.builder.platform), cmdSlice...)
  458. }
  459. runConfig.Cmd = strslice.StrSlice(cmdSlice)
  460. // set config as already being escaped, this prevents double escaping on windows
  461. runConfig.ArgsEscaped = true
  462. if err := req.builder.commit(req.state, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
  463. return err
  464. }
  465. if len(req.args) != 0 {
  466. req.state.cmdSet = true
  467. }
  468. return nil
  469. }
  470. // parseOptInterval(flag) is the duration of flag.Value, or 0 if
  471. // empty. An error is reported if the value is given and less than minimum duration.
  472. func parseOptInterval(f *Flag) (time.Duration, error) {
  473. s := f.Value
  474. if s == "" {
  475. return 0, nil
  476. }
  477. d, err := time.ParseDuration(s)
  478. if err != nil {
  479. return 0, err
  480. }
  481. if d < container.MinimumDuration {
  482. return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration)
  483. }
  484. return d, nil
  485. }
  486. // HEALTHCHECK foo
  487. //
  488. // Set the default healthcheck command to run in the container (which may be empty).
  489. // Argument handling is the same as RUN.
  490. //
  491. func healthcheck(req dispatchRequest) error {
  492. if len(req.args) == 0 {
  493. return errAtLeastOneArgument("HEALTHCHECK")
  494. }
  495. runConfig := req.state.runConfig
  496. typ := strings.ToUpper(req.args[0])
  497. args := req.args[1:]
  498. if typ == "NONE" {
  499. if len(args) != 0 {
  500. return errors.New("HEALTHCHECK NONE takes no arguments")
  501. }
  502. test := strslice.StrSlice{typ}
  503. runConfig.Healthcheck = &container.HealthConfig{
  504. Test: test,
  505. }
  506. } else {
  507. if runConfig.Healthcheck != nil {
  508. oldCmd := runConfig.Healthcheck.Test
  509. if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
  510. fmt.Fprintf(req.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
  511. }
  512. }
  513. healthcheck := container.HealthConfig{}
  514. flInterval := req.flags.AddString("interval", "")
  515. flTimeout := req.flags.AddString("timeout", "")
  516. flStartPeriod := req.flags.AddString("start-period", "")
  517. flRetries := req.flags.AddString("retries", "")
  518. if err := req.flags.Parse(); err != nil {
  519. return err
  520. }
  521. switch typ {
  522. case "CMD":
  523. cmdSlice := handleJSONArgs(args, req.attributes)
  524. if len(cmdSlice) == 0 {
  525. return errors.New("Missing command after HEALTHCHECK CMD")
  526. }
  527. if !req.attributes["json"] {
  528. typ = "CMD-SHELL"
  529. }
  530. healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
  531. default:
  532. return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
  533. }
  534. interval, err := parseOptInterval(flInterval)
  535. if err != nil {
  536. return err
  537. }
  538. healthcheck.Interval = interval
  539. timeout, err := parseOptInterval(flTimeout)
  540. if err != nil {
  541. return err
  542. }
  543. healthcheck.Timeout = timeout
  544. startPeriod, err := parseOptInterval(flStartPeriod)
  545. if err != nil {
  546. return err
  547. }
  548. healthcheck.StartPeriod = startPeriod
  549. if flRetries.Value != "" {
  550. retries, err := strconv.ParseInt(flRetries.Value, 10, 32)
  551. if err != nil {
  552. return err
  553. }
  554. if retries < 1 {
  555. return fmt.Errorf("--retries must be at least 1 (not %d)", retries)
  556. }
  557. healthcheck.Retries = int(retries)
  558. } else {
  559. healthcheck.Retries = 0
  560. }
  561. runConfig.Healthcheck = &healthcheck
  562. }
  563. return req.builder.commit(req.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck))
  564. }
  565. // ENTRYPOINT /usr/sbin/nginx
  566. //
  567. // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments
  568. // to /usr/sbin/nginx. Uses the default shell if not in JSON format.
  569. //
  570. // Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint
  571. // is initialized at newBuilder time instead of through argument parsing.
  572. //
  573. func entrypoint(req dispatchRequest) error {
  574. if err := req.flags.Parse(); err != nil {
  575. return err
  576. }
  577. runConfig := req.state.runConfig
  578. parsed := handleJSONArgs(req.args, req.attributes)
  579. switch {
  580. case req.attributes["json"]:
  581. // ENTRYPOINT ["echo", "hi"]
  582. runConfig.Entrypoint = strslice.StrSlice(parsed)
  583. case len(parsed) == 0:
  584. // ENTRYPOINT []
  585. runConfig.Entrypoint = nil
  586. default:
  587. // ENTRYPOINT echo hi
  588. runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig, req.builder.platform), parsed[0]))
  589. }
  590. // when setting the entrypoint if a CMD was not explicitly set then
  591. // set the command to nil
  592. if !req.state.cmdSet {
  593. runConfig.Cmd = nil
  594. }
  595. return req.builder.commit(req.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint))
  596. }
  597. // EXPOSE 6667/tcp 7000/tcp
  598. //
  599. // Expose ports for links and port mappings. This all ends up in
  600. // req.runConfig.ExposedPorts for runconfig.
  601. //
  602. func expose(req dispatchRequest) error {
  603. portsTab := req.args
  604. if len(req.args) == 0 {
  605. return errAtLeastOneArgument("EXPOSE")
  606. }
  607. if err := req.flags.Parse(); err != nil {
  608. return err
  609. }
  610. runConfig := req.state.runConfig
  611. if runConfig.ExposedPorts == nil {
  612. runConfig.ExposedPorts = make(nat.PortSet)
  613. }
  614. ports, _, err := nat.ParsePortSpecs(portsTab)
  615. if err != nil {
  616. return err
  617. }
  618. // instead of using ports directly, we build a list of ports and sort it so
  619. // the order is consistent. This prevents cache burst where map ordering
  620. // changes between builds
  621. portList := make([]string, len(ports))
  622. var i int
  623. for port := range ports {
  624. if _, exists := runConfig.ExposedPorts[port]; !exists {
  625. runConfig.ExposedPorts[port] = struct{}{}
  626. }
  627. portList[i] = string(port)
  628. i++
  629. }
  630. sort.Strings(portList)
  631. return req.builder.commit(req.state, "EXPOSE "+strings.Join(portList, " "))
  632. }
  633. // USER foo
  634. //
  635. // Set the user to 'foo' for future commands and when running the
  636. // ENTRYPOINT/CMD at container run time.
  637. //
  638. func user(req dispatchRequest) error {
  639. if len(req.args) != 1 {
  640. return errExactlyOneArgument("USER")
  641. }
  642. if err := req.flags.Parse(); err != nil {
  643. return err
  644. }
  645. req.state.runConfig.User = req.args[0]
  646. return req.builder.commit(req.state, fmt.Sprintf("USER %v", req.args))
  647. }
  648. // VOLUME /foo
  649. //
  650. // Expose the volume /foo for use. Will also accept the JSON array form.
  651. //
  652. func volume(req dispatchRequest) error {
  653. if len(req.args) == 0 {
  654. return errAtLeastOneArgument("VOLUME")
  655. }
  656. if err := req.flags.Parse(); err != nil {
  657. return err
  658. }
  659. runConfig := req.state.runConfig
  660. if runConfig.Volumes == nil {
  661. runConfig.Volumes = map[string]struct{}{}
  662. }
  663. for _, v := range req.args {
  664. v = strings.TrimSpace(v)
  665. if v == "" {
  666. return errors.New("VOLUME specified can not be an empty string")
  667. }
  668. runConfig.Volumes[v] = struct{}{}
  669. }
  670. return req.builder.commit(req.state, fmt.Sprintf("VOLUME %v", req.args))
  671. }
  672. // STOPSIGNAL signal
  673. //
  674. // Set the signal that will be used to kill the container.
  675. func stopSignal(req dispatchRequest) error {
  676. if len(req.args) != 1 {
  677. return errExactlyOneArgument("STOPSIGNAL")
  678. }
  679. sig := req.args[0]
  680. _, err := signal.ParseSignal(sig)
  681. if err != nil {
  682. return validationError{err}
  683. }
  684. req.state.runConfig.StopSignal = sig
  685. return req.builder.commit(req.state, fmt.Sprintf("STOPSIGNAL %v", req.args))
  686. }
  687. // ARG name[=value]
  688. //
  689. // Adds the variable foo to the trusted list of variables that can be passed
  690. // to builder using the --build-arg flag for expansion/substitution or passing to 'run'.
  691. // Dockerfile author may optionally set a default value of this variable.
  692. func arg(req dispatchRequest) error {
  693. if len(req.args) != 1 {
  694. return errExactlyOneArgument("ARG")
  695. }
  696. var (
  697. name string
  698. newValue string
  699. hasDefault bool
  700. )
  701. arg := req.args[0]
  702. // 'arg' can just be a name or name-value pair. Note that this is different
  703. // from 'env' that handles the split of name and value at the parser level.
  704. // The reason for doing it differently for 'arg' is that we support just
  705. // defining an arg and not assign it a value (while 'env' always expects a
  706. // name-value pair). If possible, it will be good to harmonize the two.
  707. if strings.Contains(arg, "=") {
  708. parts := strings.SplitN(arg, "=", 2)
  709. if len(parts[0]) == 0 {
  710. return errBlankCommandNames("ARG")
  711. }
  712. name = parts[0]
  713. newValue = parts[1]
  714. hasDefault = true
  715. } else {
  716. name = arg
  717. hasDefault = false
  718. }
  719. var value *string
  720. if hasDefault {
  721. value = &newValue
  722. }
  723. req.builder.buildArgs.AddArg(name, value)
  724. // Arg before FROM doesn't add a layer
  725. if !req.state.hasFromImage() {
  726. req.builder.buildArgs.AddMetaArg(name, value)
  727. return nil
  728. }
  729. return req.builder.commit(req.state, "ARG "+arg)
  730. }
  731. // SHELL powershell -command
  732. //
  733. // Set the non-default shell to use.
  734. func shell(req dispatchRequest) error {
  735. if err := req.flags.Parse(); err != nil {
  736. return err
  737. }
  738. shellSlice := handleJSONArgs(req.args, req.attributes)
  739. switch {
  740. case len(shellSlice) == 0:
  741. // SHELL []
  742. return errAtLeastOneArgument("SHELL")
  743. case req.attributes["json"]:
  744. // SHELL ["powershell", "-command"]
  745. req.state.runConfig.Shell = strslice.StrSlice(shellSlice)
  746. default:
  747. // SHELL powershell -command - not JSON
  748. return errNotJSON("SHELL", req.original)
  749. }
  750. return req.builder.commit(req.state, fmt.Sprintf("SHELL %v", shellSlice))
  751. }
  752. func errAtLeastOneArgument(command string) error {
  753. return fmt.Errorf("%s requires at least one argument", command)
  754. }
  755. func errExactlyOneArgument(command string) error {
  756. return fmt.Errorf("%s requires exactly one argument", command)
  757. }
  758. func errAtLeastTwoArguments(command string) error {
  759. return fmt.Errorf("%s requires at least two arguments", command)
  760. }
  761. func errBlankCommandNames(command string) error {
  762. return fmt.Errorf("%s names can not be blank", command)
  763. }
  764. func errTooManyArguments(command string) error {
  765. return fmt.Errorf("Bad input to %s, too many arguments", command)
  766. }