Skip to content

Commit

Permalink
Add support for heredocs with ONBUILD
Browse files Browse the repository at this point in the history
Signed-off-by: Justin Chadwell <me@jedevc.com>
  • Loading branch information
jedevc committed Jun 29, 2021
1 parent 103ad93 commit 5ad1ff3
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 10 deletions.
92 changes: 92 additions & 0 deletions frontend/dockerfile/dockerfile_heredoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package dockerfile

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -12,6 +13,7 @@ import (
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

Expand All @@ -23,6 +25,7 @@ var hdTests = []integration.Test{
testRunComplexHeredoc,
testHeredocIndent,
testHeredocVarSubstitution,
testOnBuildHeredoc,
}

func init() {
Expand Down Expand Up @@ -500,3 +503,92 @@ COPY --from=build /dest /
require.Equal(t, content, string(dt))
}
}

func testOnBuildHeredoc(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)

registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrorRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)

dockerfile := []byte(`
FROM busybox
ONBUILD RUN <<EOF
echo "hello world" >> /dest
EOF
`)

dir, err := tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

target := registry + "/buildkit/testonbuildheredoc:base"
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
Exports: []client.ExportEntry{
{
Type: client.ExporterImage,
Attrs: map[string]string{
"push": "true",
"name": target,
},
},
},
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)

dockerfile = []byte(fmt.Sprintf(`
FROM %s
`, target))

dir, err = tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

dockerfile = []byte(fmt.Sprintf(`
FROM %s AS base
FROM scratch
COPY --from=base /dest /dest
`, target))

dir, err = tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(filepath.Join(destDir, "dest"))
require.NoError(t, err)
require.Equal(t, "hello world\n", string(dt))
}
5 changes: 4 additions & 1 deletion frontend/dockerfile/instructions/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,14 @@ func parseOnBuild(req parseRequest) (*OnbuildCommand, error) {
}

original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
for _, heredoc := range req.heredocs {
original += "\n" + heredoc.Content + heredoc.Name
}

return &OnbuildCommand{
Expression: original,
withNameAndCode: newWithNameAndCode(req),
}, nil

}

func parseWorkdir(req parseRequest) (*WorkdirCommand, error) {
Expand Down
29 changes: 20 additions & 9 deletions frontend/dockerfile/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,17 @@ func (node *Node) lines(start, end int) {
}

func (node *Node) canContainHeredoc() bool {
if _, allowedDirective := heredocDirectives[node.Value]; !allowedDirective {
// check for compound commands, like ONBUILD
if ok := heredocCompoundDirectives[node.Value]; ok {
if node.Next != nil && len(node.Next.Children) > 0 {
node = node.Next.Children[0]
}
}

if ok := heredocDirectives[node.Value]; !ok {
return false
}
if _, isJSON := node.Attributes["json"]; isJSON {
if isJSON := node.Attributes["json"]; isJSON {
return false
}

Expand All @@ -106,13 +113,12 @@ type Heredoc struct {
}

var (
dispatch map[string]func(string, *directives) (*Node, map[string]bool, error)
heredocDirectives map[string]bool
reWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
reDirectives = regexp.MustCompile(`^#\s*([a-zA-Z][a-zA-Z0-9]*)\s*=\s*(.+?)\s*$`)
reComment = regexp.MustCompile(`^#.*$`)
reHeredoc = regexp.MustCompile(`^(\d*)<<(-?)(['"]?)([a-zA-Z][a-zA-Z0-9]*)(['"]?)$`)
reLeadingTabs = regexp.MustCompile(`(?m)^\t+`)
dispatch map[string]func(string, *directives) (*Node, map[string]bool, error)
reWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
reDirectives = regexp.MustCompile(`^#\s*([a-zA-Z][a-zA-Z0-9]*)\s*=\s*(.+?)\s*$`)
reComment = regexp.MustCompile(`^#.*$`)
reHeredoc = regexp.MustCompile(`^(\d*)<<(-?)(['"]?)([a-zA-Z][a-zA-Z0-9]*)(['"]?)$`)
reLeadingTabs = regexp.MustCompile(`(?m)^\t+`)
)

// DefaultEscapeToken is the default escape token
Expand All @@ -123,6 +129,11 @@ var validDirectives = map[string]struct{}{
"syntax": {},
}

var (
heredocDirectives map[string]bool // directives allowed to contain heredocs
heredocCompoundDirectives map[string]bool // directives allowed to contain directives containing heredocs
)

// directive is the structure used during a build run to hold the state of
// parsing directives.
type directives struct {
Expand Down
4 changes: 4 additions & 0 deletions frontend/dockerfile/parser/parser_heredoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ func init() {
command.Copy: true,
command.Run: true,
}

heredocCompoundDirectives = map[string]bool{
command.Onbuild: true,
}
}

0 comments on commit 5ad1ff3

Please sign in to comment.