Browse Source

Merge pull request #33719 from dnephin/warn-on-empty-continuation-carry

[Builder] Warn on empty continuation lines
Sebastiaan van Stijn 8 năm trước cách đây
mục cha
commit
8d1ae76dcb

+ 1 - 0
builder/dockerfile/builder.go

@@ -255,6 +255,7 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
 		return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
 		return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
 	}
 	}
 
 
+	dockerfile.PrintWarnings(b.Stderr)
 	b.buildArgs.WarnOnUnusedBuildArgs(b.Stderr)
 	b.buildArgs.WarnOnUnusedBuildArgs(b.Stderr)
 
 
 	if dispatchState.imageID == "" {
 	if dispatchState.imageID == "" {

+ 28 - 4
builder/dockerfile/parser/parser.go

@@ -243,6 +243,15 @@ type Result struct {
 	AST         *Node
 	AST         *Node
 	EscapeToken rune
 	EscapeToken rune
 	Platform    string
 	Platform    string
+	Warnings    []string
+}
+
+// PrintWarnings to the writer
+func (r *Result) PrintWarnings(out io.Writer) {
+	if len(r.Warnings) == 0 {
+		return
+	}
+	fmt.Fprintf(out, strings.Join(r.Warnings, "\n")+"\n")
 }
 }
 
 
 // Parse reads lines from a Reader, parses the lines into an AST and returns
 // Parse reads lines from a Reader, parses the lines into an AST and returns
@@ -252,6 +261,7 @@ func Parse(rwc io.Reader) (*Result, error) {
 	currentLine := 0
 	currentLine := 0
 	root := &Node{StartLine: -1}
 	root := &Node{StartLine: -1}
 	scanner := bufio.NewScanner(rwc)
 	scanner := bufio.NewScanner(rwc)
+	warnings := []string{}
 
 
 	var err error
 	var err error
 	for scanner.Scan() {
 	for scanner.Scan() {
@@ -272,6 +282,7 @@ func Parse(rwc io.Reader) (*Result, error) {
 			continue
 			continue
 		}
 		}
 
 
+		var hasEmptyContinuationLine bool
 		for !isEndOfLine && scanner.Scan() {
 		for !isEndOfLine && scanner.Scan() {
 			bytesRead, err := processLine(d, scanner.Bytes(), false)
 			bytesRead, err := processLine(d, scanner.Bytes(), false)
 			if err != nil {
 			if err != nil {
@@ -279,8 +290,8 @@ func Parse(rwc io.Reader) (*Result, error) {
 			}
 			}
 			currentLine++
 			currentLine++
 
 
-			// TODO: warn this is being deprecated/removed
 			if isEmptyContinuationLine(bytesRead) {
 			if isEmptyContinuationLine(bytesRead) {
+				hasEmptyContinuationLine = true
 				continue
 				continue
 			}
 			}
 
 
@@ -289,13 +300,27 @@ func Parse(rwc io.Reader) (*Result, error) {
 			line += continuationLine
 			line += continuationLine
 		}
 		}
 
 
+		if hasEmptyContinuationLine {
+			warning := "[WARNING]: Empty continuation line found in:\n    " + line
+			warnings = append(warnings, warning)
+		}
+
 		child, err := newNodeFromLine(line, d)
 		child, err := newNodeFromLine(line, d)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 		root.AddChild(child, startLine, currentLine)
 		root.AddChild(child, startLine, currentLine)
 	}
 	}
-	return &Result{AST: root, EscapeToken: d.escapeToken, Platform: d.platformToken}, nil
+
+	if len(warnings) > 0 {
+		warnings = append(warnings, "[WARNING]: Empty continuation lines will become errors in a future release.")
+	}
+	return &Result{
+		AST:         root,
+		Warnings:    warnings,
+		EscapeToken: d.escapeToken,
+		Platform:    d.platformToken,
+	}, nil
 }
 }
 
 
 func trimComments(src []byte) []byte {
 func trimComments(src []byte) []byte {
@@ -326,6 +351,5 @@ func processLine(d *Directive, token []byte, stripLeftWhitespace bool) ([]byte,
 	if stripLeftWhitespace {
 	if stripLeftWhitespace {
 		token = trimWhitespace(token)
 		token = trimWhitespace(token)
 	}
 	}
-	err := d.possibleParserDirective(string(token))
-	return trimComments(token), err
+	return trimComments(token), d.possibleParserDirective(string(token))
 }
 }

+ 38 - 19
builder/dockerfile/parser/parser_test.go

@@ -27,40 +27,39 @@ func getDirs(t *testing.T, dir string) []string {
 	return dirs
 	return dirs
 }
 }
 
 
-func TestTestNegative(t *testing.T) {
+func TestParseErrorCases(t *testing.T) {
 	for _, dir := range getDirs(t, negativeTestDir) {
 	for _, dir := range getDirs(t, negativeTestDir) {
 		dockerfile := filepath.Join(negativeTestDir, dir, "Dockerfile")
 		dockerfile := filepath.Join(negativeTestDir, dir, "Dockerfile")
 
 
 		df, err := os.Open(dockerfile)
 		df, err := os.Open(dockerfile)
-		require.NoError(t, err)
+		require.NoError(t, err, dockerfile)
 		defer df.Close()
 		defer df.Close()
 
 
 		_, err = Parse(df)
 		_, err = Parse(df)
-		assert.Error(t, err)
+		assert.Error(t, err, dockerfile)
 	}
 	}
 }
 }
 
 
-func TestTestData(t *testing.T) {
+func TestParseCases(t *testing.T) {
 	for _, dir := range getDirs(t, testDir) {
 	for _, dir := range getDirs(t, testDir) {
 		dockerfile := filepath.Join(testDir, dir, "Dockerfile")
 		dockerfile := filepath.Join(testDir, dir, "Dockerfile")
 		resultfile := filepath.Join(testDir, dir, "result")
 		resultfile := filepath.Join(testDir, dir, "result")
 
 
 		df, err := os.Open(dockerfile)
 		df, err := os.Open(dockerfile)
-		require.NoError(t, err)
+		require.NoError(t, err, dockerfile)
 		defer df.Close()
 		defer df.Close()
 
 
 		result, err := Parse(df)
 		result, err := Parse(df)
-		require.NoError(t, err)
+		require.NoError(t, err, dockerfile)
 
 
 		content, err := ioutil.ReadFile(resultfile)
 		content, err := ioutil.ReadFile(resultfile)
-		require.NoError(t, err)
+		require.NoError(t, err, resultfile)
 
 
 		if runtime.GOOS == "windows" {
 		if runtime.GOOS == "windows" {
 			// CRLF --> CR to match Unix behavior
 			// CRLF --> CR to match Unix behavior
 			content = bytes.Replace(content, []byte{'\x0d', '\x0a'}, []byte{'\x0a'}, -1)
 			content = bytes.Replace(content, []byte{'\x0d', '\x0a'}, []byte{'\x0a'}, -1)
 		}
 		}
-
-		assert.Contains(t, result.AST.Dump()+"\n", string(content), "In "+dockerfile)
+		assert.Equal(t, result.AST.Dump()+"\n", string(content), "In "+dockerfile)
 	}
 	}
 }
 }
 
 
@@ -106,7 +105,7 @@ func TestParseWords(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestLineInformation(t *testing.T) {
+func TestParseIncludesLineNumbers(t *testing.T) {
 	df, err := os.Open(testFileLineInfo)
 	df, err := os.Open(testFileLineInfo)
 	require.NoError(t, err)
 	require.NoError(t, err)
 	defer df.Close()
 	defer df.Close()
@@ -115,10 +114,8 @@ func TestLineInformation(t *testing.T) {
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	ast := result.AST
 	ast := result.AST
-	if ast.StartLine != 5 || ast.endLine != 31 {
-		fmt.Fprintf(os.Stderr, "Wrong root line information: expected(%d-%d), actual(%d-%d)\n", 5, 31, ast.StartLine, ast.endLine)
-		t.Fatal("Root line information doesn't match result.")
-	}
+	assert.Equal(t, 5, ast.StartLine)
+	assert.Equal(t, 31, ast.endLine)
 	assert.Len(t, ast.Children, 3)
 	assert.Len(t, ast.Children, 3)
 	expected := [][]int{
 	expected := [][]int{
 		{5, 5},
 		{5, 5},
@@ -126,10 +123,32 @@ func TestLineInformation(t *testing.T) {
 		{17, 31},
 		{17, 31},
 	}
 	}
 	for i, child := range ast.Children {
 	for i, child := range ast.Children {
-		if child.StartLine != expected[i][0] || child.endLine != expected[i][1] {
-			t.Logf("Wrong line information for child %d: expected(%d-%d), actual(%d-%d)\n",
-				i, expected[i][0], expected[i][1], child.StartLine, child.endLine)
-			t.Fatal("Root line information doesn't match result.")
-		}
+		msg := fmt.Sprintf("Child %d", i)
+		assert.Equal(t, expected[i], []int{child.StartLine, child.endLine}, msg)
 	}
 	}
 }
 }
+
+func TestParseWarnsOnEmptyContinutationLine(t *testing.T) {
+	dockerfile := bytes.NewBufferString(`
+FROM alpine:3.6
+
+RUN something \
+
+    following \
+
+    more
+
+RUN another \
+
+    thing
+	`)
+
+	result, err := Parse(dockerfile)
+	require.NoError(t, err)
+	warnings := result.Warnings
+	assert.Len(t, warnings, 3)
+	assert.Contains(t, warnings[0], "Empty continuation line found in")
+	assert.Contains(t, warnings[0], "RUN something     following     more")
+	assert.Contains(t, warnings[1], "RUN another     thing")
+	assert.Contains(t, warnings[2], "will become errors in a future release")
+}

+ 5 - 9
builder/remotecontext/detect.go

@@ -110,17 +110,13 @@ func newURLRemote(url string, dockerfilePath string, progressReader func(in io.R
 			return progressReader(rc), nil
 			return progressReader(rc), nil
 		},
 		},
 	})
 	})
-	if err != nil {
-		if err == dockerfileFoundErr {
-			res, err := parser.Parse(dockerfile)
-			if err != nil {
-				return nil, nil, err
-			}
-			return nil, res, nil
-		}
+	switch {
+	case err == dockerfileFoundErr:
+		res, err := parser.Parse(dockerfile)
+		return nil, res, err
+	case err != nil:
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-
 	return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
 	return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
 }
 }