github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/builder/dockerfile/instructions/parse.go (about)

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