dispatchers.go 24 KB

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