Browse code

Fix dockerfile parser with empty line after escape

This fix tries to fix the bug reported by #24693 where an empty
line after escape will not be stopped by the parser.

This fix addresses this issue by stop the parser from continue
with an empty line after escape.

An additional integration test has been added.

This fix fixes #24693.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/07/17 04:37:16
Showing 11 changed files
... ...
@@ -176,10 +176,17 @@ func Parse(rwc io.Reader, d *Directive) (*Node, error) {
176 176
 				newline := scanner.Text()
177 177
 				currentLine++
178 178
 
179
-				if stripComments(strings.TrimSpace(newline)) == "" {
180
-					continue
179
+				// If escape followed by a comment line then stop
180
+				// Note here that comment line starts with `#` at
181
+				// the first pos of the line
182
+				if stripComments(newline) == "" {
183
+					break
181 184
 				}
182 185
 
186
+				// If escape followed by an empty line then stop
187
+				if strings.TrimSpace(newline) == "" {
188
+					break
189
+				}
183 190
 				line, child, err = ParseLine(line+newline, d)
184 191
 				if err != nil {
185 192
 					return nil, err
... ...
@@ -150,8 +150,8 @@ func TestLineInformation(t *testing.T) {
150 150
 		t.Fatalf("Error parsing dockerfile %s: %v", testFileLineInfo, err)
151 151
 	}
152 152
 
153
-	if ast.StartLine != 5 || ast.EndLine != 31 {
154
-		fmt.Fprintf(os.Stderr, "Wrong root line information: expected(%d-%d), actual(%d-%d)\n", 5, 31, ast.StartLine, ast.EndLine)
153
+	if ast.StartLine != 5 || ast.EndLine != 27 {
154
+		fmt.Fprintf(os.Stderr, "Wrong root line information: expected(%d-%d), actual(%d-%d)\n", 5, 27, ast.StartLine, ast.EndLine)
155 155
 		t.Fatalf("Root line information doesn't match result.")
156 156
 	}
157 157
 	if len(ast.Children) != 3 {
... ...
@@ -161,7 +161,7 @@ func TestLineInformation(t *testing.T) {
161 161
 	expected := [][]int{
162 162
 		{5, 5},
163 163
 		{11, 12},
164
-		{17, 31},
164
+		{17, 27},
165 165
 	}
166 166
 	for i, child := range ast.Children {
167 167
 		if child.StartLine != expected[i][0] || child.EndLine != expected[i][1] {
... ...
@@ -16,15 +16,11 @@ ENV GOPATH \
16 16
 # Install the packages we need, clean up after them and us
17 17
 RUN apt-get update \
18 18
 	&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
19
-
20
-
21 19
     && apt-get install -y --no-install-recommends git golang ca-certificates \
22 20
     && apt-get clean \
23 21
     && rm -rf /var/lib/apt/lists \
24
-
25 22
 	&& go get -v github.com/brimstone/consuldock \
26 23
     && mv $GOPATH/bin/consuldock /usr/local/bin/consuldock \
27
-
28 24
 	&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
29 25
 	&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
30 26
 	&& rm /tmp/dpkg.* \
... ...
@@ -16,10 +16,8 @@ RUN apt-get update \
16 16
     && apt-get install -y --no-install-recommends git golang ca-certificates \
17 17
     && apt-get clean \
18 18
     && rm -rf /var/lib/apt/lists \
19
-
20 19
 	&& go get -v github.com/brimstone/consuldock \
21 20
     && mv $GOPATH/bin/consuldock /usr/local/bin/consuldock \
22
-
23 21
 	&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
24 22
 	&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
25 23
 	&& rm /tmp/dpkg.* \
... ...
@@ -23,14 +23,12 @@ RUN apt-get update \
23 23
     && apt-get install -y --no-install-recommends unzip wget \
24 24
     && apt-get clean \
25 25
     && rm -rf /var/lib/apt/lists \
26
-
27 26
     && cd /tmp \
28 27
     && wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip \
29 28
        -O web_ui.zip \
30 29
     && unzip web_ui.zip \
31 30
     && mv dist /webui \
32 31
     && rm web_ui.zip \
33
-
34 32
 	&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
35 33
 	&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
36 34
 	&& rm /tmp/dpkg.*
... ...
@@ -42,10 +40,8 @@ RUN apt-get update \
42 42
     && apt-get install -y --no-install-recommends git golang ca-certificates build-essential \
43 43
     && apt-get clean \
44 44
     && rm -rf /var/lib/apt/lists \
45
-
46 45
 	&& go get -v github.com/hashicorp/consul \
47 46
 	&& mv $GOPATH/bin/consul /usr/bin/consul \
48
-
49 47
 	&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
50 48
 	&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
51 49
 	&& rm /tmp/dpkg.* \
... ...
@@ -27,8 +27,5 @@ bye\
27 27
 frog
28 28
 
29 29
 RUN echo hello \
30
-# this is a comment
31
-
32
-# this is a comment with a blank line surrounding it
33
-
34
-this is some more useful stuff
30
+# this is a comment that breaks escape continuation
31
+RUN echo this is some more useful stuff
... ...
@@ -6,4 +6,5 @@
6 6
 (run "echo hi   world  goodnight")
7 7
 (run "echo goodbyefrog")
8 8
 (run "echo goodbyefrog")
9
-(run "echo hello this is some more useful stuff")
9
+(run "echo hello")
10
+(run "echo this is some more useful stuff")
10 11
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+FROM busybox
1
+
2
+# The following will create two instructions
3
+# `Run foo`
4
+# `bar`
5
+# because empty line will break the escape.
6
+# The parser will generate the following:
7
+# (from "busybox")
8
+# (run "foo")
9
+# (bar "")
10
+# And `bar` will return an error instruction later
11
+# Note: Parse() will not immediately error out.
12
+RUN foo \
13
+
14
+bar
0 15
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+(from "busybox")
1
+(run "foo")
2
+(bar "")
... ...
@@ -6,9 +6,7 @@ RUN apt-get \update && \
6 6
 ADD \conf\\" /.znc
7 7
 
8 8
 RUN foo \
9
-
10 9
 bar \
11
-
12 10
 baz
13 11
 
14 12
 CMD [ "\/usr\\\"/bin/znc", "-f", "-r" ]
... ...
@@ -3577,8 +3577,8 @@ RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/do
3577 3577
 
3578 3578
 # Switch back to root and double check that worked exactly as we might expect it to
3579 3579
 USER root
3580
+# Add a "supplementary" group for our dockerio user
3580 3581
 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0 10:root wheel' ] && \
3581
-	# Add a "supplementary" group for our dockerio user \
3582 3582
 	echo 'supplementary:x:1002:dockerio' >> /etc/group
3583 3583
 
3584 3584
 # ... and then go verify that we get it like we expect
... ...
@@ -7106,3 +7106,38 @@ func (s *DockerSuite) TestBuildNetContainer(c *check.C) {
7106 7106
 	host, _ := dockerCmd(c, "run", "testbuildnetcontainer", "cat", "/otherhost")
7107 7107
 	c.Assert(strings.TrimSpace(host), check.Equals, "foobar")
7108 7108
 }
7109
+
7110
+// Test case for #24693
7111
+func (s *DockerSuite) TestBuildRunEmptyLineAfterEscape(c *check.C) {
7112
+	name := "testbuildemptylineafterescape"
7113
+	_, out, err := buildImageWithOut(name,
7114
+		`
7115
+FROM busybox
7116
+RUN echo x \
7117
+
7118
+RUN echo y
7119
+RUN echo z
7120
+# Comment requires the '#' to start from position 1
7121
+# RUN echo w
7122
+`, true)
7123
+	c.Assert(err, checker.IsNil)
7124
+	c.Assert(out, checker.Contains, "Step 1/4 : FROM busybox")
7125
+	c.Assert(out, checker.Contains, "Step 2/4 : RUN echo x")
7126
+	c.Assert(out, checker.Contains, "Step 3/4 : RUN echo y")
7127
+	c.Assert(out, checker.Contains, "Step 4/4 : RUN echo z")
7128
+
7129
+	// With comment, see #24693
7130
+	name = "testbuildcommentandemptylineafterescape"
7131
+	_, out, err = buildImageWithOut(name,
7132
+		`
7133
+FROM busybox
7134
+RUN echo grafana && \
7135
+    echo raintank \
7136
+#echo env-load
7137
+RUN echo vegeta
7138
+`, true)
7139
+	c.Assert(err, checker.IsNil)
7140
+	c.Assert(out, checker.Contains, "Step 1/3 : FROM busybox")
7141
+	c.Assert(out, checker.Contains, "Step 2/3 : RUN echo grafana &&     echo raintank")
7142
+	c.Assert(out, checker.Contains, "Step 3/3 : RUN echo vegeta")
7143
+}