Skip to content

Commit

Permalink
Merge pull request #218 from tonistiigi/dockerignore
Browse files Browse the repository at this point in the history
dockerfile: dockerignore support
  • Loading branch information
AkihiroSuda authored Dec 18, 2017
2 parents 51fc666 + 2d3f36d commit 8c9d66f
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 19 deletions.
29 changes: 29 additions & 0 deletions client/llb/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ func Local(name string, opts ...LocalOption) State {
if gi.IncludePatterns != "" {
attrs[pb.AttrIncludePatterns] = gi.IncludePatterns
}
if gi.ExcludePatterns != "" {
attrs[pb.AttrExcludePatterns] = gi.ExcludePatterns
}
if gi.SharedKeyHint != "" {
attrs[pb.AttrSharedKeyHint] = gi.SharedKeyHint
}

source := NewSource("local://"+name, attrs, gi.Metadata())
return NewState(source.Output())
Expand All @@ -216,15 +222,38 @@ func SessionID(id string) LocalOption {

func IncludePatterns(p []string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
if len(p) == 0 {
li.IncludePatterns = ""
return
}
dt, _ := json.Marshal(p) // empty on error
li.IncludePatterns = string(dt)
})
}

func ExcludePatterns(p []string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
if len(p) == 0 {
li.ExcludePatterns = ""
return
}
dt, _ := json.Marshal(p) // empty on error
li.ExcludePatterns = string(dt)
})
}

func SharedKeyHint(h string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
li.SharedKeyHint = h
})
}

type LocalInfo struct {
opMetaWrapper
SessionID string
IncludePatterns string
ExcludePatterns string
SharedKeyHint string
}

func HTTP(url string, opts ...HTTPOption) State {
Expand Down
5 changes: 0 additions & 5 deletions client/local.go

This file was deleted.

55 changes: 49 additions & 6 deletions frontend/dockerfile/builder/build.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package builder

import (
"bytes"
"context"
"encoding/json"
"path"
"strings"

"github.com/docker/docker/builder/dockerignore"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)

const (
Expand All @@ -19,6 +22,7 @@ const (
keyFilename = "filename"
exporterImageConfig = "containerimage.config"
defaultDockerfileName = "Dockerfile"
dockerignoreFilename = ".dockerignore"
buildArgPrefix = "build-arg:"
gitPrefix = "git://"
)
Expand All @@ -37,6 +41,7 @@ func Build(ctx context.Context, c client.Client) error {
src := llb.Local(LocalNameDockerfile,
llb.IncludePatterns([]string{filename}),
llb.SessionID(c.SessionID()),
llb.SharedKeyHint(defaultDockerfileName),
)
var buildContext *llb.State
if strings.HasPrefix(opts[LocalNameContext], gitPrefix) {
Expand All @@ -48,13 +53,50 @@ func Build(ctx context.Context, c client.Client) error {
return err
}

ref, err := c.Solve(ctx, def.ToPB(), "", nil, false)
if err != nil {
return err
}
eg, ctx2 := errgroup.WithContext(ctx)
var dtDockerfile []byte
eg.Go(func() error {
ref, err := c.Solve(ctx2, def.ToPB(), "", nil, false)
if err != nil {
return err
}

dtDockerfile, err := ref.ReadFile(ctx, filename)
if err != nil {
dtDockerfile, err = ref.ReadFile(ctx2, filename)
if err != nil {
return err
}
return nil
})
var excludes []string
eg.Go(func() error {
dockerignoreState := buildContext
if dockerignoreState == nil {
st := llb.Local(LocalNameContext,
llb.SessionID(c.SessionID()),
llb.IncludePatterns([]string{dockerignoreFilename}),
llb.SharedKeyHint(dockerignoreFilename),
)
dockerignoreState = &st
}
def, err := dockerignoreState.Marshal()
if err != nil {
return err
}
ref, err := c.Solve(ctx2, def.ToPB(), "", nil, false)
if err != nil {
return err
}
dtDockerignore, err := ref.ReadFile(ctx2, dockerignoreFilename)
if err == nil {
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(dtDockerignore))
if err != nil {
return errors.Wrap(err, "failed to parse dockerignore")
}
}
return nil
})

if err := eg.Wait(); err != nil {
return err
}

Expand All @@ -64,6 +106,7 @@ func Build(ctx context.Context, c client.Client) error {
BuildArgs: filterBuildArgs(opts),
SessionID: c.SessionID(),
BuildContext: buildContext,
Excludes: excludes,
})

if err != nil {
Expand Down
8 changes: 6 additions & 2 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ConvertOpt struct {
BuildArgs map[string]string
SessionID string
BuildContext *llb.State
Excludes []string
}

func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) {
Expand Down Expand Up @@ -166,8 +167,11 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
if err := eg.Wait(); err != nil {
return nil, nil, err
}

buildContext := llb.Local(localNameContext, llb.SessionID(opt.SessionID))
buildContext := llb.Local(localNameContext,
llb.SessionID(opt.SessionID),
llb.ExcludePatterns(opt.Excludes),
llb.SharedKeyHint(localNameContext),
)
if opt.BuildContext != nil {
buildContext = *opt.BuildContext
}
Expand Down
73 changes: 73 additions & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestIntegration(t *testing.T) {
testExportedHistory,
testExposeExpansion,
testUser,
testDockerignore,
})
}

Expand Down Expand Up @@ -503,6 +504,78 @@ EXPOSE 5000
require.Equal(t, "5000/tcp", ports[2])
}

func testDockerignore(t *testing.T, sb integration.Sandbox) {
t.Parallel()

dockerfile := []byte(`
FROM scratch
COPY . .
`)

dockerignore := []byte(`
ba*
Dockerfile
!bay
.dockerignore
`)

dir, err := tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte(`foo-contents`), 0600),
fstest.CreateFile("bar", []byte(`bar-contents`), 0600),
fstest.CreateFile("baz", []byte(`baz-contents`), 0600),
fstest.CreateFile("bay", []byte(`bay-contents`), 0600),
fstest.CreateFile(".dockerignore", dockerignore, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

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

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

err = c.Solve(context.TODO(), nil, client.SolveOpt{
Frontend: "dockerfile.v0",
Exporter: client.ExporterLocal,
ExporterAttrs: map[string]string{
"output": destDir,
},
LocalDirs: map[string]string{
builder.LocalNameDockerfile: dir,
builder.LocalNameContext: dir,
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo"))
require.NoError(t, err)
require.Equal(t, "foo-contents", string(dt))

_, err = os.Stat(filepath.Join(destDir, ".dockerignore"))
require.Error(t, err)
require.True(t, os.IsNotExist(err))

_, err = os.Stat(filepath.Join(destDir, "Dockerfile"))
require.Error(t, err)
require.True(t, os.IsNotExist(err))

_, err = os.Stat(filepath.Join(destDir, "bar"))
require.Error(t, err)
require.True(t, os.IsNotExist(err))

_, err = os.Stat(filepath.Join(destDir, "baz"))
require.Error(t, err)
require.True(t, os.IsNotExist(err))

dt, err = ioutil.ReadFile(filepath.Join(destDir, "bay"))
require.NoError(t, err)
require.Equal(t, "bay-contents", string(dt))
}

func testExportedHistory(t *testing.T, sb integration.Sandbox) {
t.Parallel()

Expand Down
14 changes: 10 additions & 4 deletions session/filesync/filesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
const (
keyOverrideExcludes = "override-excludes"
keyIncludePatterns = "include-patterns"
keyExcludePatterns = "exclude-patterns"
keyDirName = "dir-name"
)

Expand Down Expand Up @@ -55,7 +56,7 @@ func (sp *fsSyncProvider) TarStream(stream FileSync_TarStreamServer) error {
return sp.handle("tarstream", stream)
}

func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) error {
func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retErr error) {
var pr *protocol
for _, p := range supportedProtocols {
if method == p.name && isProtoSupported(p.name) {
Expand All @@ -80,8 +81,8 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) error
return errors.Errorf("no access allowed to dir %q", dirName)
}

var excludes []string
if len(opts[keyOverrideExcludes]) == 0 || opts[keyOverrideExcludes][0] != "true" {
excludes := opts[keyExcludePatterns]
if len(dir.Excludes) != 0 && (len(opts[keyOverrideExcludes]) == 0 || opts[keyOverrideExcludes][0] != "true") {
excludes = dir.Excludes
}
includes := opts[keyIncludePatterns]
Expand Down Expand Up @@ -140,7 +141,8 @@ var supportedProtocols = []protocol{
type FSSendRequestOpt struct {
Name string
IncludePatterns []string
OverrideExcludes bool
ExcludePatterns []string
OverrideExcludes bool // deprecated: this is used by docker/cli for automatically loading .dockerignore from the directory
DestDir string
CacheUpdater CacheUpdater
ProgressCb func(int, bool)
Expand Down Expand Up @@ -175,6 +177,10 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
opts[keyIncludePatterns] = opt.IncludePatterns
}

if opt.ExcludePatterns != nil {
opts[keyExcludePatterns] = opt.ExcludePatterns
}

opts[keyDirName] = []string{opt.Name}

ctx, cancel := context.WithCancel(ctx)
Expand Down
2 changes: 2 additions & 0 deletions solver/pb/attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package pb
const AttrKeepGitDir = "git.keepgitdir"
const AttrLocalSessionID = "local.session"
const AttrIncludePatterns = "local.includepattern"
const AttrExcludePatterns = "local.excludepatterns"
const AttrSharedKeyHint = "local.sharedkeyhint"
const AttrLLBDefinitionFilename = "llbbuild.filename"

const AttrHTTPChecksum = "http.checksum"
Expand Down
10 changes: 10 additions & 0 deletions source/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ func FromLLB(op *pb.Op_Source) (Identifier, error) {
return nil, err
}
id.IncludePatterns = patterns
case pb.AttrExcludePatterns:
var patterns []string
if err := json.Unmarshal([]byte(v), &patterns); err != nil {
return nil, err
}
id.ExcludePatterns = patterns
case pb.AttrSharedKeyHint:
id.SharedKeyHint = v
}
}
}
Expand Down Expand Up @@ -142,6 +150,8 @@ type LocalIdentifier struct {
Name string
SessionID string
IncludePatterns []string
ExcludePatterns []string
SharedKeyHint string
}

func NewLocalIdentifier(str string) (*LocalIdentifier, error) {
Expand Down
6 changes: 4 additions & 2 deletions source/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ func (ls *localSourceHandler) CacheKey(ctx context.Context) (string, error) {
dt, err := json.Marshal(struct {
SessionID string
IncludePatterns []string
}{SessionID: sessionID, IncludePatterns: ls.src.IncludePatterns})
ExcludePatterns []string
}{SessionID: sessionID, IncludePatterns: ls.src.IncludePatterns, ExcludePatterns: ls.src.ExcludePatterns})
if err != nil {
return "", err
}
Expand All @@ -101,7 +102,7 @@ func (ls *localSourceHandler) Snapshot(ctx context.Context) (out cache.Immutable
return nil, err
}

sharedKey := keySharedKey + ":" + ls.src.Name + ":" + caller.SharedKey()
sharedKey := keySharedKey + ":" + ls.src.Name + ":" + ls.src.SharedKeyHint + ":" + caller.SharedKey() // TODO: replace caller.SharedKey() with source based hint from client(absolute-path+nodeid)

var mutable cache.MutableRef
sis, err := ls.md.Search(sharedKey)
Expand Down Expand Up @@ -157,6 +158,7 @@ func (ls *localSourceHandler) Snapshot(ctx context.Context) (out cache.Immutable
opt := filesync.FSSendRequestOpt{
Name: ls.src.Name,
IncludePatterns: ls.src.IncludePatterns,
ExcludePatterns: ls.src.ExcludePatterns,
OverrideExcludes: false,
DestDir: dest,
CacheUpdater: &cacheUpdater{cc},
Expand Down
Loading

0 comments on commit 8c9d66f

Please sign in to comment.