123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- package instructions
- import (
- "strings"
- "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/strslice"
- "github.com/moby/buildkit/frontend/dockerfile/parser"
- "github.com/pkg/errors"
- )
- // 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
- }
- // KeyValuePairOptional is the same as KeyValuePair but Value is optional
- type KeyValuePairOptional struct {
- Key string
- Value *string
- Comment string
- }
- func (kvpo *KeyValuePairOptional) ValueString() string {
- v := ""
- if kvpo.Value != nil {
- v = *kvpo.Value
- }
- return v
- }
- // Command is implemented by every command present in a dockerfile
- type Command interface {
- Name() string
- Location() []parser.Range
- }
- // 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
- location []parser.Range
- }
- func (c *withNameAndCode) String() string {
- return c.code
- }
- // Name of the command
- func (c *withNameAndCode) Name() string {
- return c.name
- }
- // Location of the command in source
- func (c *withNameAndCode) Location() []parser.Range {
- return c.location
- }
- func newWithNameAndCode(req parseRequest) withNameAndCode {
- return withNameAndCode{code: strings.TrimSpace(req.original), name: req.command, location: req.location}
- }
- // 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
- }
- // NewLabelCommand creates a new 'LABEL' command
- func NewLabelCommand(k string, v string, NoExp bool) *LabelCommand {
- kvp := KeyValuePair{Key: k, Value: v}
- c := "LABEL "
- c += kvp.String()
- nc := withNameAndCode{code: c, name: "label"}
- cmd := &LabelCommand{
- withNameAndCode: nc,
- Labels: KeyValuePairs{
- kvp,
- },
- noExpand: NoExp,
- }
- return cmd
- }
- // 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
- noExpand bool
- }
- // Expand variables
- func (c *LabelCommand) Expand(expander SingleWordExpander) error {
- if c.noExpand {
- return nil
- }
- 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 (http, https) handling
- // exist here. If you do not wish to have this automatic handling, use COPY.
- //
- type AddCommand struct {
- withNameAndCode
- SourcesAndDest
- Chown string
- Chmod string
- }
- // Expand variables
- func (c *AddCommand) Expand(expander SingleWordExpander) error {
- expandedChown, err := expander(c.Chown)
- if err != nil {
- return err
- }
- c.Chown = expandedChown
- 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
- Chmod string
- }
- // Expand variables
- func (c *CopyCommand) Expand(expander SingleWordExpander) error {
- expandedChown, err := expander(c.Chown)
- if err != nil {
- return err
- }
- c.Chown = expandedChown
- 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
- withExternalData
- 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
- Args []KeyValuePairOptional
- }
- // Expand variables
- func (c *ArgCommand) Expand(expander SingleWordExpander) error {
- for i, v := range c.Args {
- p, err := expander(v.Key)
- if err != nil {
- return err
- }
- v.Key = p
- if v.Value != nil {
- p, err = expander(*v.Value)
- if err != nil {
- return err
- }
- v.Value = &p
- }
- c.Args[i] = v
- }
- 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 string
- Location []parser.Range
- Comment string
- }
- // 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
- }
- type withExternalData struct {
- m map[interface{}]interface{}
- }
- func (c *withExternalData) getExternalValue(k interface{}) interface{} {
- return c.m[k]
- }
- func (c *withExternalData) setExternalValue(k, v interface{}) {
- if c.m == nil {
- c.m = map[interface{}]interface{}{}
- }
- c.m[k] = v
- }
|