Browse code

builder: comments / documentation

Docker-DCO-1.1-Signed-off-by: Erik Hollensbe <github@hollensbe.org> (github: erikh)

Erik Hollensbe authored on 2014/08/07 14:56:44
Showing 6 changed files
... ...
@@ -1,5 +1,12 @@
1 1
 package evaluator
2 2
 
3
+// This file contains the dispatchers for each command. Note that
4
+// `nullDispatch` is not actually a command, but support for commands we parse
5
+// but do nothing with.
6
+//
7
+// See evaluator.go for a higher level discussion of the whole evaluator
8
+// package.
9
+
3 10
 import (
4 11
 	"fmt"
5 12
 	"path/filepath"
... ...
@@ -10,11 +17,16 @@ import (
10 10
 	"github.com/docker/docker/utils"
11 11
 )
12 12
 
13
-// dispatch with no layer / parsing.
13
+// dispatch with no layer / parsing. This is effectively not a command.
14 14
 func nullDispatch(b *buildFile, args []string) error {
15 15
 	return nil
16 16
 }
17 17
 
18
+// ENV foo bar
19
+//
20
+// Sets the environment variable foo to bar, also makes interpolation
21
+// in the dockerfile available from the next statement on via ${foo}.
22
+//
18 23
 func env(b *buildFile, args []string) error {
19 24
 	if len(args) != 2 {
20 25
 		return fmt.Errorf("ENV accepts two arguments")
... ...
@@ -29,6 +41,9 @@ func env(b *buildFile, args []string) error {
29 29
 	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, b.env[key]))
30 30
 }
31 31
 
32
+// MAINTAINER some text <maybe@an.email.address>
33
+//
34
+// Sets the maintainer metadata.
32 35
 func maintainer(b *buildFile, args []string) error {
33 36
 	if len(args) != 1 {
34 37
 		return fmt.Errorf("MAINTAINER requires only one argument")
... ...
@@ -38,6 +53,11 @@ func maintainer(b *buildFile, args []string) error {
38 38
 	return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
39 39
 }
40 40
 
41
+// ADD foo /path
42
+//
43
+// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
44
+// exist here. If you do not wish to have this automatic handling, use COPY.
45
+//
41 46
 func add(b *buildFile, args []string) error {
42 47
 	if len(args) != 2 {
43 48
 		return fmt.Errorf("ADD requires two arguments")
... ...
@@ -46,6 +66,10 @@ func add(b *buildFile, args []string) error {
46 46
 	return b.runContextCommand(args, true, true, "ADD")
47 47
 }
48 48
 
49
+// COPY foo /path
50
+//
51
+// Same as 'ADD' but without the tar and remote url handling.
52
+//
49 53
 func dispatchCopy(b *buildFile, args []string) error {
50 54
 	if len(args) != 2 {
51 55
 		return fmt.Errorf("COPY requires two arguments")
... ...
@@ -54,6 +78,10 @@ func dispatchCopy(b *buildFile, args []string) error {
54 54
 	return b.runContextCommand(args, false, false, "COPY")
55 55
 }
56 56
 
57
+// FROM imagename
58
+//
59
+// This sets the image the dockerfile will build on top of.
60
+//
57 61
 func from(b *buildFile, args []string) error {
58 62
 	if len(args) != 1 {
59 63
 		return fmt.Errorf("FROM requires one argument")
... ...
@@ -77,6 +105,15 @@ func from(b *buildFile, args []string) error {
77 77
 	return b.processImageFrom(image)
78 78
 }
79 79
 
80
+// ONBUILD RUN echo yo
81
+//
82
+// ONBUILD triggers run when the image is used in a FROM statement.
83
+//
84
+// ONBUILD handling has a lot of special-case functionality, the heading in
85
+// evaluator.go and comments around dispatch() in the same file explain the
86
+// special cases. search for 'OnBuild' in internals.go for additional special
87
+// cases.
88
+//
80 89
 func onbuild(b *buildFile, args []string) error {
81 90
 	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
82 91
 	switch triggerInstruction {
... ...
@@ -92,6 +129,10 @@ func onbuild(b *buildFile, args []string) error {
92 92
 	return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
93 93
 }
94 94
 
95
+// WORKDIR /tmp
96
+//
97
+// Set the working directory for future RUN/CMD/etc statements.
98
+//
95 99
 func workdir(b *buildFile, args []string) error {
96 100
 	if len(args) != 1 {
97 101
 		return fmt.Errorf("WORKDIR requires exactly one argument")
... ...
@@ -111,6 +152,15 @@ func workdir(b *buildFile, args []string) error {
111 111
 	return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
112 112
 }
113 113
 
114
+// RUN some command yo
115
+//
116
+// run a command and commit the image. Args are automatically prepended with
117
+// 'sh -c' in the event there is only one argument. The difference in
118
+// processing:
119
+//
120
+// RUN echo hi          # sh -c echo hi
121
+// RUN [ "echo", "hi" ] # echo hi
122
+//
114 123
 func run(b *buildFile, args []string) error {
115 124
 	if len(args) == 1 { // literal string command, not an exec array
116 125
 		args = append([]string{"/bin/sh", "-c"}, args[0])
... ...
@@ -162,6 +212,11 @@ func run(b *buildFile, args []string) error {
162 162
 	return nil
163 163
 }
164 164
 
165
+// CMD foo
166
+//
167
+// Set the default command to run in the container (which may be empty).
168
+// Argument handling is the same as RUN.
169
+//
165 170
 func cmd(b *buildFile, args []string) error {
166 171
 	if len(args) < 2 {
167 172
 		args = append([]string{"/bin/sh", "-c"}, args...)
... ...
@@ -176,6 +231,14 @@ func cmd(b *buildFile, args []string) error {
176 176
 	return nil
177 177
 }
178 178
 
179
+// ENTRYPOINT /usr/sbin/nginx
180
+//
181
+// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
182
+// accept the CMD as the arguments to /usr/sbin/nginx.
183
+//
184
+// Handles command processing similar to CMD and RUN, only b.config.Entrypoint
185
+// is initialized at NewBuilder time instead of through argument parsing.
186
+//
179 187
 func entrypoint(b *buildFile, args []string) error {
180 188
 	b.config.Entrypoint = args
181 189
 
... ...
@@ -189,6 +252,11 @@ func entrypoint(b *buildFile, args []string) error {
189 189
 	return nil
190 190
 }
191 191
 
192
+// EXPOSE 6667/tcp 7000/tcp
193
+//
194
+// Expose ports for links and port mappings. This all ends up in
195
+// b.config.ExposedPorts for runconfig.
196
+//
192 197
 func expose(b *buildFile, args []string) error {
193 198
 	portsTab := args
194 199
 
... ...
@@ -211,6 +279,11 @@ func expose(b *buildFile, args []string) error {
211 211
 	return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
212 212
 }
213 213
 
214
+// USER foo
215
+//
216
+// Set the user to 'foo' for future commands and when running the
217
+// ENTRYPOINT/CMD at container run time.
218
+//
214 219
 func user(b *buildFile, args []string) error {
215 220
 	if len(args) != 1 {
216 221
 		return fmt.Errorf("USER requires exactly one argument")
... ...
@@ -220,6 +293,11 @@ func user(b *buildFile, args []string) error {
220 220
 	return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
221 221
 }
222 222
 
223
+// VOLUME /foo
224
+//
225
+// Expose the volume /foo for use. Will also accept the JSON form, but either
226
+// way requires exactly one argument.
227
+//
223 228
 func volume(b *buildFile, args []string) error {
224 229
 	if len(args) != 1 {
225 230
 		return fmt.Errorf("Volume cannot be empty")
... ...
@@ -239,6 +317,7 @@ func volume(b *buildFile, args []string) error {
239 239
 	return nil
240 240
 }
241 241
 
242
+// INSERT is no longer accepted, but we still parse it.
242 243
 func insert(b *buildFile, args []string) error {
243 244
 	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
244 245
 }
... ...
@@ -1,3 +1,22 @@
1
+// evaluator is the evaluation step in the Dockerfile parse/evaluate pipeline.
2
+//
3
+// It incorporates a dispatch table based on the parser.Node values (see the
4
+// parser package for more information) that are yielded from the parser itself.
5
+// Calling NewBuilder with the BuildOpts struct can be used to customize the
6
+// experience for execution purposes only. Parsing is controlled in the parser
7
+// package, and this division of resposibility should be respected.
8
+//
9
+// Please see the jump table targets for the actual invocations, most of which
10
+// will call out to the functions in internals.go to deal with their tasks.
11
+//
12
+// ONBUILD is a special case, which is covered in the onbuild() func in
13
+// dispatchers.go.
14
+//
15
+// The evaluator uses the concept of "steps", which are usually each processable
16
+// line in the Dockerfile. Each step is numbered and certain actions are taken
17
+// before and after each step, such as creating an image ID and removing temporary
18
+// containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which
19
+// includes its own set of steps (usually only one of them).
1 20
 package evaluator
2 21
 
3 22
 import (
... ...
@@ -49,32 +68,40 @@ func init() {
49 49
 type envMap map[string]string
50 50
 type uniqueMap map[string]struct{}
51 51
 
52
+// internal struct, used to maintain configuration of the Dockerfile's
53
+// processing as it evaluates the parsing result.
52 54
 type buildFile struct {
53
-	dockerfile *parser.Node
54
-	env        envMap
55
-	image      string
56
-	config     *runconfig.Config
57
-	options    *BuildOpts
58
-	maintainer string
59
-
60
-	// cmdSet indicates is CMD was set in current Dockerfile
61
-	cmdSet bool
62
-
63
-	context       *tarsum.TarSum
64
-	contextPath   string
65
-	tmpContainers uniqueMap
66
-	tmpImages     uniqueMap
55
+	dockerfile  *parser.Node      // the syntax tree of the dockerfile
56
+	env         envMap            // map of environment variables
57
+	image       string            // image name for commit processing
58
+	config      *runconfig.Config // runconfig for cmd, run, entrypoint etc.
59
+	options     *BuildOpts        // see below
60
+	maintainer  string            // maintainer name. could probably be removed.
61
+	cmdSet      bool              // indicates is CMD was set in current Dockerfile
62
+	context     *tarsum.TarSum    // the context is a tarball that is uploaded by the client
63
+	contextPath string            // the path of the temporary directory the local context is unpacked to (server side)
64
+
65
+	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
66
+	tmpContainers uniqueMap // a map of containers used for removes
67
+	tmpImages     uniqueMap // a map of images used for removes
67 68
 }
68 69
 
69 70
 type BuildOpts struct {
70
-	Daemon         *daemon.Daemon
71
-	Engine         *engine.Engine
72
-	OutStream      io.Writer
73
-	ErrStream      io.Writer
74
-	Verbose        bool
75
-	UtilizeCache   bool
76
-	Remove         bool
77
-	ForceRemove    bool
71
+	Daemon *daemon.Daemon
72
+	Engine *engine.Engine
73
+
74
+	// effectively stdio for the run. Because it is not stdio, I said
75
+	// "Effectively". Do not use stdio anywhere in this package for any reason.
76
+	OutStream io.Writer
77
+	ErrStream io.Writer
78
+
79
+	Verbose      bool
80
+	UtilizeCache bool
81
+
82
+	// controls how images and containers are handled between steps.
83
+	Remove      bool
84
+	ForceRemove bool
85
+
78 86
 	AuthConfig     *registry.AuthConfig
79 87
 	AuthConfigFile *registry.ConfigFile
80 88
 
... ...
@@ -83,6 +110,7 @@ type BuildOpts struct {
83 83
 	StreamFormatter *utils.StreamFormatter
84 84
 }
85 85
 
86
+// Create a new builder.
86 87
 func NewBuilder(opts *BuildOpts) (*buildFile, error) {
87 88
 	return &buildFile{
88 89
 		dockerfile:    nil,
... ...
@@ -94,10 +122,20 @@ func NewBuilder(opts *BuildOpts) (*buildFile, error) {
94 94
 	}, nil
95 95
 }
96 96
 
97
+// Run the builder with the context. This is the lynchpin of this package. This
98
+// will (barring errors):
99
+//
100
+// * call readContext() which will set up the temporary directory and unpack
101
+//   the context into it.
102
+// * read the dockerfile
103
+// * parse the dockerfile
104
+// * walk the parse tree and execute it by dispatching to handlers. If Remove
105
+//   or ForceRemove is set, additional cleanup around containers happens after
106
+//   processing.
107
+// * Print a happy message and return the image ID.
108
+//
97 109
 func (b *buildFile) Run(context io.Reader) (string, error) {
98
-	err := b.readContext(context)
99
-
100
-	if err != nil {
110
+	if err := b.readContext(context); err != nil {
101 111
 		return "", err
102 112
 	}
103 113
 
... ...
@@ -131,7 +169,7 @@ func (b *buildFile) Run(context io.Reader) (string, error) {
131 131
 	}
132 132
 
133 133
 	if b.image == "" {
134
-		return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
134
+		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
135 135
 	}
136 136
 
137 137
 	fmt.Fprintf(b.options.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
... ...
@@ -153,6 +191,20 @@ func initRunConfig() *runconfig.Config {
153 153
 	}
154 154
 }
155 155
 
156
+// This method is the entrypoint to all statement handling routines.
157
+//
158
+// Almost all nodes will have this structure:
159
+// Child[Node, Node, Node] where Child is from parser.Node.Children and each
160
+// node comes from parser.Node.Next. This forms a "line" with a statement and
161
+// arguments and we process them in this normalized form by hitting
162
+// evaluateTable with the leaf nodes of the command and the buildFile object.
163
+//
164
+// ONBUILD is a special case; in this case the parser will emit:
165
+// Child[Node, Child[Node, Node...]] where the first node is the literal
166
+// "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
167
+// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
168
+// deal with that, at least until it becomes more of a general concern with new
169
+// features.
156 170
 func (b *buildFile) dispatch(stepN int, ast *parser.Node) error {
157 171
 	cmd := ast.Value
158 172
 	strs := []string{}
... ...
@@ -1,5 +1,8 @@
1 1
 package evaluator
2 2
 
3
+// internals for handling commands. Covers many areas and a lot of
4
+// non-contiguous functionality. Please read the comments.
5
+
3 6
 import (
4 7
 	"crypto/sha256"
5 8
 	"encoding/hex"
... ...
@@ -9,6 +9,7 @@ var (
9 9
 	TOKEN_ENV_INTERPOLATION = regexp.MustCompile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
10 10
 )
11 11
 
12
+// handle environment replacement. Used in dispatcher.
12 13
 func replaceEnv(b *buildFile, str string) string {
13 14
 	for _, match := range TOKEN_ENV_INTERPOLATION.FindAllString(str, -1) {
14 15
 		match = match[strings.Index(match, "$"):]
... ...
@@ -1,5 +1,11 @@
1 1
 package parser
2 2
 
3
+// line parsers are dispatch calls that parse a single unit of text into a
4
+// Node object which contains the whole statement. Dockerfiles have varied
5
+// (but not usually unique, see ONBUILD for a unique example) parsing rules
6
+// per-command, and these unify the processing in a way that makes it
7
+// manageable.
8
+
3 9
 import (
4 10
 	"encoding/json"
5 11
 	"strconv"
... ...
@@ -12,6 +18,11 @@ func parseIgnore(rest string) (*Node, error) {
12 12
 	return blankNode(), nil
13 13
 }
14 14
 
15
+// used for onbuild. Could potentially be used for anything that represents a
16
+// statement with sub-statements.
17
+//
18
+// ONBUILD RUN foo bar -> (onbuild (run foo bar))
19
+//
15 20
 func parseSubCommand(rest string) (*Node, error) {
16 21
 	_, child, err := parseLine(rest)
17 22
 	if err != nil {
... ...
@@ -8,32 +8,17 @@ import (
8 8
 	"strings"
9 9
 )
10 10
 
11
-// Node is the building block of the AST this package will create.
11
+// Node is a structure used to represent a parse tree.
12 12
 //
13
-// Nodes are structured to have a value, next, and child, the latter two of
14
-// which are Nodes themselves.
13
+// In the node there are three fields, Value, Next, and Children. Value is the
14
+// current token's string value. Next is always the next non-child token, and
15
+// children contains all the children. Here's an example:
15 16
 //
16
-// This terminology is unfortunately rather confusing, so here's a diagram.
17
-// Anything after the ; is a comment.
17
+// (value next (child child-next child-next-next) next-next)
18 18
 //
19
-//     (
20
-//       (run "foo") ; value run, and next is a value foo.
21
-//       (run "1" "2" "3") ;
22
-//       (something (really cool))
23
-//     )
24
-//
25
-// Will give you something like this:
26
-//
27
-//     &Node{
28
-//       Value:"",
29
-//       Child: &Node{Value: "run", Next: &Node{Value: "foo"}, Child: nil},
30
-//       Next: &Node{Value:"", Child: &Node{Value:"run", Next: &Node{Value:`"1"`....
31
-//
32
-// ... and so on.
33
-//
34
-// The short and fast rule is that anything that starts with ( is a child of
35
-// something. Anything which follows a previous statement is a next of
36
-// something.
19
+// This data structure is frankly pretty lousy for handling complex languages,
20
+// but lucky for us the Dockerfile isn't very complicated. This structure
21
+// works a little more effectively than a "proper" parse tree for our needs.
37 22
 //
38 23
 type Node struct {
39 24
 	Value    string  // actual content
... ...
@@ -79,6 +64,7 @@ func blankNode() *Node {
79 79
 	return &Node{"", nil, []*Node{}}
80 80
 }
81 81
 
82
+// parse a line and return the remainder.
82 83
 func parseLine(line string) (string, *Node, error) {
83 84
 	if line = stripComments(line); line == "" {
84 85
 		return "", nil, nil