dispatchers.go 24 KB

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