parse.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. package instructions // import "github.com/docker/docker/builder/dockerfile/instructions"
  2. import (
  3. "fmt"
  4. "regexp"
  5. "sort"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/docker/docker/api/types/container"
  10. "github.com/docker/docker/api/types/strslice"
  11. "github.com/docker/docker/builder/dockerfile/command"
  12. "github.com/docker/docker/builder/dockerfile/parser"
  13. "github.com/docker/docker/pkg/system"
  14. "github.com/pkg/errors"
  15. )
  16. type parseRequest struct {
  17. command string
  18. args []string
  19. attributes map[string]bool
  20. flags *BFlags
  21. original string
  22. }
  23. func nodeArgs(node *parser.Node) []string {
  24. result := []string{}
  25. for ; node.Next != nil; node = node.Next {
  26. arg := node.Next
  27. if len(arg.Children) == 0 {
  28. result = append(result, arg.Value)
  29. } else if len(arg.Children) == 1 {
  30. //sub command
  31. result = append(result, arg.Children[0].Value)
  32. result = append(result, nodeArgs(arg.Children[0])...)
  33. }
  34. }
  35. return result
  36. }
  37. func newParseRequestFromNode(node *parser.Node) parseRequest {
  38. return parseRequest{
  39. command: node.Value,
  40. args: nodeArgs(node),
  41. attributes: node.Attributes,
  42. original: node.Original,
  43. flags: NewBFlagsWithArgs(node.Flags),
  44. }
  45. }
  46. // ParseInstruction converts an AST to a typed instruction (either a command or a build stage beginning when encountering a `FROM` statement)
  47. func ParseInstruction(node *parser.Node) (interface{}, error) {
  48. req := newParseRequestFromNode(node)
  49. switch node.Value {
  50. case command.Env:
  51. return parseEnv(req)
  52. case command.Maintainer:
  53. return parseMaintainer(req)
  54. case command.Label:
  55. return parseLabel(req)
  56. case command.Add:
  57. return parseAdd(req)
  58. case command.Copy:
  59. return parseCopy(req)
  60. case command.From:
  61. return parseFrom(req)
  62. case command.Onbuild:
  63. return parseOnBuild(req)
  64. case command.Workdir:
  65. return parseWorkdir(req)
  66. case command.Run:
  67. return parseRun(req)
  68. case command.Cmd:
  69. return parseCmd(req)
  70. case command.Healthcheck:
  71. return parseHealthcheck(req)
  72. case command.Entrypoint:
  73. return parseEntrypoint(req)
  74. case command.Expose:
  75. return parseExpose(req)
  76. case command.User:
  77. return parseUser(req)
  78. case command.Volume:
  79. return parseVolume(req)
  80. case command.StopSignal:
  81. return parseStopSignal(req)
  82. case command.Arg:
  83. return parseArg(req)
  84. case command.Shell:
  85. return parseShell(req)
  86. }
  87. return nil, &UnknownInstruction{Instruction: node.Value, Line: node.StartLine}
  88. }
  89. // ParseCommand converts an AST to a typed Command
  90. func ParseCommand(node *parser.Node) (Command, error) {
  91. s, err := ParseInstruction(node)
  92. if err != nil {
  93. return nil, err
  94. }
  95. if c, ok := s.(Command); ok {
  96. return c, nil
  97. }
  98. return nil, errors.Errorf("%T is not a command type", s)
  99. }
  100. // UnknownInstruction represents an error occurring when a command is unresolvable
  101. type UnknownInstruction struct {
  102. Line int
  103. Instruction string
  104. }
  105. func (e *UnknownInstruction) Error() string {
  106. return fmt.Sprintf("unknown instruction: %s", strings.ToUpper(e.Instruction))
  107. }
  108. // IsUnknownInstruction checks if the error is an UnknownInstruction or a parseError containing an UnknownInstruction
  109. func IsUnknownInstruction(err error) bool {
  110. _, ok := err.(*UnknownInstruction)
  111. if !ok {
  112. var pe *parseError
  113. if pe, ok = err.(*parseError); ok {
  114. _, ok = pe.inner.(*UnknownInstruction)
  115. }
  116. }
  117. return ok
  118. }
  119. type parseError struct {
  120. inner error
  121. node *parser.Node
  122. }
  123. func (e *parseError) Error() string {
  124. return fmt.Sprintf("Dockerfile parse error line %d: %v", e.node.StartLine, e.inner.Error())
  125. }
  126. // Parse a docker file into a collection of buildable stages
  127. func Parse(ast *parser.Node) (stages []Stage, metaArgs []ArgCommand, err error) {
  128. for _, n := range ast.Children {
  129. cmd, err := ParseInstruction(n)
  130. if err != nil {
  131. return nil, nil, &parseError{inner: err, node: n}
  132. }
  133. if len(stages) == 0 {
  134. // meta arg case
  135. if a, isArg := cmd.(*ArgCommand); isArg {
  136. metaArgs = append(metaArgs, *a)
  137. continue
  138. }
  139. }
  140. switch c := cmd.(type) {
  141. case *Stage:
  142. stages = append(stages, *c)
  143. case Command:
  144. stage, err := CurrentStage(stages)
  145. if err != nil {
  146. return nil, nil, err
  147. }
  148. stage.AddCommand(c)
  149. default:
  150. return nil, nil, errors.Errorf("%T is not a command type", cmd)
  151. }
  152. }
  153. return stages, metaArgs, nil
  154. }
  155. func parseKvps(args []string, cmdName string) (KeyValuePairs, error) {
  156. if len(args) == 0 {
  157. return nil, errAtLeastOneArgument(cmdName)
  158. }
  159. if len(args)%2 != 0 {
  160. // should never get here, but just in case
  161. return nil, errTooManyArguments(cmdName)
  162. }
  163. var res KeyValuePairs
  164. for j := 0; j < len(args); j += 2 {
  165. if len(args[j]) == 0 {
  166. return nil, errBlankCommandNames(cmdName)
  167. }
  168. name := args[j]
  169. value := args[j+1]
  170. res = append(res, KeyValuePair{Key: name, Value: value})
  171. }
  172. return res, nil
  173. }
  174. func parseEnv(req parseRequest) (*EnvCommand, error) {
  175. if err := req.flags.Parse(); err != nil {
  176. return nil, err
  177. }
  178. envs, err := parseKvps(req.args, "ENV")
  179. if err != nil {
  180. return nil, err
  181. }
  182. return &EnvCommand{
  183. Env: envs,
  184. withNameAndCode: newWithNameAndCode(req),
  185. }, nil
  186. }
  187. func parseMaintainer(req parseRequest) (*MaintainerCommand, error) {
  188. if len(req.args) != 1 {
  189. return nil, errExactlyOneArgument("MAINTAINER")
  190. }
  191. if err := req.flags.Parse(); err != nil {
  192. return nil, err
  193. }
  194. return &MaintainerCommand{
  195. Maintainer: req.args[0],
  196. withNameAndCode: newWithNameAndCode(req),
  197. }, nil
  198. }
  199. func parseLabel(req parseRequest) (*LabelCommand, error) {
  200. if err := req.flags.Parse(); err != nil {
  201. return nil, err
  202. }
  203. labels, err := parseKvps(req.args, "LABEL")
  204. if err != nil {
  205. return nil, err
  206. }
  207. return &LabelCommand{
  208. Labels: labels,
  209. withNameAndCode: newWithNameAndCode(req),
  210. }, nil
  211. }
  212. func parseAdd(req parseRequest) (*AddCommand, error) {
  213. if len(req.args) < 2 {
  214. return nil, errNoDestinationArgument("ADD")
  215. }
  216. flChown := req.flags.AddString("chown", "")
  217. if err := req.flags.Parse(); err != nil {
  218. return nil, err
  219. }
  220. return &AddCommand{
  221. SourcesAndDest: SourcesAndDest(req.args),
  222. withNameAndCode: newWithNameAndCode(req),
  223. Chown: flChown.Value,
  224. }, nil
  225. }
  226. func parseCopy(req parseRequest) (*CopyCommand, error) {
  227. if len(req.args) < 2 {
  228. return nil, errNoDestinationArgument("COPY")
  229. }
  230. flChown := req.flags.AddString("chown", "")
  231. flFrom := req.flags.AddString("from", "")
  232. if err := req.flags.Parse(); err != nil {
  233. return nil, err
  234. }
  235. return &CopyCommand{
  236. SourcesAndDest: SourcesAndDest(req.args),
  237. From: flFrom.Value,
  238. withNameAndCode: newWithNameAndCode(req),
  239. Chown: flChown.Value,
  240. }, nil
  241. }
  242. func parseFrom(req parseRequest) (*Stage, error) {
  243. stageName, err := parseBuildStageName(req.args)
  244. if err != nil {
  245. return nil, err
  246. }
  247. flPlatform := req.flags.AddString("platform", "")
  248. if err := req.flags.Parse(); err != nil {
  249. return nil, err
  250. }
  251. code := strings.TrimSpace(req.original)
  252. return &Stage{
  253. BaseName: req.args[0],
  254. Name: stageName,
  255. SourceCode: code,
  256. Commands: []Command{},
  257. Platform: *system.ParsePlatform(flPlatform.Value),
  258. }, nil
  259. }
  260. func parseBuildStageName(args []string) (string, error) {
  261. stageName := ""
  262. switch {
  263. case len(args) == 3 && strings.EqualFold(args[1], "as"):
  264. stageName = strings.ToLower(args[2])
  265. if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", stageName); !ok {
  266. return "", errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", stageName)
  267. }
  268. case len(args) != 1:
  269. return "", errors.New("FROM requires either one or three arguments")
  270. }
  271. return stageName, nil
  272. }
  273. func parseOnBuild(req parseRequest) (*OnbuildCommand, error) {
  274. if len(req.args) == 0 {
  275. return nil, errAtLeastOneArgument("ONBUILD")
  276. }
  277. if err := req.flags.Parse(); err != nil {
  278. return nil, err
  279. }
  280. triggerInstruction := strings.ToUpper(strings.TrimSpace(req.args[0]))
  281. switch strings.ToUpper(triggerInstruction) {
  282. case "ONBUILD":
  283. return nil, errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
  284. case "MAINTAINER", "FROM":
  285. return nil, fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
  286. }
  287. original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
  288. return &OnbuildCommand{
  289. Expression: original,
  290. withNameAndCode: newWithNameAndCode(req),
  291. }, nil
  292. }
  293. func parseWorkdir(req parseRequest) (*WorkdirCommand, error) {
  294. if len(req.args) != 1 {
  295. return nil, errExactlyOneArgument("WORKDIR")
  296. }
  297. err := req.flags.Parse()
  298. if err != nil {
  299. return nil, err
  300. }
  301. return &WorkdirCommand{
  302. Path: req.args[0],
  303. withNameAndCode: newWithNameAndCode(req),
  304. }, nil
  305. }
  306. func parseShellDependentCommand(req parseRequest, emptyAsNil bool) ShellDependantCmdLine {
  307. args := handleJSONArgs(req.args, req.attributes)
  308. cmd := strslice.StrSlice(args)
  309. if emptyAsNil && len(cmd) == 0 {
  310. cmd = nil
  311. }
  312. return ShellDependantCmdLine{
  313. CmdLine: cmd,
  314. PrependShell: !req.attributes["json"],
  315. }
  316. }
  317. func parseRun(req parseRequest) (*RunCommand, error) {
  318. if err := req.flags.Parse(); err != nil {
  319. return nil, err
  320. }
  321. return &RunCommand{
  322. ShellDependantCmdLine: parseShellDependentCommand(req, false),
  323. withNameAndCode: newWithNameAndCode(req),
  324. }, nil
  325. }
  326. func parseCmd(req parseRequest) (*CmdCommand, error) {
  327. if err := req.flags.Parse(); err != nil {
  328. return nil, err
  329. }
  330. return &CmdCommand{
  331. ShellDependantCmdLine: parseShellDependentCommand(req, false),
  332. withNameAndCode: newWithNameAndCode(req),
  333. }, nil
  334. }
  335. func parseEntrypoint(req parseRequest) (*EntrypointCommand, error) {
  336. if err := req.flags.Parse(); err != nil {
  337. return nil, err
  338. }
  339. cmd := &EntrypointCommand{
  340. ShellDependantCmdLine: parseShellDependentCommand(req, true),
  341. withNameAndCode: newWithNameAndCode(req),
  342. }
  343. return cmd, nil
  344. }
  345. // parseOptInterval(flag) is the duration of flag.Value, or 0 if
  346. // empty. An error is reported if the value is given and less than minimum duration.
  347. func parseOptInterval(f *Flag) (time.Duration, error) {
  348. s := f.Value
  349. if s == "" {
  350. return 0, nil
  351. }
  352. d, err := time.ParseDuration(s)
  353. if err != nil {
  354. return 0, err
  355. }
  356. if d < container.MinimumDuration {
  357. return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration)
  358. }
  359. return d, nil
  360. }
  361. func parseHealthcheck(req parseRequest) (*HealthCheckCommand, error) {
  362. if len(req.args) == 0 {
  363. return nil, errAtLeastOneArgument("HEALTHCHECK")
  364. }
  365. cmd := &HealthCheckCommand{
  366. withNameAndCode: newWithNameAndCode(req),
  367. }
  368. typ := strings.ToUpper(req.args[0])
  369. args := req.args[1:]
  370. if typ == "NONE" {
  371. if len(args) != 0 {
  372. return nil, errors.New("HEALTHCHECK NONE takes no arguments")
  373. }
  374. test := strslice.StrSlice{typ}
  375. cmd.Health = &container.HealthConfig{
  376. Test: test,
  377. }
  378. } else {
  379. healthcheck := container.HealthConfig{}
  380. flInterval := req.flags.AddString("interval", "")
  381. flTimeout := req.flags.AddString("timeout", "")
  382. flStartPeriod := req.flags.AddString("start-period", "")
  383. flRetries := req.flags.AddString("retries", "")
  384. if err := req.flags.Parse(); err != nil {
  385. return nil, err
  386. }
  387. switch typ {
  388. case "CMD":
  389. cmdSlice := handleJSONArgs(args, req.attributes)
  390. if len(cmdSlice) == 0 {
  391. return nil, errors.New("Missing command after HEALTHCHECK CMD")
  392. }
  393. if !req.attributes["json"] {
  394. typ = "CMD-SHELL"
  395. }
  396. healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
  397. default:
  398. return nil, fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
  399. }
  400. interval, err := parseOptInterval(flInterval)
  401. if err != nil {
  402. return nil, err
  403. }
  404. healthcheck.Interval = interval
  405. timeout, err := parseOptInterval(flTimeout)
  406. if err != nil {
  407. return nil, err
  408. }
  409. healthcheck.Timeout = timeout
  410. startPeriod, err := parseOptInterval(flStartPeriod)
  411. if err != nil {
  412. return nil, err
  413. }
  414. healthcheck.StartPeriod = startPeriod
  415. if flRetries.Value != "" {
  416. retries, err := strconv.ParseInt(flRetries.Value, 10, 32)
  417. if err != nil {
  418. return nil, err
  419. }
  420. if retries < 1 {
  421. return nil, fmt.Errorf("--retries must be at least 1 (not %d)", retries)
  422. }
  423. healthcheck.Retries = int(retries)
  424. } else {
  425. healthcheck.Retries = 0
  426. }
  427. cmd.Health = &healthcheck
  428. }
  429. return cmd, nil
  430. }
  431. func parseExpose(req parseRequest) (*ExposeCommand, error) {
  432. portsTab := req.args
  433. if len(req.args) == 0 {
  434. return nil, errAtLeastOneArgument("EXPOSE")
  435. }
  436. if err := req.flags.Parse(); err != nil {
  437. return nil, err
  438. }
  439. sort.Strings(portsTab)
  440. return &ExposeCommand{
  441. Ports: portsTab,
  442. withNameAndCode: newWithNameAndCode(req),
  443. }, nil
  444. }
  445. func parseUser(req parseRequest) (*UserCommand, error) {
  446. if len(req.args) != 1 {
  447. return nil, errExactlyOneArgument("USER")
  448. }
  449. if err := req.flags.Parse(); err != nil {
  450. return nil, err
  451. }
  452. return &UserCommand{
  453. User: req.args[0],
  454. withNameAndCode: newWithNameAndCode(req),
  455. }, nil
  456. }
  457. func parseVolume(req parseRequest) (*VolumeCommand, error) {
  458. if len(req.args) == 0 {
  459. return nil, errAtLeastOneArgument("VOLUME")
  460. }
  461. if err := req.flags.Parse(); err != nil {
  462. return nil, err
  463. }
  464. cmd := &VolumeCommand{
  465. withNameAndCode: newWithNameAndCode(req),
  466. }
  467. for _, v := range req.args {
  468. v = strings.TrimSpace(v)
  469. if v == "" {
  470. return nil, errors.New("VOLUME specified can not be an empty string")
  471. }
  472. cmd.Volumes = append(cmd.Volumes, v)
  473. }
  474. return cmd, nil
  475. }
  476. func parseStopSignal(req parseRequest) (*StopSignalCommand, error) {
  477. if len(req.args) != 1 {
  478. return nil, errExactlyOneArgument("STOPSIGNAL")
  479. }
  480. sig := req.args[0]
  481. cmd := &StopSignalCommand{
  482. Signal: sig,
  483. withNameAndCode: newWithNameAndCode(req),
  484. }
  485. return cmd, nil
  486. }
  487. func parseArg(req parseRequest) (*ArgCommand, error) {
  488. if len(req.args) != 1 {
  489. return nil, errExactlyOneArgument("ARG")
  490. }
  491. var (
  492. name string
  493. newValue *string
  494. )
  495. arg := req.args[0]
  496. // 'arg' can just be a name or name-value pair. Note that this is different
  497. // from 'env' that handles the split of name and value at the parser level.
  498. // The reason for doing it differently for 'arg' is that we support just
  499. // defining an arg and not assign it a value (while 'env' always expects a
  500. // name-value pair). If possible, it will be good to harmonize the two.
  501. if strings.Contains(arg, "=") {
  502. parts := strings.SplitN(arg, "=", 2)
  503. if len(parts[0]) == 0 {
  504. return nil, errBlankCommandNames("ARG")
  505. }
  506. name = parts[0]
  507. newValue = &parts[1]
  508. } else {
  509. name = arg
  510. }
  511. return &ArgCommand{
  512. Key: name,
  513. Value: newValue,
  514. withNameAndCode: newWithNameAndCode(req),
  515. }, nil
  516. }
  517. func parseShell(req parseRequest) (*ShellCommand, error) {
  518. if err := req.flags.Parse(); err != nil {
  519. return nil, err
  520. }
  521. shellSlice := handleJSONArgs(req.args, req.attributes)
  522. switch {
  523. case len(shellSlice) == 0:
  524. // SHELL []
  525. return nil, errAtLeastOneArgument("SHELL")
  526. case req.attributes["json"]:
  527. // SHELL ["powershell", "-command"]
  528. return &ShellCommand{
  529. Shell: strslice.StrSlice(shellSlice),
  530. withNameAndCode: newWithNameAndCode(req),
  531. }, nil
  532. default:
  533. // SHELL powershell -command - not JSON
  534. return nil, errNotJSON("SHELL", req.original)
  535. }
  536. }
  537. func errAtLeastOneArgument(command string) error {
  538. return errors.Errorf("%s requires at least one argument", command)
  539. }
  540. func errExactlyOneArgument(command string) error {
  541. return errors.Errorf("%s requires exactly one argument", command)
  542. }
  543. func errNoDestinationArgument(command string) error {
  544. return errors.Errorf("%s requires at least two arguments, but only one was provided. Destination could not be determined.", command)
  545. }
  546. func errBlankCommandNames(command string) error {
  547. return errors.Errorf("%s names can not be blank", command)
  548. }
  549. func errTooManyArguments(command string) error {
  550. return errors.Errorf("Bad input to %s, too many arguments", command)
  551. }