File: commands.go

package info (click to toggle)
docker.io 18.09.1%2Bdfsg1-7.1%2Bdeb10u3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 66,144 kB
  • sloc: sh: 9,753; makefile: 827; ansic: 239; python: 162; asm: 10
file content (446 lines) | stat: -rw-r--r-- 10,422 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
package instructions

import (
	"errors"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
)

// 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
}

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
}

// 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
}

// 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
}

// 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
	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
	KeyValuePairOptional
}

// 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   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
}