Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Docker dependencies code #466

Merged
merged 4 commits into from
May 17, 2018
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
5 changes: 1 addition & 4 deletions pkg/skaffold/bazel/bazel.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package bazel
import (
"fmt"
"os/exec"
"path/filepath"
"strings"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
Expand Down Expand Up @@ -52,9 +51,7 @@ func (*BazelDependencyResolver) GetDependencies(a *v1alpha2.Artifact) ([]string,
continue
}

dep := filepath.Join(a.Workspace, depToPath(l))

deps = append(deps, dep)
deps = append(deps, depToPath(l))
}
return deps, nil
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/skaffold/build/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package build

import (
"fmt"
"path/filepath"
"sort"
"strings"

Expand Down Expand Up @@ -119,7 +120,7 @@ func pathsForArtifact(a *v1alpha2.Artifact) ([]string, error) {
logrus.Debugf("Ignoring %s for artifact dependencies", dep)
continue
}
filteredDeps = append(filteredDeps, dep)
filteredDeps = append(filteredDeps, filepath.Join(a.Workspace, dep))
}
return filteredDeps, nil
}
Expand Down
8 changes: 2 additions & 6 deletions pkg/skaffold/docker/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,8 @@ import (
"github.com/pkg/errors"
)

func contextTarPaths(dockerfilePath string, context string) ([]string, error) {
return GetDockerfileDependencies(dockerfilePath, context)
}

func CreateDockerTarContext(w io.Writer, dockerfilePath, context string) error {
paths, err := contextTarPaths(dockerfilePath, context)
paths, err := GetDockerfileDependencies(dockerfilePath, context)
if err != nil {
return errors.Wrap(err, "getting relative tar paths")
}
Expand All @@ -41,7 +37,7 @@ func CreateDockerTarContext(w io.Writer, dockerfilePath, context string) error {
}

func CreateDockerTarGzContext(w io.Writer, dockerfilePath, context string) error {
paths, err := contextTarPaths(dockerfilePath, context)
paths, err := GetDockerfileDependencies(dockerfilePath, context)
if err != nil {
return errors.Wrap(err, "getting relative tar paths")
}
Expand Down
46 changes: 29 additions & 17 deletions pkg/skaffold/docker/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,63 @@ package docker
import (
"archive/tar"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/pkg/errors"
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestDockerContext(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()

os.Mkdir(filepath.Join(tmpDir, "files"), 0750)
ioutil.WriteFile(filepath.Join(tmpDir, "files", "ignored.txt"), []byte(""), 0644)
ioutil.WriteFile(filepath.Join(tmpDir, "files", "included.txt"), []byte(""), 0644)
ioutil.WriteFile(filepath.Join(tmpDir, ".dockerignore"), []byte("**/ignored.txt\nalsoignored.txt"), 0644)
ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte("FROM alpine\nCOPY ./files /files"), 0644)
ioutil.WriteFile(filepath.Join(tmpDir, "ignored.txt"), []byte(""), 0644)
ioutil.WriteFile(filepath.Join(tmpDir, "alsoignored.txt"), []byte(""), 0644)

reader, writer := io.Pipe()
go func() {
err := CreateDockerTarContext(writer, "Dockerfile", "../../../testdata/docker")
err := CreateDockerTarContext(writer, "Dockerfile", tmpDir)
if err != nil {
writer.CloseWithError(errors.Wrap(err, "creating docker context"))
panic(err)
writer.CloseWithError(err)
} else {
writer.Close()
}
writer.Close()
}()

var files []string
files := make(map[string]bool)
tr := tar.NewReader(reader)
for {
header, err := tr.Next()

if err == io.EOF {
break
}
if err != nil {
panic(errors.Wrap(err, "reading tar headers"))
t.Fatal(err)
}

files = append(files, header.Name)
files[header.Name] = true
}

if util.StrSliceContains(files, "ignored.txt") {
if files["ignored.txt"] {
t.Error("File ignored.txt should have been excluded, but was not")
}

if util.StrSliceContains(files, "files/ignored.txt") {
if files["alsoignored.txt"] {
t.Error("File alsoignored.txt should have been excluded, but was not")
}
if files["files/ignored.txt"] {
t.Error("File files/ignored.txt should have been excluded, but was not")
}

if !util.StrSliceContains(files, "files/included.txt") {
if !files["files/included.txt"] {
t.Error("File files/included.txt should have been included, but was not")
}

if !util.StrSliceContains(files, "Dockerfile") {
if !files["Dockerfile"] {
t.Error("File Dockerfile should have been included, but was not")
}
}
132 changes: 72 additions & 60 deletions pkg/skaffold/docker/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"sort"
"strings"
"sync"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/google/go-containerregistry/v1"

"github.com/docker/docker/builder/dockerignore"
Expand Down Expand Up @@ -57,11 +55,9 @@ func (d *DockerfileDepResolver) GetDependencies(a *v1alpha2.Artifact) ([]string,
return GetDockerfileDependencies(a.DockerArtifact.DockerfilePath, a.Workspace)
}

// GetDockerfileDependencies parses a dockerfile and returns the full paths
// of all the source files that the resulting docker image depends on.
func GetDockerfileDependencies(dockerfilePath, workspace string) ([]string, error) {
func readDockerfile(workspace, dockerfilePath string) ([]string, error) {
path := filepath.Join(workspace, dockerfilePath)
f, err := util.Fs.Open(path)
f, err := os.Open(path)
if err != nil {
return nil, errors.Wrapf(err, "opening dockerfile: %s", path)
}
Expand Down Expand Up @@ -91,7 +87,7 @@ func GetDockerfileDependencies(dockerfilePath, workspace string) ([]string, erro
for _, value := range r.AST.Children {
switch value.Value {
case add, copy:
processCopy(workspace, value, depMap, envs)
processCopy(value, depMap, envs)
case env:
envs[value.Next.Value] = value.Next.Next.Value
}
Expand All @@ -109,30 +105,86 @@ func GetDockerfileDependencies(dockerfilePath, workspace string) ([]string, erro

dispatchInstructions(res)

deps := []string{}
var deps []string
for dep := range depMap {
deps = append(deps, dep)
}
logrus.Infof("Found dependencies for dockerfile %s", deps)

expandedDeps, err := util.ExpandPaths(workspace, deps)
return deps, nil
}

func GetDockerfileDependencies(dockerfilePath, workspace string) ([]string, error) {
deps, err := readDockerfile(workspace, dockerfilePath)
if err != nil {
return nil, errors.Wrap(err, "expanding dockerfile paths")
return nil, err
}
logrus.Infof("deps %s", expandedDeps)

if !util.StrSliceContains(expandedDeps, path) {
expandedDeps = append(expandedDeps, path)
// Read patterns to ignore
var excludes []string
dockerignorePath := filepath.Join(workspace, ".dockerignore")
if _, err := os.Stat(dockerignorePath); !os.IsNotExist(err) {
r, err := os.Open(dockerignorePath)
if err != nil {
return nil, err
}
defer r.Close()

excludes, err = dockerignore.ReadAll(r)
if err != nil {
return nil, err
}
}

// Walk the workspace
files := make(map[string]bool)
for _, dep := range deps {
filepath.Walk(filepath.Join(workspace, dep), func(fpath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(workspace, fpath)
if err != nil {
return err
}

ignored, err := fileutils.Matches(relPath, excludes)
if err != nil {
return err
}

if info.IsDir() && ignored {
return filepath.SkipDir
}

if !info.IsDir() && !ignored {
files[relPath] = true
}

return nil
})
}

// Look for .dockerignore.
ignorePath := filepath.Join(workspace, ".dockerignore")
filteredDeps, err := ApplyDockerIgnore(expandedDeps, ignorePath)
// Add dockerfile?
m, err := fileutils.Matches(dockerfilePath, excludes)
if err != nil {
return nil, errors.Wrap(err, "applying dockerignore")
return nil, err
}
if !m {
files[dockerfilePath] = true
}

// Ignore .dockerignore
delete(files, ".dockerignore")

var dependencies []string
for file := range files {
dependencies = append(dependencies, file)
}
sort.Strings(dependencies)

return filteredDeps, nil
return dependencies, nil
}

func PortsFromDockerfile(r io.Reader) ([]string, error) {
Expand Down Expand Up @@ -242,7 +294,7 @@ func retrieveRemoteConfig(identifier string) (*v1.ConfigFile, error) {
return img.ConfigFile()
}

func processCopy(workspace string, value *parser.Node, paths map[string]struct{}, envs map[string]string) error {
func processCopy(value *parser.Node, paths map[string]struct{}, envs map[string]string) error {
slex := shell.NewLex('\\')
for {
// Skip last node, since it is the destination, and stop if we arrive at a comment
Expand All @@ -259,8 +311,7 @@ func processCopy(workspace string, value *parser.Node, paths map[string]struct{}
return nil
}
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
dep := path.Join(workspace, src)
paths[dep] = struct{}{}
paths[src] = struct{}{}
} else {
logrus.Debugf("Skipping watch on remote dependency %s", src)
}
Expand All @@ -286,42 +337,3 @@ func hasMultiStageFlag(flags []string) bool {
}
return false
}

func ApplyDockerIgnore(paths []string, dockerIgnorePath string) ([]string, error) {
absPaths, err := util.RelPathToAbsPath(paths)
if err != nil {
return nil, errors.Wrap(err, "getting absolute path of dependencies")
}
excludes := []string{}
if _, err := util.Fs.Stat(dockerIgnorePath); !os.IsNotExist(err) {
r, err := util.Fs.Open(dockerIgnorePath)
if err != nil {
return nil, err
}
defer r.Close()

excludes, err = dockerignore.ReadAll(r)
if err != nil {
return nil, err
}
excludes = append(excludes, ".dockerignore")
}

absPathExcludes, err := util.RelPathToAbsPath(excludes)
if err != nil {
return nil, errors.Wrap(err, "getting absolute path of docker ignored paths")
}

filteredDeps := []string{}
for _, d := range absPaths {
m, err := fileutils.Matches(d, absPathExcludes)
if err != nil {
return nil, err
}
if !m {
filteredDeps = append(filteredDeps, d)
}
}
sort.Strings(filteredDeps)
return filteredDeps, nil
}
Loading