Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions frontend/dockerfile/dockerignore/dockerignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,31 @@ import (
"io"
"path/filepath"
"strings"

"github.com/pkg/errors"
)

// ReadAll reads a .dockerignore file and returns the list of file patterns
// to ignore. Note this will trim whitespace from each line as well
// as use GO's "clean" func to get the shortest/cleanest path for each.
// ReadAll reads an ignore file from a reader and returns the list of file
// patterns to ignore, applying the following rules:
//
// - An UTF8 BOM header (if present) is stripped.
// - Lines starting with "#" are considered comments and are skipped.
//
// For remaining lines:
//
// - Leading and trailing whitespace is removed from each ignore pattern.
// - It uses [filepath.Clean] to get the shortest/cleanest path for
// ignore patterns.
// - Leading forward-slashes ("/") are removed from ignore patterns,
// so "/some/path" and "some/path" are considered equivalent.
func ReadAll(reader io.Reader) ([]string, error) {
if reader == nil {
return nil, nil
}

scanner := bufio.NewScanner(reader)
var excludes []string
currentLine := 0

utf8bom := []byte{0xEF, 0xBB, 0xBF}

scanner := bufio.NewScanner(reader)
for scanner.Scan() {
scannedBytes := scanner.Bytes()
// We trim UTF8 BOM
Expand Down Expand Up @@ -59,7 +67,7 @@ func ReadAll(reader io.Reader) ([]string, error) {
excludes = append(excludes, pattern)
}
if err := scanner.Err(); err != nil {
return nil, errors.Wrap(err, "error reading .dockerignore")
return nil, err
}
return excludes, nil
}
77 changes: 35 additions & 42 deletions frontend/dockerfile/dockerignore/dockerignore_test.go
Original file line number Diff line number Diff line change
@@ -1,61 +1,54 @@
package dockerignore

import (
"os"
"path/filepath"
"strings"
"testing"
)

func TestReadAll(t *testing.T) {
di, err := ReadAll(nil)
actual, err := ReadAll(nil)
if err != nil {
t.Fatalf("Expected not to have error, got %v", err)
t.Errorf("Expected no error, got %v", err)
}

if diLen := len(di); diLen != 0 {
t.Fatalf("Expected to have zero dockerignore entry, got %d", diLen)
if entries := len(actual); entries != 0 {
t.Fatalf("Expected to have zero entries, got %d", entries)
}

diName := filepath.Join(t.TempDir(), ".dockerignore")
content := "test1\n/test2\n/a/file/here\n\nlastfile\n# this is a comment\n! /inverted/abs/path\n!\n! \n"
err = os.WriteFile(diName, []byte(content), 0600)
if err != nil {
t.Fatal(err)
}
const content = `test1
/test2
/a/file/here
diFd, err := os.Open(diName)
if err != nil {
t.Fatal(err)
lastfile
# this is a comment
! /inverted/abs/path
!
! `

expected := []string{
"test1",
"test2", // according to https://docs.docker.com/engine/reference/builder/#dockerignore-file, /foo/bar should be treated as foo/bar
"a/file/here", // according to https://docs.docker.com/engine/reference/builder/#dockerignore-file, /foo/bar should be treated as foo/bar
"lastfile",
"!inverted/abs/path",
"!",
"!",
}
defer diFd.Close()

di, err = ReadAll(diFd)
actual, err = ReadAll(strings.NewReader(content))
if err != nil {
t.Fatal(err)
t.Error(err)
}

if len(di) != 7 {
t.Fatalf("Expected 7 entries, got %v", len(di))
}
if di[0] != "test1" {
t.Fatal("First element is not test1")
}
if di[1] != "test2" { // according to https://docs.docker.com/engine/reference/builder/#dockerignore-file, /foo/bar should be treated as foo/bar
t.Fatal("Second element is not test2")
}
if di[2] != "a/file/here" { // according to https://docs.docker.com/engine/reference/builder/#dockerignore-file, /foo/bar should be treated as foo/bar
t.Fatal("Third element is not a/file/here")
}
if di[3] != "lastfile" {
t.Fatal("Fourth element is not lastfile")
}
if di[4] != "!inverted/abs/path" {
t.Fatal("Fifth element is not !inverted/abs/path")
}
if di[5] != "!" {
t.Fatalf("Sixth element is not !, but %s", di[5])
}
if di[6] != "!" {
t.Fatalf("Seventh element is not !, but %s", di[6])
if len(actual) != len(expected) {
t.Errorf("Expected %d entries, got %v", len(expected), len(actual))
}
for i, expectedLine := range expected {
if i >= len(actual) {
t.Errorf(`missing line %d: expected: "%s", got none`, i+1, expectedLine)
continue
}
if actual[i] != expectedLine {
t.Errorf(`line %d: expected: "%s", got: "%s"`, i+1, expectedLine, actual[i])
}
}
}
7 changes: 5 additions & 2 deletions frontend/dockerui/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ type Client struct {
g flightcontrol.Group[*buildContext]
bopts client.BuildOpts

dockerignore []byte
dockerignore []byte
dockerignoreName string
}

type SBOM struct {
Expand Down Expand Up @@ -375,6 +376,7 @@ func (bc *Client) ReadEntrypoint(ctx context.Context, lang string, opts ...llb.L
})
if err == nil {
bc.dockerignore = dt
bc.dockerignoreName = bctx.filename + ".dockerignore"
}

return &Source{
Expand Down Expand Up @@ -435,13 +437,14 @@ func (bc *Client) MainContext(ctx context.Context, opts ...llb.LocalOption) (*ll
dt = []byte{}
}
bc.dockerignore = dt
bc.dockerignoreName = DefaultDockerignoreName
}

var excludes []string
if len(bc.dockerignore) != 0 {
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(bc.dockerignore))
if err != nil {
return nil, errors.Wrap(err, "failed to parse dockerignore")
return nil, errors.Wrapf(err, "failed parsing %s", bc.dockerignoreName)
}
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/dockerui/namedcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (bc *Client) namedContextRecursive(ctx context.Context, name string, nameWi
if len(dt) != 0 {
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(dt))
if err != nil {
return nil, nil, err
return nil, nil, errors.Wrapf(err, "failed parsing %s", DefaultDockerignoreName)
}
}
}
Expand Down