parse.go 15 KB

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