Browse code

builder: move the evaluator package code directly into builder

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

Erik Hollensbe authored on 2014/08/16 01:29:35
Showing 10 changed files
... ...
@@ -1,17 +1,16 @@
1 1
 package builder
2 2
 
3 3
 import (
4
-	"github.com/docker/docker/builder/evaluator"
5 4
 	"github.com/docker/docker/runconfig"
6 5
 )
7 6
 
8
-// Create a new builder.
9
-func NewBuilder(opts *evaluator.BuildOpts) *evaluator.BuildFile {
10
-	return &evaluator.BuildFile{
7
+// Create a new builder. See
8
+func NewBuilder(opts *BuildOpts) *BuildFile {
9
+	return &BuildFile{
11 10
 		Dockerfile:    nil,
12 11
 		Config:        &runconfig.Config{},
13 12
 		Options:       opts,
14
-		TmpContainers: evaluator.UniqueMap{},
15
-		TmpImages:     evaluator.UniqueMap{},
13
+		TmpContainers: UniqueMap{},
14
+		TmpImages:     UniqueMap{},
16 15
 	}
17 16
 }
18 17
new file mode 100644
... ...
@@ -0,0 +1,324 @@
0
+package builder
1
+
2
+// This file contains the dispatchers for each command. Note that
3
+// `nullDispatch` is not actually a command, but support for commands we parse
4
+// but do nothing with.
5
+//
6
+// See evaluator.go for a higher level discussion of the whole evaluator
7
+// package.
8
+
9
+import (
10
+	"fmt"
11
+	"path/filepath"
12
+	"strings"
13
+
14
+	"github.com/docker/docker/nat"
15
+	"github.com/docker/docker/pkg/log"
16
+	"github.com/docker/docker/runconfig"
17
+)
18
+
19
+// dispatch with no layer / parsing. This is effectively not a command.
20
+func nullDispatch(b *BuildFile, args []string, attributes map[string]bool) error {
21
+	return nil
22
+}
23
+
24
+// ENV foo bar
25
+//
26
+// Sets the environment variable foo to bar, also makes interpolation
27
+// in the dockerfile available from the next statement on via ${foo}.
28
+//
29
+func env(b *BuildFile, args []string, attributes map[string]bool) error {
30
+	if len(args) != 2 {
31
+		return fmt.Errorf("ENV accepts two arguments")
32
+	}
33
+
34
+	fullEnv := fmt.Sprintf("%s=%s", args[0], args[1])
35
+
36
+	for i, envVar := range b.Config.Env {
37
+		envParts := strings.SplitN(envVar, "=", 2)
38
+		if args[0] == envParts[0] {
39
+			b.Config.Env[i] = fullEnv
40
+			return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
41
+		}
42
+	}
43
+	b.Config.Env = append(b.Config.Env, fullEnv)
44
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
45
+}
46
+
47
+// MAINTAINER some text <maybe@an.email.address>
48
+//
49
+// Sets the maintainer metadata.
50
+func maintainer(b *BuildFile, args []string, attributes map[string]bool) error {
51
+	if len(args) != 1 {
52
+		return fmt.Errorf("MAINTAINER requires only one argument")
53
+	}
54
+
55
+	b.maintainer = args[0]
56
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
57
+}
58
+
59
+// ADD foo /path
60
+//
61
+// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
62
+// exist here. If you do not wish to have this automatic handling, use COPY.
63
+//
64
+func add(b *BuildFile, args []string, attributes map[string]bool) error {
65
+	if len(args) != 2 {
66
+		return fmt.Errorf("ADD requires two arguments")
67
+	}
68
+
69
+	return b.runContextCommand(args, true, true, "ADD")
70
+}
71
+
72
+// COPY foo /path
73
+//
74
+// Same as 'ADD' but without the tar and remote url handling.
75
+//
76
+func dispatchCopy(b *BuildFile, args []string, attributes map[string]bool) error {
77
+	if len(args) != 2 {
78
+		return fmt.Errorf("COPY requires two arguments")
79
+	}
80
+
81
+	return b.runContextCommand(args, false, false, "COPY")
82
+}
83
+
84
+// FROM imagename
85
+//
86
+// This sets the image the dockerfile will build on top of.
87
+//
88
+func from(b *BuildFile, args []string, attributes map[string]bool) error {
89
+	if len(args) != 1 {
90
+		return fmt.Errorf("FROM requires one argument")
91
+	}
92
+
93
+	name := args[0]
94
+
95
+	image, err := b.Options.Daemon.Repositories().LookupImage(name)
96
+	if err != nil {
97
+		if b.Options.Daemon.Graph().IsNotExist(err) {
98
+			image, err = b.pullImage(name)
99
+		}
100
+
101
+		// note that the top level err will still be !nil here if IsNotExist is
102
+		// not the error. This approach just simplifies hte logic a bit.
103
+		if err != nil {
104
+			return err
105
+		}
106
+	}
107
+
108
+	return b.processImageFrom(image)
109
+}
110
+
111
+// ONBUILD RUN echo yo
112
+//
113
+// ONBUILD triggers run when the image is used in a FROM statement.
114
+//
115
+// ONBUILD handling has a lot of special-case functionality, the heading in
116
+// evaluator.go and comments around dispatch() in the same file explain the
117
+// special cases. search for 'OnBuild' in internals.go for additional special
118
+// cases.
119
+//
120
+func onbuild(b *BuildFile, args []string, attributes map[string]bool) error {
121
+	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
122
+	switch triggerInstruction {
123
+	case "ONBUILD":
124
+		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
125
+	case "MAINTAINER", "FROM":
126
+		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
127
+	}
128
+
129
+	trigger := strings.Join(args, " ")
130
+
131
+	b.Config.OnBuild = append(b.Config.OnBuild, trigger)
132
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
133
+}
134
+
135
+// WORKDIR /tmp
136
+//
137
+// Set the working directory for future RUN/CMD/etc statements.
138
+//
139
+func workdir(b *BuildFile, args []string, attributes map[string]bool) error {
140
+	if len(args) != 1 {
141
+		return fmt.Errorf("WORKDIR requires exactly one argument")
142
+	}
143
+
144
+	workdir := args[0]
145
+
146
+	if workdir[0] == '/' {
147
+		b.Config.WorkingDir = workdir
148
+	} else {
149
+		if b.Config.WorkingDir == "" {
150
+			b.Config.WorkingDir = "/"
151
+		}
152
+		b.Config.WorkingDir = filepath.Join(b.Config.WorkingDir, workdir)
153
+	}
154
+
155
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
156
+}
157
+
158
+// RUN some command yo
159
+//
160
+// run a command and commit the image. Args are automatically prepended with
161
+// 'sh -c' in the event there is only one argument. The difference in
162
+// processing:
163
+//
164
+// RUN echo hi          # sh -c echo hi
165
+// RUN [ "echo", "hi" ] # echo hi
166
+//
167
+func run(b *BuildFile, args []string, attributes map[string]bool) error {
168
+	args = handleJsonArgs(args, attributes)
169
+
170
+	if b.image == "" {
171
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
172
+	}
173
+
174
+	config, _, _, err := runconfig.Parse(append([]string{b.image}, args...), nil)
175
+	if err != nil {
176
+		return err
177
+	}
178
+
179
+	cmd := b.Config.Cmd
180
+	// set Cmd manually, this is special case only for Dockerfiles
181
+	b.Config.Cmd = config.Cmd
182
+	runconfig.Merge(b.Config, config)
183
+
184
+	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
185
+
186
+	log.Debugf("Command to be executed: %v", b.Config.Cmd)
187
+
188
+	hit, err := b.probeCache()
189
+	if err != nil {
190
+		return err
191
+	}
192
+	if hit {
193
+		return nil
194
+	}
195
+
196
+	c, err := b.create()
197
+	if err != nil {
198
+		return err
199
+	}
200
+
201
+	// Ensure that we keep the container mounted until the commit
202
+	// to avoid unmounting and then mounting directly again
203
+	c.Mount()
204
+	defer c.Unmount()
205
+
206
+	err = b.run(c)
207
+	if err != nil {
208
+		return err
209
+	}
210
+	if err := b.commit(c.ID, cmd, "run"); err != nil {
211
+		return err
212
+	}
213
+
214
+	return nil
215
+}
216
+
217
+// CMD foo
218
+//
219
+// Set the default command to run in the container (which may be empty).
220
+// Argument handling is the same as RUN.
221
+//
222
+func cmd(b *BuildFile, args []string, attributes map[string]bool) error {
223
+	b.Config.Cmd = handleJsonArgs(args, attributes)
224
+
225
+	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
226
+		return err
227
+	}
228
+
229
+	b.cmdSet = true
230
+	return nil
231
+}
232
+
233
+// ENTRYPOINT /usr/sbin/nginx
234
+//
235
+// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
236
+// accept the CMD as the arguments to /usr/sbin/nginx.
237
+//
238
+// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
239
+// is initialized at NewBuilder time instead of through argument parsing.
240
+//
241
+func entrypoint(b *BuildFile, args []string, attributes map[string]bool) error {
242
+	b.Config.Entrypoint = handleJsonArgs(args, attributes)
243
+
244
+	// if there is no cmd in current Dockerfile - cleanup cmd
245
+	if !b.cmdSet {
246
+		b.Config.Cmd = nil
247
+	}
248
+
249
+	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
250
+		return err
251
+	}
252
+	return nil
253
+}
254
+
255
+// EXPOSE 6667/tcp 7000/tcp
256
+//
257
+// Expose ports for links and port mappings. This all ends up in
258
+// b.Config.ExposedPorts for runconfig.
259
+//
260
+func expose(b *BuildFile, args []string, attributes map[string]bool) error {
261
+	portsTab := args
262
+
263
+	if b.Config.ExposedPorts == nil {
264
+		b.Config.ExposedPorts = make(nat.PortSet)
265
+	}
266
+
267
+	ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
268
+	if err != nil {
269
+		return err
270
+	}
271
+
272
+	for port := range ports {
273
+		if _, exists := b.Config.ExposedPorts[port]; !exists {
274
+			b.Config.ExposedPorts[port] = struct{}{}
275
+		}
276
+	}
277
+	b.Config.PortSpecs = nil
278
+
279
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
280
+}
281
+
282
+// USER foo
283
+//
284
+// Set the user to 'foo' for future commands and when running the
285
+// ENTRYPOINT/CMD at container run time.
286
+//
287
+func user(b *BuildFile, args []string, attributes map[string]bool) error {
288
+	if len(args) != 1 {
289
+		return fmt.Errorf("USER requires exactly one argument")
290
+	}
291
+
292
+	b.Config.User = args[0]
293
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
294
+}
295
+
296
+// VOLUME /foo
297
+//
298
+// Expose the volume /foo for use. Will also accept the JSON form, but either
299
+// way requires exactly one argument.
300
+//
301
+func volume(b *BuildFile, args []string, attributes map[string]bool) error {
302
+	if len(args) != 1 {
303
+		return fmt.Errorf("Volume cannot be empty")
304
+	}
305
+
306
+	volume := args
307
+
308
+	if b.Config.Volumes == nil {
309
+		b.Config.Volumes = map[string]struct{}{}
310
+	}
311
+	for _, v := range volume {
312
+		b.Config.Volumes[v] = struct{}{}
313
+	}
314
+	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
315
+		return err
316
+	}
317
+	return nil
318
+}
319
+
320
+// INSERT is no longer accepted, but we still parse it.
321
+func insert(b *BuildFile, args []string, attributes map[string]bool) error {
322
+	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
323
+}
0 324
new file mode 100644
... ...
@@ -0,0 +1,210 @@
0
+// builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
1
+//
2
+// It incorporates a dispatch table based on the parser.Node values (see the
3
+// parser package for more information) that are yielded from the parser itself.
4
+// Calling NewBuilder with the BuildOpts struct can be used to customize the
5
+// experience for execution purposes only. Parsing is controlled in the parser
6
+// package, and this division of resposibility should be respected.
7
+//
8
+// Please see the jump table targets for the actual invocations, most of which
9
+// will call out to the functions in internals.go to deal with their tasks.
10
+//
11
+// ONBUILD is a special case, which is covered in the onbuild() func in
12
+// dispatchers.go.
13
+//
14
+// The evaluator uses the concept of "steps", which are usually each processable
15
+// line in the Dockerfile. Each step is numbered and certain actions are taken
16
+// before and after each step, such as creating an image ID and removing temporary
17
+// containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which
18
+// includes its own set of steps (usually only one of them).
19
+package builder
20
+
21
+import (
22
+	"bytes"
23
+	"errors"
24
+	"fmt"
25
+	"io"
26
+	"io/ioutil"
27
+	"os"
28
+	"path"
29
+	"strings"
30
+
31
+	"github.com/docker/docker/builder/parser"
32
+	"github.com/docker/docker/daemon"
33
+	"github.com/docker/docker/engine"
34
+	"github.com/docker/docker/pkg/tarsum"
35
+	"github.com/docker/docker/registry"
36
+	"github.com/docker/docker/runconfig"
37
+	"github.com/docker/docker/utils"
38
+)
39
+
40
+type UniqueMap map[string]struct{}
41
+
42
+var (
43
+	ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
44
+)
45
+
46
+var evaluateTable map[string]func(*BuildFile, []string, map[string]bool) error
47
+
48
+func init() {
49
+	evaluateTable = map[string]func(*BuildFile, []string, map[string]bool) error{
50
+		"env":            env,
51
+		"maintainer":     maintainer,
52
+		"add":            add,
53
+		"copy":           dispatchCopy, // copy() is a go builtin
54
+		"from":           from,
55
+		"onbuild":        onbuild,
56
+		"workdir":        workdir,
57
+		"docker-version": nullDispatch, // we don't care about docker-version
58
+		"run":            run,
59
+		"cmd":            cmd,
60
+		"entrypoint":     entrypoint,
61
+		"expose":         expose,
62
+		"volume":         volume,
63
+		"user":           user,
64
+		"insert":         insert,
65
+	}
66
+}
67
+
68
+// internal struct, used to maintain configuration of the Dockerfile's
69
+// processing as it evaluates the parsing result.
70
+type BuildFile struct {
71
+	Dockerfile *parser.Node      // the syntax tree of the dockerfile
72
+	Config     *runconfig.Config // runconfig for cmd, run, entrypoint etc.
73
+	Options    *BuildOpts        // see below
74
+
75
+	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
76
+	TmpContainers UniqueMap // a map of containers used for removes
77
+	TmpImages     UniqueMap // a map of images used for removes
78
+
79
+	image       string         // image name for commit processing
80
+	maintainer  string         // maintainer name. could probably be removed.
81
+	cmdSet      bool           // indicates is CMD was set in current Dockerfile
82
+	context     *tarsum.TarSum // the context is a tarball that is uploaded by the client
83
+	contextPath string         // the path of the temporary directory the local context is unpacked to (server side)
84
+
85
+}
86
+
87
+type BuildOpts struct {
88
+	Daemon *daemon.Daemon
89
+	Engine *engine.Engine
90
+
91
+	// effectively stdio for the run. Because it is not stdio, I said
92
+	// "Effectively". Do not use stdio anywhere in this package for any reason.
93
+	OutStream io.Writer
94
+	ErrStream io.Writer
95
+
96
+	Verbose      bool
97
+	UtilizeCache bool
98
+
99
+	// controls how images and containers are handled between steps.
100
+	Remove      bool
101
+	ForceRemove bool
102
+
103
+	AuthConfig     *registry.AuthConfig
104
+	AuthConfigFile *registry.ConfigFile
105
+
106
+	// Deprecated, original writer used for ImagePull. To be removed.
107
+	OutOld          io.Writer
108
+	StreamFormatter *utils.StreamFormatter
109
+}
110
+
111
+// Run the builder with the context. This is the lynchpin of this package. This
112
+// will (barring errors):
113
+//
114
+// * call readContext() which will set up the temporary directory and unpack
115
+//   the context into it.
116
+// * read the dockerfile
117
+// * parse the dockerfile
118
+// * walk the parse tree and execute it by dispatching to handlers. If Remove
119
+//   or ForceRemove is set, additional cleanup around containers happens after
120
+//   processing.
121
+// * Print a happy message and return the image ID.
122
+//
123
+func (b *BuildFile) Run(context io.Reader) (string, error) {
124
+	if err := b.readContext(context); err != nil {
125
+		return "", err
126
+	}
127
+
128
+	filename := path.Join(b.contextPath, "Dockerfile")
129
+	if _, err := os.Stat(filename); os.IsNotExist(err) {
130
+		return "", fmt.Errorf("Cannot build a directory without a Dockerfile")
131
+	}
132
+	fileBytes, err := ioutil.ReadFile(filename)
133
+	if err != nil {
134
+		return "", err
135
+	}
136
+	if len(fileBytes) == 0 {
137
+		return "", ErrDockerfileEmpty
138
+	}
139
+	ast, err := parser.Parse(bytes.NewReader(fileBytes))
140
+	if err != nil {
141
+		return "", err
142
+	}
143
+
144
+	b.Dockerfile = ast
145
+
146
+	for i, n := range b.Dockerfile.Children {
147
+		if err := b.dispatch(i, n); err != nil {
148
+			if b.Options.ForceRemove {
149
+				b.clearTmp(b.TmpContainers)
150
+			}
151
+			return "", err
152
+		}
153
+		fmt.Fprintf(b.Options.OutStream, " ---> %s\n", utils.TruncateID(b.image))
154
+		if b.Options.Remove {
155
+			b.clearTmp(b.TmpContainers)
156
+		}
157
+	}
158
+
159
+	if b.image == "" {
160
+		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
161
+	}
162
+
163
+	fmt.Fprintf(b.Options.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
164
+	return b.image, nil
165
+}
166
+
167
+// This method is the entrypoint to all statement handling routines.
168
+//
169
+// Almost all nodes will have this structure:
170
+// Child[Node, Node, Node] where Child is from parser.Node.Children and each
171
+// node comes from parser.Node.Next. This forms a "line" with a statement and
172
+// arguments and we process them in this normalized form by hitting
173
+// evaluateTable with the leaf nodes of the command and the BuildFile object.
174
+//
175
+// ONBUILD is a special case; in this case the parser will emit:
176
+// Child[Node, Child[Node, Node...]] where the first node is the literal
177
+// "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
178
+// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
179
+// deal with that, at least until it becomes more of a general concern with new
180
+// features.
181
+func (b *BuildFile) dispatch(stepN int, ast *parser.Node) error {
182
+	cmd := ast.Value
183
+	attrs := ast.Attributes
184
+	strs := []string{}
185
+	msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
186
+
187
+	if cmd == "onbuild" {
188
+		fmt.Fprintf(b.Options.OutStream, "%#v\n", ast.Next.Children[0].Value)
189
+		ast = ast.Next.Children[0]
190
+		strs = append(strs, b.replaceEnv(ast.Value))
191
+		msg += " " + ast.Value
192
+	}
193
+
194
+	for ast.Next != nil {
195
+		ast = ast.Next
196
+		strs = append(strs, b.replaceEnv(ast.Value))
197
+		msg += " " + ast.Value
198
+	}
199
+
200
+	fmt.Fprintf(b.Options.OutStream, "%s\n", msg)
201
+
202
+	// XXX yes, we skip any cmds that are not valid; the parser should have
203
+	// picked these out already.
204
+	if f, ok := evaluateTable[cmd]; ok {
205
+		return f(b, strs, attrs)
206
+	}
207
+
208
+	return nil
209
+}
0 210
deleted file mode 100644
... ...
@@ -1,324 +0,0 @@
1
-package evaluator
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
-
10
-import (
11
-	"fmt"
12
-	"path/filepath"
13
-	"strings"
14
-
15
-	"github.com/docker/docker/nat"
16
-	"github.com/docker/docker/pkg/log"
17
-	"github.com/docker/docker/runconfig"
18
-)
19
-
20
-// dispatch with no layer / parsing. This is effectively not a command.
21
-func nullDispatch(b *BuildFile, args []string, attributes map[string]bool) error {
22
-	return nil
23
-}
24
-
25
-// ENV foo bar
26
-//
27
-// Sets the environment variable foo to bar, also makes interpolation
28
-// in the dockerfile available from the next statement on via ${foo}.
29
-//
30
-func env(b *BuildFile, args []string, attributes map[string]bool) error {
31
-	if len(args) != 2 {
32
-		return fmt.Errorf("ENV accepts two arguments")
33
-	}
34
-
35
-	fullEnv := fmt.Sprintf("%s=%s", args[0], args[1])
36
-
37
-	for i, envVar := range b.Config.Env {
38
-		envParts := strings.SplitN(envVar, "=", 2)
39
-		if args[0] == envParts[0] {
40
-			b.Config.Env[i] = fullEnv
41
-			return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
42
-		}
43
-	}
44
-	b.Config.Env = append(b.Config.Env, fullEnv)
45
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
46
-}
47
-
48
-// MAINTAINER some text <maybe@an.email.address>
49
-//
50
-// Sets the maintainer metadata.
51
-func maintainer(b *BuildFile, args []string, attributes map[string]bool) error {
52
-	if len(args) != 1 {
53
-		return fmt.Errorf("MAINTAINER requires only one argument")
54
-	}
55
-
56
-	b.maintainer = args[0]
57
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
58
-}
59
-
60
-// ADD foo /path
61
-//
62
-// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
63
-// exist here. If you do not wish to have this automatic handling, use COPY.
64
-//
65
-func add(b *BuildFile, args []string, attributes map[string]bool) error {
66
-	if len(args) != 2 {
67
-		return fmt.Errorf("ADD requires two arguments")
68
-	}
69
-
70
-	return b.runContextCommand(args, true, true, "ADD")
71
-}
72
-
73
-// COPY foo /path
74
-//
75
-// Same as 'ADD' but without the tar and remote url handling.
76
-//
77
-func dispatchCopy(b *BuildFile, args []string, attributes map[string]bool) error {
78
-	if len(args) != 2 {
79
-		return fmt.Errorf("COPY requires two arguments")
80
-	}
81
-
82
-	return b.runContextCommand(args, false, false, "COPY")
83
-}
84
-
85
-// FROM imagename
86
-//
87
-// This sets the image the dockerfile will build on top of.
88
-//
89
-func from(b *BuildFile, args []string, attributes map[string]bool) error {
90
-	if len(args) != 1 {
91
-		return fmt.Errorf("FROM requires one argument")
92
-	}
93
-
94
-	name := args[0]
95
-
96
-	image, err := b.Options.Daemon.Repositories().LookupImage(name)
97
-	if err != nil {
98
-		if b.Options.Daemon.Graph().IsNotExist(err) {
99
-			image, err = b.pullImage(name)
100
-		}
101
-
102
-		// note that the top level err will still be !nil here if IsNotExist is
103
-		// not the error. This approach just simplifies hte logic a bit.
104
-		if err != nil {
105
-			return err
106
-		}
107
-	}
108
-
109
-	return b.processImageFrom(image)
110
-}
111
-
112
-// ONBUILD RUN echo yo
113
-//
114
-// ONBUILD triggers run when the image is used in a FROM statement.
115
-//
116
-// ONBUILD handling has a lot of special-case functionality, the heading in
117
-// evaluator.go and comments around dispatch() in the same file explain the
118
-// special cases. search for 'OnBuild' in internals.go for additional special
119
-// cases.
120
-//
121
-func onbuild(b *BuildFile, args []string, attributes map[string]bool) error {
122
-	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
123
-	switch triggerInstruction {
124
-	case "ONBUILD":
125
-		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
126
-	case "MAINTAINER", "FROM":
127
-		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
128
-	}
129
-
130
-	trigger := strings.Join(args, " ")
131
-
132
-	b.Config.OnBuild = append(b.Config.OnBuild, trigger)
133
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
134
-}
135
-
136
-// WORKDIR /tmp
137
-//
138
-// Set the working directory for future RUN/CMD/etc statements.
139
-//
140
-func workdir(b *BuildFile, args []string, attributes map[string]bool) error {
141
-	if len(args) != 1 {
142
-		return fmt.Errorf("WORKDIR requires exactly one argument")
143
-	}
144
-
145
-	workdir := args[0]
146
-
147
-	if workdir[0] == '/' {
148
-		b.Config.WorkingDir = workdir
149
-	} else {
150
-		if b.Config.WorkingDir == "" {
151
-			b.Config.WorkingDir = "/"
152
-		}
153
-		b.Config.WorkingDir = filepath.Join(b.Config.WorkingDir, workdir)
154
-	}
155
-
156
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
157
-}
158
-
159
-// RUN some command yo
160
-//
161
-// run a command and commit the image. Args are automatically prepended with
162
-// 'sh -c' in the event there is only one argument. The difference in
163
-// processing:
164
-//
165
-// RUN echo hi          # sh -c echo hi
166
-// RUN [ "echo", "hi" ] # echo hi
167
-//
168
-func run(b *BuildFile, args []string, attributes map[string]bool) error {
169
-	args = handleJsonArgs(args, attributes)
170
-
171
-	if b.image == "" {
172
-		return fmt.Errorf("Please provide a source image with `from` prior to run")
173
-	}
174
-
175
-	config, _, _, err := runconfig.Parse(append([]string{b.image}, args...), nil)
176
-	if err != nil {
177
-		return err
178
-	}
179
-
180
-	cmd := b.Config.Cmd
181
-	// set Cmd manually, this is special case only for Dockerfiles
182
-	b.Config.Cmd = config.Cmd
183
-	runconfig.Merge(b.Config, config)
184
-
185
-	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
186
-
187
-	log.Debugf("Command to be executed: %v", b.Config.Cmd)
188
-
189
-	hit, err := b.probeCache()
190
-	if err != nil {
191
-		return err
192
-	}
193
-	if hit {
194
-		return nil
195
-	}
196
-
197
-	c, err := b.create()
198
-	if err != nil {
199
-		return err
200
-	}
201
-
202
-	// Ensure that we keep the container mounted until the commit
203
-	// to avoid unmounting and then mounting directly again
204
-	c.Mount()
205
-	defer c.Unmount()
206
-
207
-	err = b.run(c)
208
-	if err != nil {
209
-		return err
210
-	}
211
-	if err := b.commit(c.ID, cmd, "run"); err != nil {
212
-		return err
213
-	}
214
-
215
-	return nil
216
-}
217
-
218
-// CMD foo
219
-//
220
-// Set the default command to run in the container (which may be empty).
221
-// Argument handling is the same as RUN.
222
-//
223
-func cmd(b *BuildFile, args []string, attributes map[string]bool) error {
224
-	b.Config.Cmd = handleJsonArgs(args, attributes)
225
-
226
-	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
227
-		return err
228
-	}
229
-
230
-	b.cmdSet = true
231
-	return nil
232
-}
233
-
234
-// ENTRYPOINT /usr/sbin/nginx
235
-//
236
-// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
237
-// accept the CMD as the arguments to /usr/sbin/nginx.
238
-//
239
-// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
240
-// is initialized at NewBuilder time instead of through argument parsing.
241
-//
242
-func entrypoint(b *BuildFile, args []string, attributes map[string]bool) error {
243
-	b.Config.Entrypoint = handleJsonArgs(args, attributes)
244
-
245
-	// if there is no cmd in current Dockerfile - cleanup cmd
246
-	if !b.cmdSet {
247
-		b.Config.Cmd = nil
248
-	}
249
-
250
-	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
251
-		return err
252
-	}
253
-	return nil
254
-}
255
-
256
-// EXPOSE 6667/tcp 7000/tcp
257
-//
258
-// Expose ports for links and port mappings. This all ends up in
259
-// b.Config.ExposedPorts for runconfig.
260
-//
261
-func expose(b *BuildFile, args []string, attributes map[string]bool) error {
262
-	portsTab := args
263
-
264
-	if b.Config.ExposedPorts == nil {
265
-		b.Config.ExposedPorts = make(nat.PortSet)
266
-	}
267
-
268
-	ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
269
-	if err != nil {
270
-		return err
271
-	}
272
-
273
-	for port := range ports {
274
-		if _, exists := b.Config.ExposedPorts[port]; !exists {
275
-			b.Config.ExposedPorts[port] = struct{}{}
276
-		}
277
-	}
278
-	b.Config.PortSpecs = nil
279
-
280
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
281
-}
282
-
283
-// USER foo
284
-//
285
-// Set the user to 'foo' for future commands and when running the
286
-// ENTRYPOINT/CMD at container run time.
287
-//
288
-func user(b *BuildFile, args []string, attributes map[string]bool) error {
289
-	if len(args) != 1 {
290
-		return fmt.Errorf("USER requires exactly one argument")
291
-	}
292
-
293
-	b.Config.User = args[0]
294
-	return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
295
-}
296
-
297
-// VOLUME /foo
298
-//
299
-// Expose the volume /foo for use. Will also accept the JSON form, but either
300
-// way requires exactly one argument.
301
-//
302
-func volume(b *BuildFile, args []string, attributes map[string]bool) error {
303
-	if len(args) != 1 {
304
-		return fmt.Errorf("Volume cannot be empty")
305
-	}
306
-
307
-	volume := args
308
-
309
-	if b.Config.Volumes == nil {
310
-		b.Config.Volumes = map[string]struct{}{}
311
-	}
312
-	for _, v := range volume {
313
-		b.Config.Volumes[v] = struct{}{}
314
-	}
315
-	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
316
-		return err
317
-	}
318
-	return nil
319
-}
320
-
321
-// INSERT is no longer accepted, but we still parse it.
322
-func insert(b *BuildFile, args []string, attributes map[string]bool) error {
323
-	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
324
-}
325 1
deleted file mode 100644
... ...
@@ -1,210 +0,0 @@
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).
20
-package evaluator
21
-
22
-import (
23
-	"bytes"
24
-	"errors"
25
-	"fmt"
26
-	"io"
27
-	"io/ioutil"
28
-	"os"
29
-	"path"
30
-	"strings"
31
-
32
-	"github.com/docker/docker/builder/parser"
33
-	"github.com/docker/docker/daemon"
34
-	"github.com/docker/docker/engine"
35
-	"github.com/docker/docker/pkg/tarsum"
36
-	"github.com/docker/docker/registry"
37
-	"github.com/docker/docker/runconfig"
38
-	"github.com/docker/docker/utils"
39
-)
40
-
41
-type UniqueMap map[string]struct{}
42
-
43
-var (
44
-	ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
45
-)
46
-
47
-var evaluateTable map[string]func(*BuildFile, []string, map[string]bool) error
48
-
49
-func init() {
50
-	evaluateTable = map[string]func(*BuildFile, []string, map[string]bool) error{
51
-		"env":            env,
52
-		"maintainer":     maintainer,
53
-		"add":            add,
54
-		"copy":           dispatchCopy, // copy() is a go builtin
55
-		"from":           from,
56
-		"onbuild":        onbuild,
57
-		"workdir":        workdir,
58
-		"docker-version": nullDispatch, // we don't care about docker-version
59
-		"run":            run,
60
-		"cmd":            cmd,
61
-		"entrypoint":     entrypoint,
62
-		"expose":         expose,
63
-		"volume":         volume,
64
-		"user":           user,
65
-		"insert":         insert,
66
-	}
67
-}
68
-
69
-// internal struct, used to maintain configuration of the Dockerfile's
70
-// processing as it evaluates the parsing result.
71
-type BuildFile struct {
72
-	Dockerfile *parser.Node      // the syntax tree of the dockerfile
73
-	Config     *runconfig.Config // runconfig for cmd, run, entrypoint etc.
74
-	Options    *BuildOpts        // see below
75
-
76
-	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
77
-	TmpContainers UniqueMap // a map of containers used for removes
78
-	TmpImages     UniqueMap // a map of images used for removes
79
-
80
-	image       string         // image name for commit processing
81
-	maintainer  string         // maintainer name. could probably be removed.
82
-	cmdSet      bool           // indicates is CMD was set in current Dockerfile
83
-	context     *tarsum.TarSum // the context is a tarball that is uploaded by the client
84
-	contextPath string         // the path of the temporary directory the local context is unpacked to (server side)
85
-
86
-}
87
-
88
-type BuildOpts struct {
89
-	Daemon *daemon.Daemon
90
-	Engine *engine.Engine
91
-
92
-	// effectively stdio for the run. Because it is not stdio, I said
93
-	// "Effectively". Do not use stdio anywhere in this package for any reason.
94
-	OutStream io.Writer
95
-	ErrStream io.Writer
96
-
97
-	Verbose      bool
98
-	UtilizeCache bool
99
-
100
-	// controls how images and containers are handled between steps.
101
-	Remove      bool
102
-	ForceRemove bool
103
-
104
-	AuthConfig     *registry.AuthConfig
105
-	AuthConfigFile *registry.ConfigFile
106
-
107
-	// Deprecated, original writer used for ImagePull. To be removed.
108
-	OutOld          io.Writer
109
-	StreamFormatter *utils.StreamFormatter
110
-}
111
-
112
-// Run the builder with the context. This is the lynchpin of this package. This
113
-// will (barring errors):
114
-//
115
-// * call readContext() which will set up the temporary directory and unpack
116
-//   the context into it.
117
-// * read the dockerfile
118
-// * parse the dockerfile
119
-// * walk the parse tree and execute it by dispatching to handlers. If Remove
120
-//   or ForceRemove is set, additional cleanup around containers happens after
121
-//   processing.
122
-// * Print a happy message and return the image ID.
123
-//
124
-func (b *BuildFile) Run(context io.Reader) (string, error) {
125
-	if err := b.readContext(context); err != nil {
126
-		return "", err
127
-	}
128
-
129
-	filename := path.Join(b.contextPath, "Dockerfile")
130
-	if _, err := os.Stat(filename); os.IsNotExist(err) {
131
-		return "", fmt.Errorf("Cannot build a directory without a Dockerfile")
132
-	}
133
-	fileBytes, err := ioutil.ReadFile(filename)
134
-	if err != nil {
135
-		return "", err
136
-	}
137
-	if len(fileBytes) == 0 {
138
-		return "", ErrDockerfileEmpty
139
-	}
140
-	ast, err := parser.Parse(bytes.NewReader(fileBytes))
141
-	if err != nil {
142
-		return "", err
143
-	}
144
-
145
-	b.Dockerfile = ast
146
-
147
-	for i, n := range b.Dockerfile.Children {
148
-		if err := b.dispatch(i, n); err != nil {
149
-			if b.Options.ForceRemove {
150
-				b.clearTmp(b.TmpContainers)
151
-			}
152
-			return "", err
153
-		}
154
-		fmt.Fprintf(b.Options.OutStream, " ---> %s\n", utils.TruncateID(b.image))
155
-		if b.Options.Remove {
156
-			b.clearTmp(b.TmpContainers)
157
-		}
158
-	}
159
-
160
-	if b.image == "" {
161
-		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
162
-	}
163
-
164
-	fmt.Fprintf(b.Options.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
165
-	return b.image, nil
166
-}
167
-
168
-// This method is the entrypoint to all statement handling routines.
169
-//
170
-// Almost all nodes will have this structure:
171
-// Child[Node, Node, Node] where Child is from parser.Node.Children and each
172
-// node comes from parser.Node.Next. This forms a "line" with a statement and
173
-// arguments and we process them in this normalized form by hitting
174
-// evaluateTable with the leaf nodes of the command and the BuildFile object.
175
-//
176
-// ONBUILD is a special case; in this case the parser will emit:
177
-// Child[Node, Child[Node, Node...]] where the first node is the literal
178
-// "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
179
-// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
180
-// deal with that, at least until it becomes more of a general concern with new
181
-// features.
182
-func (b *BuildFile) dispatch(stepN int, ast *parser.Node) error {
183
-	cmd := ast.Value
184
-	attrs := ast.Attributes
185
-	strs := []string{}
186
-	msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
187
-
188
-	if cmd == "onbuild" {
189
-		fmt.Fprintf(b.Options.OutStream, "%#v\n", ast.Next.Children[0].Value)
190
-		ast = ast.Next.Children[0]
191
-		strs = append(strs, b.replaceEnv(ast.Value))
192
-		msg += " " + ast.Value
193
-	}
194
-
195
-	for ast.Next != nil {
196
-		ast = ast.Next
197
-		strs = append(strs, b.replaceEnv(ast.Value))
198
-		msg += " " + ast.Value
199
-	}
200
-
201
-	fmt.Fprintf(b.Options.OutStream, "%s\n", msg)
202
-
203
-	// XXX yes, we skip any cmds that are not valid; the parser should have
204
-	// picked these out already.
205
-	if f, ok := evaluateTable[cmd]; ok {
206
-		return f(b, strs, attrs)
207
-	}
208
-
209
-	return nil
210
-}
211 1
deleted file mode 100644
... ...
@@ -1,562 +0,0 @@
1
-package evaluator
2
-
3
-// internals for handling commands. Covers many areas and a lot of
4
-// non-contiguous functionality. Please read the comments.
5
-
6
-import (
7
-	"crypto/sha256"
8
-	"encoding/hex"
9
-	"fmt"
10
-	"io"
11
-	"io/ioutil"
12
-	"net/url"
13
-	"os"
14
-	"path"
15
-	"path/filepath"
16
-	"sort"
17
-	"strings"
18
-	"syscall"
19
-	"time"
20
-
21
-	"github.com/docker/docker/archive"
22
-	"github.com/docker/docker/daemon"
23
-	imagepkg "github.com/docker/docker/image"
24
-	"github.com/docker/docker/pkg/log"
25
-	"github.com/docker/docker/pkg/parsers"
26
-	"github.com/docker/docker/pkg/symlink"
27
-	"github.com/docker/docker/pkg/system"
28
-	"github.com/docker/docker/pkg/tarsum"
29
-	"github.com/docker/docker/registry"
30
-	"github.com/docker/docker/utils"
31
-)
32
-
33
-func (b *BuildFile) readContext(context io.Reader) error {
34
-	tmpdirPath, err := ioutil.TempDir("", "docker-build")
35
-	if err != nil {
36
-		return err
37
-	}
38
-
39
-	decompressedStream, err := archive.DecompressStream(context)
40
-	if err != nil {
41
-		return err
42
-	}
43
-
44
-	b.context = &tarsum.TarSum{Reader: decompressedStream, DisableCompression: true}
45
-	if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
46
-		return err
47
-	}
48
-
49
-	b.contextPath = tmpdirPath
50
-	return nil
51
-}
52
-
53
-func (b *BuildFile) commit(id string, autoCmd []string, comment string) error {
54
-	if b.image == "" {
55
-		return fmt.Errorf("Please provide a source image with `from` prior to commit")
56
-	}
57
-	b.Config.Image = b.image
58
-	if id == "" {
59
-		cmd := b.Config.Cmd
60
-		b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
61
-		defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
62
-
63
-		hit, err := b.probeCache()
64
-		if err != nil {
65
-			return err
66
-		}
67
-		if hit {
68
-			return nil
69
-		}
70
-
71
-		container, warnings, err := b.Options.Daemon.Create(b.Config, "")
72
-		if err != nil {
73
-			return err
74
-		}
75
-		for _, warning := range warnings {
76
-			fmt.Fprintf(b.Options.OutStream, " ---> [Warning] %s\n", warning)
77
-		}
78
-		b.TmpContainers[container.ID] = struct{}{}
79
-		fmt.Fprintf(b.Options.OutStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
80
-		id = container.ID
81
-
82
-		if err := container.Mount(); err != nil {
83
-			return err
84
-		}
85
-		defer container.Unmount()
86
-	}
87
-	container := b.Options.Daemon.Get(id)
88
-	if container == nil {
89
-		return fmt.Errorf("An error occured while creating the container")
90
-	}
91
-
92
-	// Note: Actually copy the struct
93
-	autoConfig := *b.Config
94
-	autoConfig.Cmd = autoCmd
95
-	// Commit the container
96
-	image, err := b.Options.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
97
-	if err != nil {
98
-		return err
99
-	}
100
-	b.TmpImages[image.ID] = struct{}{}
101
-	b.image = image.ID
102
-	return nil
103
-}
104
-
105
-func (b *BuildFile) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
106
-	if b.context == nil {
107
-		return fmt.Errorf("No context given. Impossible to use %s", cmdName)
108
-	}
109
-
110
-	if len(args) != 2 {
111
-		return fmt.Errorf("Invalid %s format", cmdName)
112
-	}
113
-
114
-	orig := args[0]
115
-	dest := args[1]
116
-
117
-	cmd := b.Config.Cmd
118
-	b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, orig, dest)}
119
-	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
120
-	b.Config.Image = b.image
121
-
122
-	var (
123
-		origPath   = orig
124
-		destPath   = dest
125
-		remoteHash string
126
-		isRemote   bool
127
-		decompress = true
128
-	)
129
-
130
-	isRemote = utils.IsURL(orig)
131
-	if isRemote && !allowRemote {
132
-		return fmt.Errorf("Source can't be an URL for %s", cmdName)
133
-	} else if utils.IsURL(orig) {
134
-		// Initiate the download
135
-		resp, err := utils.Download(orig)
136
-		if err != nil {
137
-			return err
138
-		}
139
-
140
-		// Create a tmp dir
141
-		tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
142
-		if err != nil {
143
-			return err
144
-		}
145
-
146
-		// Create a tmp file within our tmp dir
147
-		tmpFileName := path.Join(tmpDirName, "tmp")
148
-		tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
149
-		if err != nil {
150
-			return err
151
-		}
152
-		defer os.RemoveAll(tmpDirName)
153
-
154
-		// Download and dump result to tmp file
155
-		if _, err := io.Copy(tmpFile, resp.Body); err != nil {
156
-			tmpFile.Close()
157
-			return err
158
-		}
159
-		tmpFile.Close()
160
-
161
-		// Remove the mtime of the newly created tmp file
162
-		if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
163
-			return err
164
-		}
165
-
166
-		origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
167
-
168
-		// Process the checksum
169
-		r, err := archive.Tar(tmpFileName, archive.Uncompressed)
170
-		if err != nil {
171
-			return err
172
-		}
173
-		tarSum := &tarsum.TarSum{Reader: r, DisableCompression: true}
174
-		if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
175
-			return err
176
-		}
177
-		remoteHash = tarSum.Sum(nil)
178
-		r.Close()
179
-
180
-		// If the destination is a directory, figure out the filename.
181
-		if strings.HasSuffix(dest, "/") {
182
-			u, err := url.Parse(orig)
183
-			if err != nil {
184
-				return err
185
-			}
186
-			path := u.Path
187
-			if strings.HasSuffix(path, "/") {
188
-				path = path[:len(path)-1]
189
-			}
190
-			parts := strings.Split(path, "/")
191
-			filename := parts[len(parts)-1]
192
-			if filename == "" {
193
-				return fmt.Errorf("cannot determine filename from url: %s", u)
194
-			}
195
-			destPath = dest + filename
196
-		}
197
-	}
198
-
199
-	if err := b.checkPathForAddition(origPath); err != nil {
200
-		return err
201
-	}
202
-
203
-	// Hash path and check the cache
204
-	if b.Options.UtilizeCache {
205
-		var (
206
-			hash string
207
-			sums = b.context.GetSums()
208
-		)
209
-
210
-		if remoteHash != "" {
211
-			hash = remoteHash
212
-		} else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
213
-			return err
214
-		} else if fi.IsDir() {
215
-			var subfiles []string
216
-			for file, sum := range sums {
217
-				absFile := path.Join(b.contextPath, file)
218
-				absOrigPath := path.Join(b.contextPath, origPath)
219
-				if strings.HasPrefix(absFile, absOrigPath) {
220
-					subfiles = append(subfiles, sum)
221
-				}
222
-			}
223
-			sort.Strings(subfiles)
224
-			hasher := sha256.New()
225
-			hasher.Write([]byte(strings.Join(subfiles, ",")))
226
-			hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
227
-		} else {
228
-			if origPath[0] == '/' && len(origPath) > 1 {
229
-				origPath = origPath[1:]
230
-			}
231
-			origPath = strings.TrimPrefix(origPath, "./")
232
-			if h, ok := sums[origPath]; ok {
233
-				hash = "file:" + h
234
-			}
235
-		}
236
-		b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, hash, dest)}
237
-		hit, err := b.probeCache()
238
-		if err != nil {
239
-			return err
240
-		}
241
-		// If we do not have a hash, never use the cache
242
-		if hit && hash != "" {
243
-			return nil
244
-		}
245
-	}
246
-
247
-	// Create the container
248
-	container, _, err := b.Options.Daemon.Create(b.Config, "")
249
-	if err != nil {
250
-		return err
251
-	}
252
-	b.TmpContainers[container.ID] = struct{}{}
253
-
254
-	if err := container.Mount(); err != nil {
255
-		return err
256
-	}
257
-	defer container.Unmount()
258
-
259
-	if !allowDecompression || isRemote {
260
-		decompress = false
261
-	}
262
-	if err := b.addContext(container, origPath, destPath, decompress); err != nil {
263
-		return err
264
-	}
265
-
266
-	if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, orig, dest)); err != nil {
267
-		return err
268
-	}
269
-	return nil
270
-}
271
-
272
-func (b *BuildFile) pullImage(name string) (*imagepkg.Image, error) {
273
-	remote, tag := parsers.ParseRepositoryTag(name)
274
-	pullRegistryAuth := b.Options.AuthConfig
275
-	if len(b.Options.AuthConfigFile.Configs) > 0 {
276
-		// The request came with a full auth config file, we prefer to use that
277
-		endpoint, _, err := registry.ResolveRepositoryName(remote)
278
-		if err != nil {
279
-			return nil, err
280
-		}
281
-		resolvedAuth := b.Options.AuthConfigFile.ResolveAuthConfig(endpoint)
282
-		pullRegistryAuth = &resolvedAuth
283
-	}
284
-	job := b.Options.Engine.Job("pull", remote, tag)
285
-	job.SetenvBool("json", b.Options.StreamFormatter.Json())
286
-	job.SetenvBool("parallel", true)
287
-	job.SetenvJson("authConfig", pullRegistryAuth)
288
-	job.Stdout.Add(b.Options.OutOld)
289
-	if err := job.Run(); err != nil {
290
-		return nil, err
291
-	}
292
-	image, err := b.Options.Daemon.Repositories().LookupImage(name)
293
-	if err != nil {
294
-		return nil, err
295
-	}
296
-
297
-	return image, nil
298
-}
299
-
300
-func (b *BuildFile) processImageFrom(img *imagepkg.Image) error {
301
-	b.image = img.ID
302
-
303
-	if img.Config != nil {
304
-		b.Config = img.Config
305
-	}
306
-
307
-	if b.Config.Env == nil || len(b.Config.Env) == 0 {
308
-		b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
309
-	}
310
-
311
-	// Process ONBUILD triggers if they exist
312
-	if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
313
-		fmt.Fprintf(b.Options.ErrStream, "# Executing %d build triggers\n", nTriggers)
314
-	}
315
-
316
-	// Copy the ONBUILD triggers, and remove them from the config, since the config will be commited.
317
-	onBuildTriggers := b.Config.OnBuild
318
-	b.Config.OnBuild = []string{}
319
-
320
-	// FIXME rewrite this so that builder/parser is used; right now steps in
321
-	// onbuild are muted because we have no good way to represent the step
322
-	// number
323
-	for _, step := range onBuildTriggers {
324
-		splitStep := strings.Split(step, " ")
325
-		stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " "))
326
-		switch stepInstruction {
327
-		case "ONBUILD":
328
-			return fmt.Errorf("Source image contains forbidden chained `ONBUILD ONBUILD` trigger: %s", step)
329
-		case "MAINTAINER", "FROM":
330
-			return fmt.Errorf("Source image contains forbidden %s trigger: %s", stepInstruction, step)
331
-		}
332
-
333
-		// FIXME we have to run the evaluator manually here. This does not belong
334
-		// in this function.
335
-
336
-		if f, ok := evaluateTable[strings.ToLower(stepInstruction)]; ok {
337
-			if err := f(b, splitStep[1:], nil); err != nil {
338
-				return err
339
-			}
340
-		} else {
341
-			return fmt.Errorf("%s doesn't appear to be a valid Dockerfile instruction", splitStep[0])
342
-		}
343
-	}
344
-
345
-	return nil
346
-}
347
-
348
-// probeCache checks to see if image-caching is enabled (`b.Options.UtilizeCache`)
349
-// and if so attempts to look up the current `b.image` and `b.Config` pair
350
-// in the current server `b.Options.Daemon`. If an image is found, probeCache returns
351
-// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
352
-// is any error, it returns `(false, err)`.
353
-func (b *BuildFile) probeCache() (bool, error) {
354
-	if b.Options.UtilizeCache {
355
-		if cache, err := b.Options.Daemon.ImageGetCached(b.image, b.Config); err != nil {
356
-			return false, err
357
-		} else if cache != nil {
358
-			fmt.Fprintf(b.Options.OutStream, " ---> Using cache\n")
359
-			log.Debugf("[BUILDER] Use cached version")
360
-			b.image = cache.ID
361
-			return true, nil
362
-		} else {
363
-			log.Debugf("[BUILDER] Cache miss")
364
-		}
365
-	}
366
-	return false, nil
367
-}
368
-
369
-func (b *BuildFile) create() (*daemon.Container, error) {
370
-	if b.image == "" {
371
-		return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
372
-	}
373
-	b.Config.Image = b.image
374
-
375
-	// Create the container
376
-	c, _, err := b.Options.Daemon.Create(b.Config, "")
377
-	if err != nil {
378
-		return nil, err
379
-	}
380
-	b.TmpContainers[c.ID] = struct{}{}
381
-	fmt.Fprintf(b.Options.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
382
-
383
-	// override the entry point that may have been picked up from the base image
384
-	c.Path = b.Config.Cmd[0]
385
-	c.Args = b.Config.Cmd[1:]
386
-
387
-	return c, nil
388
-}
389
-
390
-func (b *BuildFile) run(c *daemon.Container) error {
391
-	var errCh chan error
392
-	if b.Options.Verbose {
393
-		errCh = utils.Go(func() error {
394
-			// FIXME: call the 'attach' job so that daemon.Attach can be made private
395
-			//
396
-			// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
397
-			// but without hijacking for stdin. Also, with attach there can be race
398
-			// condition because of some output already was printed before it.
399
-			return <-b.Options.Daemon.Attach(c, nil, nil, b.Options.OutStream, b.Options.ErrStream)
400
-		})
401
-	}
402
-
403
-	//start the container
404
-	if err := c.Start(); err != nil {
405
-		return err
406
-	}
407
-
408
-	if errCh != nil {
409
-		if err := <-errCh; err != nil {
410
-			return err
411
-		}
412
-	}
413
-
414
-	// Wait for it to finish
415
-	if ret, _ := c.State.WaitStop(-1 * time.Second); ret != 0 {
416
-		err := &utils.JSONError{
417
-			Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret),
418
-			Code:    ret,
419
-		}
420
-		return err
421
-	}
422
-
423
-	return nil
424
-}
425
-
426
-func (b *BuildFile) checkPathForAddition(orig string) error {
427
-	origPath := path.Join(b.contextPath, orig)
428
-	origPath, err := filepath.EvalSymlinks(origPath)
429
-	if err != nil {
430
-		if os.IsNotExist(err) {
431
-			return fmt.Errorf("%s: no such file or directory", orig)
432
-		}
433
-		return err
434
-	}
435
-	if !strings.HasPrefix(origPath, b.contextPath) {
436
-		return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
437
-	}
438
-	if _, err := os.Stat(origPath); err != nil {
439
-		if os.IsNotExist(err) {
440
-			return fmt.Errorf("%s: no such file or directory", orig)
441
-		}
442
-		return err
443
-	}
444
-	return nil
445
-}
446
-
447
-func (b *BuildFile) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
448
-	var (
449
-		err        error
450
-		destExists = true
451
-		origPath   = path.Join(b.contextPath, orig)
452
-		destPath   = path.Join(container.RootfsPath(), dest)
453
-	)
454
-
455
-	if destPath != container.RootfsPath() {
456
-		destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
457
-		if err != nil {
458
-			return err
459
-		}
460
-	}
461
-
462
-	// Preserve the trailing '/'
463
-	if strings.HasSuffix(dest, "/") || dest == "." {
464
-		destPath = destPath + "/"
465
-	}
466
-
467
-	destStat, err := os.Stat(destPath)
468
-	if err != nil {
469
-		if !os.IsNotExist(err) {
470
-			return err
471
-		}
472
-		destExists = false
473
-	}
474
-
475
-	fi, err := os.Stat(origPath)
476
-	if err != nil {
477
-		if os.IsNotExist(err) {
478
-			return fmt.Errorf("%s: no such file or directory", orig)
479
-		}
480
-		return err
481
-	}
482
-
483
-	if fi.IsDir() {
484
-		return copyAsDirectory(origPath, destPath, destExists)
485
-	}
486
-
487
-	// If we are adding a remote file (or we've been told not to decompress), do not try to untar it
488
-	if decompress {
489
-		// First try to unpack the source as an archive
490
-		// to support the untar feature we need to clean up the path a little bit
491
-		// because tar is very forgiving.  First we need to strip off the archive's
492
-		// filename from the path but this is only added if it does not end in / .
493
-		tarDest := destPath
494
-		if strings.HasSuffix(tarDest, "/") {
495
-			tarDest = filepath.Dir(destPath)
496
-		}
497
-
498
-		// try to successfully untar the orig
499
-		if err := archive.UntarPath(origPath, tarDest); err == nil {
500
-			return nil
501
-		} else if err != io.EOF {
502
-			log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
503
-		}
504
-	}
505
-
506
-	if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
507
-		return err
508
-	}
509
-	if err := archive.CopyWithTar(origPath, destPath); err != nil {
510
-		return err
511
-	}
512
-
513
-	resPath := destPath
514
-	if destExists && destStat.IsDir() {
515
-		resPath = path.Join(destPath, path.Base(origPath))
516
-	}
517
-
518
-	return fixPermissions(resPath, 0, 0)
519
-}
520
-
521
-func copyAsDirectory(source, destination string, destinationExists bool) error {
522
-	if err := archive.CopyWithTar(source, destination); err != nil {
523
-		return err
524
-	}
525
-
526
-	if destinationExists {
527
-		files, err := ioutil.ReadDir(source)
528
-		if err != nil {
529
-			return err
530
-		}
531
-
532
-		for _, file := range files {
533
-			if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil {
534
-				return err
535
-			}
536
-		}
537
-		return nil
538
-	}
539
-
540
-	return fixPermissions(destination, 0, 0)
541
-}
542
-
543
-func fixPermissions(destination string, uid, gid int) error {
544
-	return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error {
545
-		if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) {
546
-			return err
547
-		}
548
-		return nil
549
-	})
550
-}
551
-
552
-func (b *BuildFile) clearTmp(containers map[string]struct{}) {
553
-	for c := range containers {
554
-		tmp := b.Options.Daemon.Get(c)
555
-		if err := b.Options.Daemon.Destroy(tmp); err != nil {
556
-			fmt.Fprintf(b.Options.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
557
-		} else {
558
-			delete(containers, c)
559
-			fmt.Fprintf(b.Options.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
560
-		}
561
-	}
562
-}
563 1
deleted file mode 100644
... ...
@@ -1,46 +0,0 @@
1
-package evaluator
2
-
3
-import (
4
-	"regexp"
5
-	"strings"
6
-)
7
-
8
-var (
9
-	TOKEN_ENV_INTERPOLATION = regexp.MustCompile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
10
-)
11
-
12
-// handle environment replacement. Used in dispatcher.
13
-func (b *BuildFile) replaceEnv(str string) string {
14
-	for _, match := range TOKEN_ENV_INTERPOLATION.FindAllString(str, -1) {
15
-		match = match[strings.Index(match, "$"):]
16
-		matchKey := strings.Trim(match, "${}")
17
-
18
-		for _, keyval := range b.Config.Env {
19
-			tmp := strings.SplitN(keyval, "=", 2)
20
-			if tmp[0] == matchKey {
21
-				str = strings.Replace(str, match, tmp[1], -1)
22
-			}
23
-		}
24
-	}
25
-
26
-	return str
27
-}
28
-
29
-func (b *BuildFile) FindEnvKey(key string) int {
30
-	for k, envVar := range b.Config.Env {
31
-		envParts := strings.SplitN(envVar, "=", 2)
32
-		if key == envParts[0] {
33
-			return k
34
-		}
35
-	}
36
-	return -1
37
-}
38
-
39
-func handleJsonArgs(args []string, attributes map[string]bool) []string {
40
-	if attributes != nil && attributes["json"] {
41
-		return args
42
-	}
43
-
44
-	// literal string command, not an exec array
45
-	return append([]string{"/bin/sh", "-c", strings.Join(args, " ")})
46
-}
47 1
new file mode 100644
... ...
@@ -0,0 +1,562 @@
0
+package builder
1
+
2
+// internals for handling commands. Covers many areas and a lot of
3
+// non-contiguous functionality. Please read the comments.
4
+
5
+import (
6
+	"crypto/sha256"
7
+	"encoding/hex"
8
+	"fmt"
9
+	"io"
10
+	"io/ioutil"
11
+	"net/url"
12
+	"os"
13
+	"path"
14
+	"path/filepath"
15
+	"sort"
16
+	"strings"
17
+	"syscall"
18
+	"time"
19
+
20
+	"github.com/docker/docker/archive"
21
+	"github.com/docker/docker/daemon"
22
+	imagepkg "github.com/docker/docker/image"
23
+	"github.com/docker/docker/pkg/log"
24
+	"github.com/docker/docker/pkg/parsers"
25
+	"github.com/docker/docker/pkg/symlink"
26
+	"github.com/docker/docker/pkg/system"
27
+	"github.com/docker/docker/pkg/tarsum"
28
+	"github.com/docker/docker/registry"
29
+	"github.com/docker/docker/utils"
30
+)
31
+
32
+func (b *BuildFile) readContext(context io.Reader) error {
33
+	tmpdirPath, err := ioutil.TempDir("", "docker-build")
34
+	if err != nil {
35
+		return err
36
+	}
37
+
38
+	decompressedStream, err := archive.DecompressStream(context)
39
+	if err != nil {
40
+		return err
41
+	}
42
+
43
+	b.context = &tarsum.TarSum{Reader: decompressedStream, DisableCompression: true}
44
+	if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
45
+		return err
46
+	}
47
+
48
+	b.contextPath = tmpdirPath
49
+	return nil
50
+}
51
+
52
+func (b *BuildFile) commit(id string, autoCmd []string, comment string) error {
53
+	if b.image == "" {
54
+		return fmt.Errorf("Please provide a source image with `from` prior to commit")
55
+	}
56
+	b.Config.Image = b.image
57
+	if id == "" {
58
+		cmd := b.Config.Cmd
59
+		b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
60
+		defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
61
+
62
+		hit, err := b.probeCache()
63
+		if err != nil {
64
+			return err
65
+		}
66
+		if hit {
67
+			return nil
68
+		}
69
+
70
+		container, warnings, err := b.Options.Daemon.Create(b.Config, "")
71
+		if err != nil {
72
+			return err
73
+		}
74
+		for _, warning := range warnings {
75
+			fmt.Fprintf(b.Options.OutStream, " ---> [Warning] %s\n", warning)
76
+		}
77
+		b.TmpContainers[container.ID] = struct{}{}
78
+		fmt.Fprintf(b.Options.OutStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
79
+		id = container.ID
80
+
81
+		if err := container.Mount(); err != nil {
82
+			return err
83
+		}
84
+		defer container.Unmount()
85
+	}
86
+	container := b.Options.Daemon.Get(id)
87
+	if container == nil {
88
+		return fmt.Errorf("An error occured while creating the container")
89
+	}
90
+
91
+	// Note: Actually copy the struct
92
+	autoConfig := *b.Config
93
+	autoConfig.Cmd = autoCmd
94
+	// Commit the container
95
+	image, err := b.Options.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
96
+	if err != nil {
97
+		return err
98
+	}
99
+	b.TmpImages[image.ID] = struct{}{}
100
+	b.image = image.ID
101
+	return nil
102
+}
103
+
104
+func (b *BuildFile) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
105
+	if b.context == nil {
106
+		return fmt.Errorf("No context given. Impossible to use %s", cmdName)
107
+	}
108
+
109
+	if len(args) != 2 {
110
+		return fmt.Errorf("Invalid %s format", cmdName)
111
+	}
112
+
113
+	orig := args[0]
114
+	dest := args[1]
115
+
116
+	cmd := b.Config.Cmd
117
+	b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, orig, dest)}
118
+	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
119
+	b.Config.Image = b.image
120
+
121
+	var (
122
+		origPath   = orig
123
+		destPath   = dest
124
+		remoteHash string
125
+		isRemote   bool
126
+		decompress = true
127
+	)
128
+
129
+	isRemote = utils.IsURL(orig)
130
+	if isRemote && !allowRemote {
131
+		return fmt.Errorf("Source can't be an URL for %s", cmdName)
132
+	} else if utils.IsURL(orig) {
133
+		// Initiate the download
134
+		resp, err := utils.Download(orig)
135
+		if err != nil {
136
+			return err
137
+		}
138
+
139
+		// Create a tmp dir
140
+		tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
141
+		if err != nil {
142
+			return err
143
+		}
144
+
145
+		// Create a tmp file within our tmp dir
146
+		tmpFileName := path.Join(tmpDirName, "tmp")
147
+		tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
148
+		if err != nil {
149
+			return err
150
+		}
151
+		defer os.RemoveAll(tmpDirName)
152
+
153
+		// Download and dump result to tmp file
154
+		if _, err := io.Copy(tmpFile, resp.Body); err != nil {
155
+			tmpFile.Close()
156
+			return err
157
+		}
158
+		tmpFile.Close()
159
+
160
+		// Remove the mtime of the newly created tmp file
161
+		if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
162
+			return err
163
+		}
164
+
165
+		origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
166
+
167
+		// Process the checksum
168
+		r, err := archive.Tar(tmpFileName, archive.Uncompressed)
169
+		if err != nil {
170
+			return err
171
+		}
172
+		tarSum := &tarsum.TarSum{Reader: r, DisableCompression: true}
173
+		if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
174
+			return err
175
+		}
176
+		remoteHash = tarSum.Sum(nil)
177
+		r.Close()
178
+
179
+		// If the destination is a directory, figure out the filename.
180
+		if strings.HasSuffix(dest, "/") {
181
+			u, err := url.Parse(orig)
182
+			if err != nil {
183
+				return err
184
+			}
185
+			path := u.Path
186
+			if strings.HasSuffix(path, "/") {
187
+				path = path[:len(path)-1]
188
+			}
189
+			parts := strings.Split(path, "/")
190
+			filename := parts[len(parts)-1]
191
+			if filename == "" {
192
+				return fmt.Errorf("cannot determine filename from url: %s", u)
193
+			}
194
+			destPath = dest + filename
195
+		}
196
+	}
197
+
198
+	if err := b.checkPathForAddition(origPath); err != nil {
199
+		return err
200
+	}
201
+
202
+	// Hash path and check the cache
203
+	if b.Options.UtilizeCache {
204
+		var (
205
+			hash string
206
+			sums = b.context.GetSums()
207
+		)
208
+
209
+		if remoteHash != "" {
210
+			hash = remoteHash
211
+		} else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
212
+			return err
213
+		} else if fi.IsDir() {
214
+			var subfiles []string
215
+			for file, sum := range sums {
216
+				absFile := path.Join(b.contextPath, file)
217
+				absOrigPath := path.Join(b.contextPath, origPath)
218
+				if strings.HasPrefix(absFile, absOrigPath) {
219
+					subfiles = append(subfiles, sum)
220
+				}
221
+			}
222
+			sort.Strings(subfiles)
223
+			hasher := sha256.New()
224
+			hasher.Write([]byte(strings.Join(subfiles, ",")))
225
+			hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
226
+		} else {
227
+			if origPath[0] == '/' && len(origPath) > 1 {
228
+				origPath = origPath[1:]
229
+			}
230
+			origPath = strings.TrimPrefix(origPath, "./")
231
+			if h, ok := sums[origPath]; ok {
232
+				hash = "file:" + h
233
+			}
234
+		}
235
+		b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, hash, dest)}
236
+		hit, err := b.probeCache()
237
+		if err != nil {
238
+			return err
239
+		}
240
+		// If we do not have a hash, never use the cache
241
+		if hit && hash != "" {
242
+			return nil
243
+		}
244
+	}
245
+
246
+	// Create the container
247
+	container, _, err := b.Options.Daemon.Create(b.Config, "")
248
+	if err != nil {
249
+		return err
250
+	}
251
+	b.TmpContainers[container.ID] = struct{}{}
252
+
253
+	if err := container.Mount(); err != nil {
254
+		return err
255
+	}
256
+	defer container.Unmount()
257
+
258
+	if !allowDecompression || isRemote {
259
+		decompress = false
260
+	}
261
+	if err := b.addContext(container, origPath, destPath, decompress); err != nil {
262
+		return err
263
+	}
264
+
265
+	if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, orig, dest)); err != nil {
266
+		return err
267
+	}
268
+	return nil
269
+}
270
+
271
+func (b *BuildFile) pullImage(name string) (*imagepkg.Image, error) {
272
+	remote, tag := parsers.ParseRepositoryTag(name)
273
+	pullRegistryAuth := b.Options.AuthConfig
274
+	if len(b.Options.AuthConfigFile.Configs) > 0 {
275
+		// The request came with a full auth config file, we prefer to use that
276
+		endpoint, _, err := registry.ResolveRepositoryName(remote)
277
+		if err != nil {
278
+			return nil, err
279
+		}
280
+		resolvedAuth := b.Options.AuthConfigFile.ResolveAuthConfig(endpoint)
281
+		pullRegistryAuth = &resolvedAuth
282
+	}
283
+	job := b.Options.Engine.Job("pull", remote, tag)
284
+	job.SetenvBool("json", b.Options.StreamFormatter.Json())
285
+	job.SetenvBool("parallel", true)
286
+	job.SetenvJson("authConfig", pullRegistryAuth)
287
+	job.Stdout.Add(b.Options.OutOld)
288
+	if err := job.Run(); err != nil {
289
+		return nil, err
290
+	}
291
+	image, err := b.Options.Daemon.Repositories().LookupImage(name)
292
+	if err != nil {
293
+		return nil, err
294
+	}
295
+
296
+	return image, nil
297
+}
298
+
299
+func (b *BuildFile) processImageFrom(img *imagepkg.Image) error {
300
+	b.image = img.ID
301
+
302
+	if img.Config != nil {
303
+		b.Config = img.Config
304
+	}
305
+
306
+	if b.Config.Env == nil || len(b.Config.Env) == 0 {
307
+		b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
308
+	}
309
+
310
+	// Process ONBUILD triggers if they exist
311
+	if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
312
+		fmt.Fprintf(b.Options.ErrStream, "# Executing %d build triggers\n", nTriggers)
313
+	}
314
+
315
+	// Copy the ONBUILD triggers, and remove them from the config, since the config will be commited.
316
+	onBuildTriggers := b.Config.OnBuild
317
+	b.Config.OnBuild = []string{}
318
+
319
+	// FIXME rewrite this so that builder/parser is used; right now steps in
320
+	// onbuild are muted because we have no good way to represent the step
321
+	// number
322
+	for _, step := range onBuildTriggers {
323
+		splitStep := strings.Split(step, " ")
324
+		stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " "))
325
+		switch stepInstruction {
326
+		case "ONBUILD":
327
+			return fmt.Errorf("Source image contains forbidden chained `ONBUILD ONBUILD` trigger: %s", step)
328
+		case "MAINTAINER", "FROM":
329
+			return fmt.Errorf("Source image contains forbidden %s trigger: %s", stepInstruction, step)
330
+		}
331
+
332
+		// FIXME we have to run the evaluator manually here. This does not belong
333
+		// in this function.
334
+
335
+		if f, ok := evaluateTable[strings.ToLower(stepInstruction)]; ok {
336
+			if err := f(b, splitStep[1:], nil); err != nil {
337
+				return err
338
+			}
339
+		} else {
340
+			return fmt.Errorf("%s doesn't appear to be a valid Dockerfile instruction", splitStep[0])
341
+		}
342
+	}
343
+
344
+	return nil
345
+}
346
+
347
+// probeCache checks to see if image-caching is enabled (`b.Options.UtilizeCache`)
348
+// and if so attempts to look up the current `b.image` and `b.Config` pair
349
+// in the current server `b.Options.Daemon`. If an image is found, probeCache returns
350
+// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
351
+// is any error, it returns `(false, err)`.
352
+func (b *BuildFile) probeCache() (bool, error) {
353
+	if b.Options.UtilizeCache {
354
+		if cache, err := b.Options.Daemon.ImageGetCached(b.image, b.Config); err != nil {
355
+			return false, err
356
+		} else if cache != nil {
357
+			fmt.Fprintf(b.Options.OutStream, " ---> Using cache\n")
358
+			log.Debugf("[BUILDER] Use cached version")
359
+			b.image = cache.ID
360
+			return true, nil
361
+		} else {
362
+			log.Debugf("[BUILDER] Cache miss")
363
+		}
364
+	}
365
+	return false, nil
366
+}
367
+
368
+func (b *BuildFile) create() (*daemon.Container, error) {
369
+	if b.image == "" {
370
+		return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
371
+	}
372
+	b.Config.Image = b.image
373
+
374
+	// Create the container
375
+	c, _, err := b.Options.Daemon.Create(b.Config, "")
376
+	if err != nil {
377
+		return nil, err
378
+	}
379
+	b.TmpContainers[c.ID] = struct{}{}
380
+	fmt.Fprintf(b.Options.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
381
+
382
+	// override the entry point that may have been picked up from the base image
383
+	c.Path = b.Config.Cmd[0]
384
+	c.Args = b.Config.Cmd[1:]
385
+
386
+	return c, nil
387
+}
388
+
389
+func (b *BuildFile) run(c *daemon.Container) error {
390
+	var errCh chan error
391
+	if b.Options.Verbose {
392
+		errCh = utils.Go(func() error {
393
+			// FIXME: call the 'attach' job so that daemon.Attach can be made private
394
+			//
395
+			// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
396
+			// but without hijacking for stdin. Also, with attach there can be race
397
+			// condition because of some output already was printed before it.
398
+			return <-b.Options.Daemon.Attach(c, nil, nil, b.Options.OutStream, b.Options.ErrStream)
399
+		})
400
+	}
401
+
402
+	//start the container
403
+	if err := c.Start(); err != nil {
404
+		return err
405
+	}
406
+
407
+	if errCh != nil {
408
+		if err := <-errCh; err != nil {
409
+			return err
410
+		}
411
+	}
412
+
413
+	// Wait for it to finish
414
+	if ret, _ := c.State.WaitStop(-1 * time.Second); ret != 0 {
415
+		err := &utils.JSONError{
416
+			Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret),
417
+			Code:    ret,
418
+		}
419
+		return err
420
+	}
421
+
422
+	return nil
423
+}
424
+
425
+func (b *BuildFile) checkPathForAddition(orig string) error {
426
+	origPath := path.Join(b.contextPath, orig)
427
+	origPath, err := filepath.EvalSymlinks(origPath)
428
+	if err != nil {
429
+		if os.IsNotExist(err) {
430
+			return fmt.Errorf("%s: no such file or directory", orig)
431
+		}
432
+		return err
433
+	}
434
+	if !strings.HasPrefix(origPath, b.contextPath) {
435
+		return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
436
+	}
437
+	if _, err := os.Stat(origPath); err != nil {
438
+		if os.IsNotExist(err) {
439
+			return fmt.Errorf("%s: no such file or directory", orig)
440
+		}
441
+		return err
442
+	}
443
+	return nil
444
+}
445
+
446
+func (b *BuildFile) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
447
+	var (
448
+		err        error
449
+		destExists = true
450
+		origPath   = path.Join(b.contextPath, orig)
451
+		destPath   = path.Join(container.RootfsPath(), dest)
452
+	)
453
+
454
+	if destPath != container.RootfsPath() {
455
+		destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
456
+		if err != nil {
457
+			return err
458
+		}
459
+	}
460
+
461
+	// Preserve the trailing '/'
462
+	if strings.HasSuffix(dest, "/") || dest == "." {
463
+		destPath = destPath + "/"
464
+	}
465
+
466
+	destStat, err := os.Stat(destPath)
467
+	if err != nil {
468
+		if !os.IsNotExist(err) {
469
+			return err
470
+		}
471
+		destExists = false
472
+	}
473
+
474
+	fi, err := os.Stat(origPath)
475
+	if err != nil {
476
+		if os.IsNotExist(err) {
477
+			return fmt.Errorf("%s: no such file or directory", orig)
478
+		}
479
+		return err
480
+	}
481
+
482
+	if fi.IsDir() {
483
+		return copyAsDirectory(origPath, destPath, destExists)
484
+	}
485
+
486
+	// If we are adding a remote file (or we've been told not to decompress), do not try to untar it
487
+	if decompress {
488
+		// First try to unpack the source as an archive
489
+		// to support the untar feature we need to clean up the path a little bit
490
+		// because tar is very forgiving.  First we need to strip off the archive's
491
+		// filename from the path but this is only added if it does not end in / .
492
+		tarDest := destPath
493
+		if strings.HasSuffix(tarDest, "/") {
494
+			tarDest = filepath.Dir(destPath)
495
+		}
496
+
497
+		// try to successfully untar the orig
498
+		if err := archive.UntarPath(origPath, tarDest); err == nil {
499
+			return nil
500
+		} else if err != io.EOF {
501
+			log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
502
+		}
503
+	}
504
+
505
+	if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
506
+		return err
507
+	}
508
+	if err := archive.CopyWithTar(origPath, destPath); err != nil {
509
+		return err
510
+	}
511
+
512
+	resPath := destPath
513
+	if destExists && destStat.IsDir() {
514
+		resPath = path.Join(destPath, path.Base(origPath))
515
+	}
516
+
517
+	return fixPermissions(resPath, 0, 0)
518
+}
519
+
520
+func copyAsDirectory(source, destination string, destinationExists bool) error {
521
+	if err := archive.CopyWithTar(source, destination); err != nil {
522
+		return err
523
+	}
524
+
525
+	if destinationExists {
526
+		files, err := ioutil.ReadDir(source)
527
+		if err != nil {
528
+			return err
529
+		}
530
+
531
+		for _, file := range files {
532
+			if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil {
533
+				return err
534
+			}
535
+		}
536
+		return nil
537
+	}
538
+
539
+	return fixPermissions(destination, 0, 0)
540
+}
541
+
542
+func fixPermissions(destination string, uid, gid int) error {
543
+	return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error {
544
+		if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) {
545
+			return err
546
+		}
547
+		return nil
548
+	})
549
+}
550
+
551
+func (b *BuildFile) clearTmp(containers map[string]struct{}) {
552
+	for c := range containers {
553
+		tmp := b.Options.Daemon.Get(c)
554
+		if err := b.Options.Daemon.Destroy(tmp); err != nil {
555
+			fmt.Fprintf(b.Options.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
556
+		} else {
557
+			delete(containers, c)
558
+			fmt.Fprintf(b.Options.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
559
+		}
560
+	}
561
+}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strings"
9 9
 
10 10
 	"github.com/docker/docker/archive"
11
-	"github.com/docker/docker/builder/evaluator"
12 11
 	"github.com/docker/docker/daemon"
13 12
 	"github.com/docker/docker/engine"
14 13
 	"github.com/docker/docker/pkg/parsers"
... ...
@@ -86,7 +85,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
86 86
 
87 87
 	sf := utils.NewStreamFormatter(job.GetenvBool("json"))
88 88
 
89
-	opts := &evaluator.BuildOpts{
89
+	opts := &BuildOpts{
90 90
 		Daemon: b.Daemon,
91 91
 		Engine: b.Engine,
92 92
 		OutStream: &utils.StdoutFormater{
93 93
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package builder
1
+
2
+import (
3
+	"regexp"
4
+	"strings"
5
+)
6
+
7
+var (
8
+	TOKEN_ENV_INTERPOLATION = regexp.MustCompile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
9
+)
10
+
11
+// handle environment replacement. Used in dispatcher.
12
+func (b *BuildFile) replaceEnv(str string) string {
13
+	for _, match := range TOKEN_ENV_INTERPOLATION.FindAllString(str, -1) {
14
+		match = match[strings.Index(match, "$"):]
15
+		matchKey := strings.Trim(match, "${}")
16
+
17
+		for _, keyval := range b.Config.Env {
18
+			tmp := strings.SplitN(keyval, "=", 2)
19
+			if tmp[0] == matchKey {
20
+				str = strings.Replace(str, match, tmp[1], -1)
21
+			}
22
+		}
23
+	}
24
+
25
+	return str
26
+}
27
+
28
+func (b *BuildFile) FindEnvKey(key string) int {
29
+	for k, envVar := range b.Config.Env {
30
+		envParts := strings.SplitN(envVar, "=", 2)
31
+		if key == envParts[0] {
32
+			return k
33
+		}
34
+	}
35
+	return -1
36
+}
37
+
38
+func handleJsonArgs(args []string, attributes map[string]bool) []string {
39
+	if attributes != nil && attributes["json"] {
40
+		return args
41
+	}
42
+
43
+	// literal string command, not an exec array
44
+	return append([]string{"/bin/sh", "-c", strings.Join(args, " ")})
45
+}