mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
7c0570473c
This PR is trying to fix issue #36956. The stage name is case-insensitive by design, so we should use `strings.EqualFold()` as the comparison method to eliminate the case sensitive noise. Also we need to return a pre-defined error code order to avoid below message like: "FIXME: Got an API for which error does not match any expected type!!!: failed to reach build target dev in Dockerfile" Signed-off-by: Dennis Chen <dennis.chen@arm.com>
399 lines
9.4 KiB
Go
399 lines
9.4 KiB
Go
package instructions // import "github.com/docker/docker/builder/dockerfile/instructions"
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/strslice"
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
// KeyValuePair represent an arbitrary named value (useful in slice instead of map[string] string to preserve ordering)
|
|
type KeyValuePair struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
func (kvp *KeyValuePair) String() string {
|
|
return kvp.Key + "=" + kvp.Value
|
|
}
|
|
|
|
// Command is implemented by every command present in a dockerfile
|
|
type Command interface {
|
|
Name() string
|
|
}
|
|
|
|
// KeyValuePairs is a slice of KeyValuePair
|
|
type KeyValuePairs []KeyValuePair
|
|
|
|
// withNameAndCode is the base of every command in a Dockerfile (String() returns its source code)
|
|
type withNameAndCode struct {
|
|
code string
|
|
name string
|
|
}
|
|
|
|
func (c *withNameAndCode) String() string {
|
|
return c.code
|
|
}
|
|
|
|
// Name of the command
|
|
func (c *withNameAndCode) Name() string {
|
|
return c.name
|
|
}
|
|
|
|
func newWithNameAndCode(req parseRequest) withNameAndCode {
|
|
return withNameAndCode{code: strings.TrimSpace(req.original), name: req.command}
|
|
}
|
|
|
|
// SingleWordExpander is a provider for variable expansion where 1 word => 1 output
|
|
type SingleWordExpander func(word string) (string, error)
|
|
|
|
// SupportsSingleWordExpansion interface marks a command as supporting variable expansion
|
|
type SupportsSingleWordExpansion interface {
|
|
Expand(expander SingleWordExpander) error
|
|
}
|
|
|
|
// PlatformSpecific adds platform checks to a command
|
|
type PlatformSpecific interface {
|
|
CheckPlatform(platform string) error
|
|
}
|
|
|
|
func expandKvp(kvp KeyValuePair, expander SingleWordExpander) (KeyValuePair, error) {
|
|
key, err := expander(kvp.Key)
|
|
if err != nil {
|
|
return KeyValuePair{}, err
|
|
}
|
|
value, err := expander(kvp.Value)
|
|
if err != nil {
|
|
return KeyValuePair{}, err
|
|
}
|
|
return KeyValuePair{Key: key, Value: value}, nil
|
|
}
|
|
func expandKvpsInPlace(kvps KeyValuePairs, expander SingleWordExpander) error {
|
|
for i, kvp := range kvps {
|
|
newKvp, err := expandKvp(kvp, expander)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kvps[i] = newKvp
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func expandSliceInPlace(values []string, expander SingleWordExpander) error {
|
|
for i, v := range values {
|
|
newValue, err := expander(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values[i] = newValue
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EnvCommand : ENV key1 value1 [keyN valueN...]
|
|
type EnvCommand struct {
|
|
withNameAndCode
|
|
Env KeyValuePairs // kvp slice instead of map to preserve ordering
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *EnvCommand) Expand(expander SingleWordExpander) error {
|
|
return expandKvpsInPlace(c.Env, expander)
|
|
}
|
|
|
|
// MaintainerCommand : MAINTAINER maintainer_name
|
|
type MaintainerCommand struct {
|
|
withNameAndCode
|
|
Maintainer string
|
|
}
|
|
|
|
// LabelCommand : LABEL some json data describing the image
|
|
//
|
|
// Sets the Label variable foo to bar,
|
|
//
|
|
type LabelCommand struct {
|
|
withNameAndCode
|
|
Labels KeyValuePairs // kvp slice instead of map to preserve ordering
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *LabelCommand) Expand(expander SingleWordExpander) error {
|
|
return expandKvpsInPlace(c.Labels, expander)
|
|
}
|
|
|
|
// SourcesAndDest represent a list of source files and a destination
|
|
type SourcesAndDest []string
|
|
|
|
// Sources list the source paths
|
|
func (s SourcesAndDest) Sources() []string {
|
|
res := make([]string, len(s)-1)
|
|
copy(res, s[:len(s)-1])
|
|
return res
|
|
}
|
|
|
|
// Dest path of the operation
|
|
func (s SourcesAndDest) Dest() string {
|
|
return s[len(s)-1]
|
|
}
|
|
|
|
// AddCommand : ADD foo /path
|
|
//
|
|
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
|
// exist here. If you do not wish to have this automatic handling, use COPY.
|
|
//
|
|
type AddCommand struct {
|
|
withNameAndCode
|
|
SourcesAndDest
|
|
Chown string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *AddCommand) Expand(expander SingleWordExpander) error {
|
|
return expandSliceInPlace(c.SourcesAndDest, expander)
|
|
}
|
|
|
|
// CopyCommand : COPY foo /path
|
|
//
|
|
// Same as 'ADD' but without the tar and remote url handling.
|
|
//
|
|
type CopyCommand struct {
|
|
withNameAndCode
|
|
SourcesAndDest
|
|
From string
|
|
Chown string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *CopyCommand) Expand(expander SingleWordExpander) error {
|
|
return expandSliceInPlace(c.SourcesAndDest, expander)
|
|
}
|
|
|
|
// OnbuildCommand : ONBUILD <some other command>
|
|
type OnbuildCommand struct {
|
|
withNameAndCode
|
|
Expression string
|
|
}
|
|
|
|
// WorkdirCommand : WORKDIR /tmp
|
|
//
|
|
// Set the working directory for future RUN/CMD/etc statements.
|
|
//
|
|
type WorkdirCommand struct {
|
|
withNameAndCode
|
|
Path string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *WorkdirCommand) Expand(expander SingleWordExpander) error {
|
|
p, err := expander(c.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Path = p
|
|
return nil
|
|
}
|
|
|
|
// ShellDependantCmdLine represents a cmdline optionally prepended with the shell
|
|
type ShellDependantCmdLine struct {
|
|
CmdLine strslice.StrSlice
|
|
PrependShell bool
|
|
}
|
|
|
|
// RunCommand : RUN some command yo
|
|
//
|
|
// run a command and commit the image. Args are automatically prepended with
|
|
// the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
|
|
// Windows, in the event there is only one argument The difference in processing:
|
|
//
|
|
// RUN echo hi # sh -c echo hi (Linux)
|
|
// RUN echo hi # cmd /S /C echo hi (Windows)
|
|
// RUN [ "echo", "hi" ] # echo hi
|
|
//
|
|
type RunCommand struct {
|
|
withNameAndCode
|
|
ShellDependantCmdLine
|
|
}
|
|
|
|
// CmdCommand : CMD foo
|
|
//
|
|
// Set the default command to run in the container (which may be empty).
|
|
// Argument handling is the same as RUN.
|
|
//
|
|
type CmdCommand struct {
|
|
withNameAndCode
|
|
ShellDependantCmdLine
|
|
}
|
|
|
|
// HealthCheckCommand : HEALTHCHECK foo
|
|
//
|
|
// Set the default healthcheck command to run in the container (which may be empty).
|
|
// Argument handling is the same as RUN.
|
|
//
|
|
type HealthCheckCommand struct {
|
|
withNameAndCode
|
|
Health *container.HealthConfig
|
|
}
|
|
|
|
// EntrypointCommand : ENTRYPOINT /usr/sbin/nginx
|
|
//
|
|
// Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments
|
|
// to /usr/sbin/nginx. Uses the default shell if not in JSON format.
|
|
//
|
|
// Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint
|
|
// is initialized at newBuilder time instead of through argument parsing.
|
|
//
|
|
type EntrypointCommand struct {
|
|
withNameAndCode
|
|
ShellDependantCmdLine
|
|
}
|
|
|
|
// ExposeCommand : EXPOSE 6667/tcp 7000/tcp
|
|
//
|
|
// Expose ports for links and port mappings. This all ends up in
|
|
// req.runConfig.ExposedPorts for runconfig.
|
|
//
|
|
type ExposeCommand struct {
|
|
withNameAndCode
|
|
Ports []string
|
|
}
|
|
|
|
// UserCommand : USER foo
|
|
//
|
|
// Set the user to 'foo' for future commands and when running the
|
|
// ENTRYPOINT/CMD at container run time.
|
|
//
|
|
type UserCommand struct {
|
|
withNameAndCode
|
|
User string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *UserCommand) Expand(expander SingleWordExpander) error {
|
|
p, err := expander(c.User)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.User = p
|
|
return nil
|
|
}
|
|
|
|
// VolumeCommand : VOLUME /foo
|
|
//
|
|
// Expose the volume /foo for use. Will also accept the JSON array form.
|
|
//
|
|
type VolumeCommand struct {
|
|
withNameAndCode
|
|
Volumes []string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *VolumeCommand) Expand(expander SingleWordExpander) error {
|
|
return expandSliceInPlace(c.Volumes, expander)
|
|
}
|
|
|
|
// StopSignalCommand : STOPSIGNAL signal
|
|
//
|
|
// Set the signal that will be used to kill the container.
|
|
type StopSignalCommand struct {
|
|
withNameAndCode
|
|
Signal string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *StopSignalCommand) Expand(expander SingleWordExpander) error {
|
|
p, err := expander(c.Signal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Signal = p
|
|
return nil
|
|
}
|
|
|
|
// CheckPlatform checks that the command is supported in the target platform
|
|
func (c *StopSignalCommand) CheckPlatform(platform string) error {
|
|
if platform == "windows" {
|
|
return errors.New("The daemon on this platform does not support the command stopsignal")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ArgCommand : ARG name[=value]
|
|
//
|
|
// Adds the variable foo to the trusted list of variables that can be passed
|
|
// to builder using the --build-arg flag for expansion/substitution or passing to 'run'.
|
|
// Dockerfile author may optionally set a default value of this variable.
|
|
type ArgCommand struct {
|
|
withNameAndCode
|
|
Key string
|
|
Value *string
|
|
}
|
|
|
|
// Expand variables
|
|
func (c *ArgCommand) Expand(expander SingleWordExpander) error {
|
|
p, err := expander(c.Key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Key = p
|
|
if c.Value != nil {
|
|
p, err = expander(*c.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Value = &p
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ShellCommand : SHELL powershell -command
|
|
//
|
|
// Set the non-default shell to use.
|
|
type ShellCommand struct {
|
|
withNameAndCode
|
|
Shell strslice.StrSlice
|
|
}
|
|
|
|
// Stage represents a single stage in a multi-stage build
|
|
type Stage struct {
|
|
Name string
|
|
Commands []Command
|
|
BaseName string
|
|
SourceCode string
|
|
Platform specs.Platform
|
|
}
|
|
|
|
// AddCommand to the stage
|
|
func (s *Stage) AddCommand(cmd Command) {
|
|
// todo: validate cmd type
|
|
s.Commands = append(s.Commands, cmd)
|
|
}
|
|
|
|
// IsCurrentStage check if the stage name is the current stage
|
|
func IsCurrentStage(s []Stage, name string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
return s[len(s)-1].Name == name
|
|
}
|
|
|
|
// CurrentStage return the last stage in a slice
|
|
func CurrentStage(s []Stage) (*Stage, error) {
|
|
if len(s) == 0 {
|
|
return nil, errors.New("No build stage in current context")
|
|
}
|
|
return &s[len(s)-1], nil
|
|
}
|
|
|
|
// HasStage looks for the presence of a given stage name
|
|
func HasStage(s []Stage, name string) (int, bool) {
|
|
for i, stage := range s {
|
|
// Stage name is case-insensitive by design
|
|
if strings.EqualFold(stage.Name, name) {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|