parse.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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. specPlatform := system.ParsePlatform(flPlatform.Value)
  252. if err := system.ValidatePlatform(specPlatform); err != nil {
  253. return nil, fmt.Errorf("invalid platform %q on FROM", flPlatform.Value)
  254. }
  255. if specPlatform.OS != "" && !system.IsOSSupported(specPlatform.OS) {
  256. return nil, fmt.Errorf("unsupported platform %q on FROM", flPlatform.Value)
  257. }
  258. if err != nil {
  259. return nil, err
  260. }
  261. code := strings.TrimSpace(req.original)
  262. return &Stage{
  263. BaseName: req.args[0],
  264. Name: stageName,
  265. SourceCode: code,
  266. Commands: []Command{},
  267. OperatingSystem: specPlatform.OS,
  268. }, nil
  269. }
  270. func parseBuildStageName(args []string) (string, error) {
  271. stageName := ""
  272. switch {
  273. case len(args) == 3 && strings.EqualFold(args[1], "as"):
  274. stageName = strings.ToLower(args[2])
  275. if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", stageName); !ok {
  276. return "", errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", stageName)
  277. }
  278. case len(args) != 1:
  279. return "", errors.New("FROM requires either one or three arguments")
  280. }
  281. return stageName, nil
  282. }
  283. func parseOnBuild(req parseRequest) (*OnbuildCommand, error) {
  284. if len(req.args) == 0 {
  285. return nil, errAtLeastOneArgument("ONBUILD")
  286. }
  287. if err := req.flags.Parse(); err != nil {
  288. return nil, err
  289. }
  290. triggerInstruction := strings.ToUpper(strings.TrimSpace(req.args[0]))
  291. switch strings.ToUpper(triggerInstruction) {
  292. case "ONBUILD":
  293. return nil, errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
  294. case "MAINTAINER", "FROM":
  295. return nil, fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
  296. }
  297. original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
  298. return &OnbuildCommand{
  299. Expression: original,
  300. withNameAndCode: newWithNameAndCode(req),
  301. }, nil
  302. }
  303. func parseWorkdir(req parseRequest) (*WorkdirCommand, error) {
  304. if len(req.args) != 1 {
  305. return nil, errExactlyOneArgument("WORKDIR")
  306. }
  307. err := req.flags.Parse()
  308. if err != nil {
  309. return nil, err
  310. }
  311. return &WorkdirCommand{
  312. Path: req.args[0],
  313. withNameAndCode: newWithNameAndCode(req),
  314. }, nil
  315. }
  316. func parseShellDependentCommand(req parseRequest, emptyAsNil bool) ShellDependantCmdLine {
  317. args := handleJSONArgs(req.args, req.attributes)
  318. cmd := strslice.StrSlice(args)
  319. if emptyAsNil && len(cmd) == 0 {
  320. cmd = nil
  321. }
  322. return ShellDependantCmdLine{
  323. CmdLine: cmd,
  324. PrependShell: !req.attributes["json"],
  325. }
  326. }
  327. func parseRun(req parseRequest) (*RunCommand, error) {
  328. if err := req.flags.Parse(); err != nil {
  329. return nil, err
  330. }
  331. return &RunCommand{
  332. ShellDependantCmdLine: parseShellDependentCommand(req, false),
  333. withNameAndCode: newWithNameAndCode(req),
  334. }, nil
  335. }
  336. func parseCmd(req parseRequest) (*CmdCommand, error) {
  337. if err := req.flags.Parse(); err != nil {
  338. return nil, err
  339. }
  340. return &CmdCommand{
  341. ShellDependantCmdLine: parseShellDependentCommand(req, false),
  342. withNameAndCode: newWithNameAndCode(req),
  343. }, nil
  344. }
  345. func parseEntrypoint(req parseRequest) (*EntrypointCommand, error) {
  346. if err := req.flags.Parse(); err != nil {
  347. return nil, err
  348. }
  349. cmd := &EntrypointCommand{
  350. ShellDependantCmdLine: parseShellDependentCommand(req, true),
  351. withNameAndCode: newWithNameAndCode(req),
  352. }
  353. return cmd, nil
  354. }
  355. // parseOptInterval(flag) is the duration of flag.Value, or 0 if
  356. // empty. An error is reported if the value is given and less than minimum duration.
  357. func parseOptInterval(f *Flag) (time.Duration, error) {
  358. s := f.Value
  359. if s == "" {
  360. return 0, nil
  361. }
  362. d, err := time.ParseDuration(s)
  363. if err != nil {
  364. return 0, err
  365. }
  366. if d < container.MinimumDuration {
  367. return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration)
  368. }
  369. return d, nil
  370. }
  371. func parseHealthcheck(req parseRequest) (*HealthCheckCommand, error) {
  372. if len(req.args) == 0 {
  373. return nil, errAtLeastOneArgument("HEALTHCHECK")
  374. }
  375. cmd := &HealthCheckCommand{
  376. withNameAndCode: newWithNameAndCode(req),
  377. }
  378. typ := strings.ToUpper(req.args[0])
  379. args := req.args[1:]
  380. if typ == "NONE" {
  381. if len(args) != 0 {
  382. return nil, errors.New("HEALTHCHECK NONE takes no arguments")
  383. }
  384. test := strslice.StrSlice{typ}
  385. cmd.Health = &container.HealthConfig{
  386. Test: test,
  387. }
  388. } else {
  389. healthcheck := container.HealthConfig{}
  390. flInterval := req.flags.AddString("interval", "")
  391. flTimeout := req.flags.AddString("timeout", "")
  392. flStartPeriod := req.flags.AddString("start-period", "")
  393. flRetries := req.flags.AddString("retries", "")
  394. if err := req.flags.Parse(); err != nil {
  395. return nil, err
  396. }
  397. switch typ {
  398. case "CMD":
  399. cmdSlice := handleJSONArgs(args, req.attributes)
  400. if len(cmdSlice) == 0 {
  401. return nil, errors.New("Missing command after HEALTHCHECK CMD")
  402. }
  403. if !req.attributes["json"] {
  404. typ = "CMD-SHELL"
  405. }
  406. healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
  407. default:
  408. return nil, fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
  409. }
  410. interval, err := parseOptInterval(flInterval)
  411. if err != nil {
  412. return nil, err
  413. }
  414. healthcheck.Interval = interval
  415. timeout, err := parseOptInterval(flTimeout)
  416. if err != nil {
  417. return nil, err
  418. }
  419. healthcheck.Timeout = timeout
  420. startPeriod, err := parseOptInterval(flStartPeriod)
  421. if err != nil {
  422. return nil, err
  423. }
  424. healthcheck.StartPeriod = startPeriod
  425. if flRetries.Value != "" {
  426. retries, err := strconv.ParseInt(flRetries.Value, 10, 32)
  427. if err != nil {
  428. return nil, err
  429. }
  430. if retries < 1 {
  431. return nil, fmt.Errorf("--retries must be at least 1 (not %d)", retries)
  432. }
  433. healthcheck.Retries = int(retries)
  434. } else {
  435. healthcheck.Retries = 0
  436. }
  437. cmd.Health = &healthcheck
  438. }
  439. return cmd, nil
  440. }
  441. func parseExpose(req parseRequest) (*ExposeCommand, error) {
  442. portsTab := req.args
  443. if len(req.args) == 0 {
  444. return nil, errAtLeastOneArgument("EXPOSE")
  445. }
  446. if err := req.flags.Parse(); err != nil {
  447. return nil, err
  448. }
  449. sort.Strings(portsTab)
  450. return &ExposeCommand{
  451. Ports: portsTab,
  452. withNameAndCode: newWithNameAndCode(req),
  453. }, nil
  454. }
  455. func parseUser(req parseRequest) (*UserCommand, error) {
  456. if len(req.args) != 1 {
  457. return nil, errExactlyOneArgument("USER")
  458. }
  459. if err := req.flags.Parse(); err != nil {
  460. return nil, err
  461. }
  462. return &UserCommand{
  463. User: req.args[0],
  464. withNameAndCode: newWithNameAndCode(req),
  465. }, nil
  466. }
  467. func parseVolume(req parseRequest) (*VolumeCommand, error) {
  468. if len(req.args) == 0 {
  469. return nil, errAtLeastOneArgument("VOLUME")
  470. }
  471. if err := req.flags.Parse(); err != nil {
  472. return nil, err
  473. }
  474. cmd := &VolumeCommand{
  475. withNameAndCode: newWithNameAndCode(req),
  476. }
  477. for _, v := range req.args {
  478. v = strings.TrimSpace(v)
  479. if v == "" {
  480. return nil, errors.New("VOLUME specified can not be an empty string")
  481. }
  482. cmd.Volumes = append(cmd.Volumes, v)
  483. }
  484. return cmd, nil
  485. }
  486. func parseStopSignal(req parseRequest) (*StopSignalCommand, error) {
  487. if len(req.args) != 1 {
  488. return nil, errExactlyOneArgument("STOPSIGNAL")
  489. }
  490. sig := req.args[0]
  491. cmd := &StopSignalCommand{
  492. Signal: sig,
  493. withNameAndCode: newWithNameAndCode(req),
  494. }
  495. return cmd, nil
  496. }
  497. func parseArg(req parseRequest) (*ArgCommand, error) {
  498. if len(req.args) != 1 {
  499. return nil, errExactlyOneArgument("ARG")
  500. }
  501. var (
  502. name string
  503. newValue *string
  504. )
  505. arg := req.args[0]
  506. // 'arg' can just be a name or name-value pair. Note that this is different
  507. // from 'env' that handles the split of name and value at the parser level.
  508. // The reason for doing it differently for 'arg' is that we support just
  509. // defining an arg and not assign it a value (while 'env' always expects a
  510. // name-value pair). If possible, it will be good to harmonize the two.
  511. if strings.Contains(arg, "=") {
  512. parts := strings.SplitN(arg, "=", 2)
  513. if len(parts[0]) == 0 {
  514. return nil, errBlankCommandNames("ARG")
  515. }
  516. name = parts[0]
  517. newValue = &parts[1]
  518. } else {
  519. name = arg
  520. }
  521. return &ArgCommand{
  522. Key: name,
  523. Value: newValue,
  524. withNameAndCode: newWithNameAndCode(req),
  525. }, nil
  526. }
  527. func parseShell(req parseRequest) (*ShellCommand, error) {
  528. if err := req.flags.Parse(); err != nil {
  529. return nil, err
  530. }
  531. shellSlice := handleJSONArgs(req.args, req.attributes)
  532. switch {
  533. case len(shellSlice) == 0:
  534. // SHELL []
  535. return nil, errAtLeastOneArgument("SHELL")
  536. case req.attributes["json"]:
  537. // SHELL ["powershell", "-command"]
  538. return &ShellCommand{
  539. Shell: strslice.StrSlice(shellSlice),
  540. withNameAndCode: newWithNameAndCode(req),
  541. }, nil
  542. default:
  543. // SHELL powershell -command - not JSON
  544. return nil, errNotJSON("SHELL", req.original)
  545. }
  546. }
  547. func errAtLeastOneArgument(command string) error {
  548. return errors.Errorf("%s requires at least one argument", command)
  549. }
  550. func errExactlyOneArgument(command string) error {
  551. return errors.Errorf("%s requires exactly one argument", command)
  552. }
  553. func errNoDestinationArgument(command string) error {
  554. return errors.Errorf("%s requires at least two arguments, but only one was provided. Destination could not be determined.", command)
  555. }
  556. func errBlankCommandNames(command string) error {
  557. return errors.Errorf("%s names can not be blank", command)
  558. }
  559. func errTooManyArguments(command string) error {
  560. return errors.Errorf("Bad input to %s, too many arguments", command)
  561. }